Merge lp:~danilo/launchpad/ss-storm-rewrite-methods into lp:launchpad

Proposed by Данило Шеган
Status: Merged
Approved by: Graham Binns
Approved revision: no longer in the source branch.
Merged at revision: 12254
Proposed branch: lp:~danilo/launchpad/ss-storm-rewrite-methods
Merge into: lp:launchpad
Prerequisite: lp:~danilo/launchpad/ss-storm-rewrite
Diff against target: 569 lines (+102/-92)
12 files modified
lib/lp/bugs/doc/bugsubscription.txt (+33/-28)
lib/lp/bugs/doc/bugtask-search.txt (+9/-9)
lib/lp/bugs/doc/initial-bug-contacts.txt (+7/-7)
lib/lp/bugs/stories/patches-view/patches-view.txt (+1/-1)
lib/lp/bugs/tests/bugs-emailinterface.txt (+1/-1)
lib/lp/registry/browser/tests/milestone-views.txt (+1/-1)
lib/lp/registry/doc/milestone.txt (+2/-2)
lib/lp/registry/doc/person.txt (+2/-2)
lib/lp/registry/model/person.py (+4/-2)
lib/lp/registry/model/structuralsubscription.py (+38/-35)
lib/lp/registry/templates/person-structural-subscriptions.pt (+2/-2)
lib/lp/registry/tests/structural-subscription-target.txt (+2/-2)
To merge this branch: bzr merge lp:~danilo/launchpad/ss-storm-rewrite-methods
Reviewer Review Type Date Requested Status
Graham Binns (community) code Approve
Review via email: mp+46843@code.launchpad.net

Commit message

[r=gmb][ui=none][no-qa] Convert StructuralSubscription to native Storm class.

Description of the change

= Convert StructuralSubscription to Storm =

This branch completes the migration of StructuralSubscription to Storm. The first step in lp:~danilo/launchpad/bugs-subscriptions was to replace column definitions with Storm versions.

This branch continues on that and moves all .select() and .selectBy() calls on StructuralSubscription to .find calls, uses store.remove() instead of object.destroySelf(), provides a constructor similar to one provided by SQLBase and then finally switches the class to inherit from Storm instead of SQLBase.

Followed https://dev.launchpad.net/StormMigrationGuide (constructor switch was not documented there, so I'll be adding it shortly).

== Implementation details ==

There are not enough unit tests for the functionality I am changing, but the full test run over all the layers of implementation (doc tests, unit tests, API tests and browser tests) did catch a few bugs (or incompatible changes that I did). While I'd love to add new tests for all the methods in StructuralSubscriptionTargetMixin, with the existing test coverage and our plan (where we'd allow more than one StructuralSubscription per target and person), most of the code I'd write tests for would go away.

It also includes slight optimizations in two places where we go over all the subscriptions to find one for a particular subscriber. It now executes a query which returns just one result instead.

To achieve this, subscriber parameter is added to getSubscriptions(), but it's not exposed in the interface because we are only using it from inside the module. That looks dirty even to me, so I'd be happy to change it if a reviewer thinks I should do it.

I am ignoring doctest lint errors since it might make diff harder to read. I'd be happy to update it after the review is done.

== Tests ==

bin/test -cvvt bugsubscription.txt -t bugnotification-sending.txt -t structural-subscriptions.txt -t test_structuralsubscription

== Demo and Q/A ==

no-qa

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/bugs/doc/bugsubscription.txt
  lib/lp/registry/model/structuralsubscription.py
  lib/lp/registry/model/person.py

./lib/lp/bugs/doc/bugsubscription.txt
       1: narrative uses a moin header.
       6: narrative uses a moin header.
      25: narrative uses a moin header.
     438: source has bad indentation.
     442: source has bad indentation.
     459: source has bad indentation.
     474: source has bad indentation.
     487: source has bad indentation.
     494: want exceeds 78 characters.
     528: narrative uses a moin header.
     589: narrative uses a moin header.
     644: narrative uses a moin header.
     852: narrative uses a moin header.

To post a comment you must log in.
Revision history for this message
Graham Binns (gmb) :
review: Approve (code)
Revision history for this message
Robert Collins (lifeless) wrote :

We can't use the Storm base class directly, we need a launchpad
specific one with CachedProperty __invalidate__ hooked in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/bugs/doc/bugsubscription.txt'
--- lib/lp/bugs/doc/bugsubscription.txt 2011-01-18 22:16:03 +0000
+++ lib/lp/bugs/doc/bugsubscription.txt 2011-01-20 17:50:31 +0000
@@ -1,9 +1,11 @@
1= BugSubscription =1BugSubscription
2===============
23
3Users can get email notifications of changes to bugs by subscribing to4Users can get email notifications of changes to bugs by subscribing to
4them.5them.
56
6== Bug Subscriber APIs ==7Bug Subscriber APIs
8-------------------
79
8First, let's login:10First, let's login:
911
@@ -22,7 +24,8 @@
22This list returns only *direct* subscribers. Bugs can also have24This list returns only *direct* subscribers. Bugs can also have
23indirect subscribers.25indirect subscribers.
2426
25=== Direct vs. Indirect Subscriptions ===27Direct vs. Indirect Subscriptions
28.................................
2629
27A user is directly subscribed to a bug if they or someone else has30A user is directly subscribed to a bug if they or someone else has
28subscribed them to the bug.31subscribed them to the bug.
@@ -147,6 +150,7 @@
147 >>> subscription_no_priv = linux_source.addBugSubscription(150 >>> subscription_no_priv = linux_source.addBugSubscription(
148 ... mr_no_privs, mr_no_privs)151 ... mr_no_privs, mr_no_privs)
149152
153 >>> transaction.commit()
150 >>> print_displayname(154 >>> print_displayname(
151 ... sub.subscriber for sub in linux_source.bug_subscriptions)155 ... sub.subscriber for sub in linux_source.bug_subscriptions)
152 No Privileges Person156 No Privileges Person
@@ -434,12 +438,12 @@
434a bug, and why, IBug.getBugNotificationRecipients() assembles an438a bug, and why, IBug.getBugNotificationRecipients() assembles an
435INotificationRecipientSet instance for us:439INotificationRecipientSet instance for us:
436440
437 >>> recipients = linux_source_bug.getBugNotificationRecipients()441 >>> recipients = linux_source_bug.getBugNotificationRecipients()
438442
439You can query for the addresses and reasons:443You can query for the addresses and reasons:
440444
441 >>> addresses = recipients.getEmails()445 >>> addresses = recipients.getEmails()
442 >>> [(address, recipients.getReason(address)[1]) for address in addresses]446 >>> [(address, recipients.getReason(address)[1]) for address in addresses]
443 [('foo.bar@canonical.com', 'Subscriber'),447 [('foo.bar@canonical.com', 'Subscriber'),
444 ('mark@example.com', 'Subscriber'),448 ('mark@example.com', 'Subscriber'),
445 ('no-priv@canonical.com', 'Subscriber'),449 ('no-priv@canonical.com', 'Subscriber'),
@@ -455,10 +459,10 @@
455getBugNotificationRecipients() even if the parameter level is larger than459getBugNotificationRecipients() even if the parameter level is larger than
456his structural subscription setting.460his structural subscription setting.
457461
458 >>> recipients = linux_source_bug.getBugNotificationRecipients(462 >>> recipients = linux_source_bug.getBugNotificationRecipients(
459 ... level=BugNotificationLevel.COMMENTS)463 ... level=BugNotificationLevel.COMMENTS)
460 >>> addresses = recipients.getEmails()464 >>> addresses = recipients.getEmails()
461 >>> [(address, recipients.getReason(address)[1]) for address in addresses]465 >>> [(address, recipients.getReason(address)[1]) for address in addresses]
462 [('foo.bar@canonical.com', 'Subscriber'),466 [('foo.bar@canonical.com', 'Subscriber'),
463 ('mark@example.com', 'Subscriber'),467 ('mark@example.com', 'Subscriber'),
464 ('no-priv@canonical.com', 'Subscriber'),468 ('no-priv@canonical.com', 'Subscriber'),
@@ -470,11 +474,11 @@
470longer included in the result of getBugNotificationRecipients() for474longer included in the result of getBugNotificationRecipients() for
471the COMMENTS level...475the COMMENTS level...
472476
473 >>> linux_source_bug.unsubscribe(mr_no_privs, mr_no_privs)477 >>> linux_source_bug.unsubscribe(mr_no_privs, mr_no_privs)
474 >>> recipients = linux_source_bug.getBugNotificationRecipients(478 >>> recipients = linux_source_bug.getBugNotificationRecipients(
475 ... level=BugNotificationLevel.COMMENTS)479 ... level=BugNotificationLevel.COMMENTS)
476 >>> addresses = recipients.getEmails()480 >>> addresses = recipients.getEmails()
477 >>> [(address, recipients.getReason(address)[1]) for address in addresses]481 >>> [(address, recipients.getReason(address)[1]) for address in addresses]
478 [('foo.bar@canonical.com', 'Subscriber'),482 [('foo.bar@canonical.com', 'Subscriber'),
479 ('mark@example.com', 'Subscriber'),483 ('mark@example.com', 'Subscriber'),
480 ('robertc@robertcollins.net', 'Subscriber'),484 ('robertc@robertcollins.net', 'Subscriber'),
@@ -483,11 +487,11 @@
483487
484...but remains included for the level LIFECYCLE.488...but remains included for the level LIFECYCLE.
485489
486 >>> linux_source_bug.unsubscribe(mr_no_privs, mr_no_privs)490 >>> linux_source_bug.unsubscribe(mr_no_privs, mr_no_privs)
487 >>> recipients = linux_source_bug.getBugNotificationRecipients(491 >>> recipients = linux_source_bug.getBugNotificationRecipients(
488 ... level=BugNotificationLevel.LIFECYCLE)492 ... level=BugNotificationLevel.LIFECYCLE)
489 >>> addresses = recipients.getEmails()493 >>> addresses = recipients.getEmails()
490 >>> [(address, recipients.getReason(address)[1]) for address in addresses]494 >>> [(address, recipients.getReason(address)[1]) for address in addresses]
491 [('foo.bar@canonical.com', 'Subscriber'),495 [('foo.bar@canonical.com', 'Subscriber'),
492 ('mark@example.com', 'Subscriber'),496 ('mark@example.com', 'Subscriber'),
493 ('no-priv@canonical.com', u'Subscriber (linux-source-2.6.15 in ubuntu)'),497 ('no-priv@canonical.com', u'Subscriber (linux-source-2.6.15 in ubuntu)'),
@@ -524,7 +528,8 @@
524 >>> bug_five.isSubscribedToDupes(sample_person)528 >>> bug_five.isSubscribedToDupes(sample_person)
525 True529 True
526530
527== Subscribing and Unsubscribing ==531Subscribing and Unsubscribing
532-----------------------------
528533
529To subscribe people to and unsubscribe people from a bug, use534To subscribe people to and unsubscribe people from a bug, use
530IBug.subscribe and IBug.unsubscribe:535IBug.subscribe and IBug.unsubscribe:
@@ -585,7 +590,8 @@
585 False590 False
586591
587592
588=== Determining whether a user can unsubscribe someone ===593Determining whether a user can unsubscribe someone
594..................................................
589595
590As user can't unsubscribe just anyone from a bug. To check whether596As user can't unsubscribe just anyone from a bug. To check whether
591someone can be unusubscribed, the canBeUnsubscribedByUser() method on597someone can be unusubscribed, the canBeUnsubscribedByUser() method on
@@ -640,14 +646,12 @@
640 UserCannotUnsubscribePerson: ...646 UserCannotUnsubscribePerson: ...
641647
642648
643== Automatic Subscriptions on Bug Creation ==649Automatic Subscriptions on Bug Creation
650---------------------------------------
644651
645When a new bug is opened, only the bug reporter is automatically, explicitly652When a new bug is opened, only the bug reporter is automatically, explicitly
646subscribed to the bug:653subscribed to the bug:
647654
648XXX: Brad Bollenbach, 2005-11-25: These bits need real sample data. See
649https://launchpad.net/bugs/5484.
650
651Define a function that get subscriber email addresses back conveniently:655Define a function that get subscriber email addresses back conveniently:
652656
653 >>> def getSubscribers(bug):657 >>> def getSubscribers(bug):
@@ -804,7 +808,7 @@
804So, if the Ubuntu team is added as a bug contact to evolution:808So, if the Ubuntu team is added as a bug contact to evolution:
805809
806 >>> evolution.addBugSubscription(ubuntu_team, ubuntu_team)810 >>> evolution.addBugSubscription(ubuntu_team, ubuntu_team)
807 <StructuralSubscription at ...>811 <...StructuralSubscription object at ...>
808812
809The team will be implicitly subscribed to the previous bug we813The team will be implicitly subscribed to the previous bug we
810created. (Remember that Sample Person is also implicitly subscribed814created. (Remember that Sample Person is also implicitly subscribed
@@ -848,7 +852,8 @@
848 ['foo.bar@canonical.com', 'support@ubuntu.com']852 ['foo.bar@canonical.com', 'support@ubuntu.com']
849853
850854
851== Subscribed by ==855Subscribed by
856-------------
852857
853Each `BugSubscription` records who created it, and provides a handy858Each `BugSubscription` records who created it, and provides a handy
854utility method for formatting this information. The methods859utility method for formatting this information. The methods
855860
=== modified file 'lib/lp/bugs/doc/bugtask-search.txt'
--- lib/lp/bugs/doc/bugtask-search.txt 2010-10-25 15:03:38 +0000
+++ lib/lp/bugs/doc/bugtask-search.txt 2011-01-20 17:50:31 +0000
@@ -1230,7 +1230,7 @@
1230 ... name='product-struct-subber')1230 ... name='product-struct-subber')
1231 >>> firefox.addBugSubscription(product_struct_subber,1231 >>> firefox.addBugSubscription(product_struct_subber,
1232 ... product_struct_subber)1232 ... product_struct_subber)
1233 <StructuralSubscription at ...>1233 <...StructuralSubscription object at ...>
1234 >>> product_struct_sub_search = BugTaskSearchParams(1234 >>> product_struct_sub_search = BugTaskSearchParams(
1235 ... user=None, structural_subscriber=product_struct_subber)1235 ... user=None, structural_subscriber=product_struct_subber)
1236 >>> found_bugtasks = bugtask_set.search(product_struct_sub_search)1236 >>> found_bugtasks = bugtask_set.search(product_struct_sub_search)
@@ -1248,7 +1248,7 @@
1248 ... name='series-struct-subber')1248 ... name='series-struct-subber')
1249 >>> product_series.addBugSubscription(series_struct_subber,1249 >>> product_series.addBugSubscription(series_struct_subber,
1250 ... series_struct_subber)1250 ... series_struct_subber)
1251 <StructuralSubscription at ...>1251 <...StructuralSubscription object at ...>
1252 >>> series_struct_sub_search = BugTaskSearchParams(1252 >>> series_struct_sub_search = BugTaskSearchParams(
1253 ... user=None, structural_subscriber=series_struct_subber)1253 ... user=None, structural_subscriber=series_struct_subber)
1254 >>> found_bugtasks = bugtask_set.search(series_struct_sub_search)1254 >>> found_bugtasks = bugtask_set.search(series_struct_sub_search)
@@ -1269,7 +1269,7 @@
1269 ... name='project-struct-subber')1269 ... name='project-struct-subber')
1270 >>> project.addBugSubscription(project_struct_subber,1270 >>> project.addBugSubscription(project_struct_subber,
1271 ... project_struct_subber)1271 ... project_struct_subber)
1272 <StructuralSubscription at ...>1272 <...StructuralSubscription object at ...>
1273 >>> project_struct_sub_search = BugTaskSearchParams(1273 >>> project_struct_sub_search = BugTaskSearchParams(
1274 ... user=None, structural_subscriber=project_struct_subber)1274 ... user=None, structural_subscriber=project_struct_subber)
1275 >>> found_bugtasks = bugtask_set.search(project_struct_sub_search)1275 >>> found_bugtasks = bugtask_set.search(project_struct_sub_search)
@@ -1285,7 +1285,7 @@
1285 >>> product2.project = project1285 >>> product2.project = project
1286 >>> product2.addBugSubscription(project_struct_subber,1286 >>> product2.addBugSubscription(project_struct_subber,
1287 ... project_struct_subber)1287 ... project_struct_subber)
1288 <StructuralSubscription at ...>1288 <...StructuralSubscription object at ...>
1289 >>> project_struct_sub_search = BugTaskSearchParams(1289 >>> project_struct_sub_search = BugTaskSearchParams(
1290 ... user=None, structural_subscriber=project_struct_subber)1290 ... user=None, structural_subscriber=project_struct_subber)
1291 >>> found_bugtasks = bugtask_set.search(project_struct_sub_search)1291 >>> found_bugtasks = bugtask_set.search(project_struct_sub_search)
@@ -1300,7 +1300,7 @@
1300 ... name='milestone-struct-subber')1300 ... name='milestone-struct-subber')
1301 >>> product_milestone.addBugSubscription(milestone_struct_subber,1301 >>> product_milestone.addBugSubscription(milestone_struct_subber,
1302 ... milestone_struct_subber)1302 ... milestone_struct_subber)
1303 <StructuralSubscription at ...>1303 <...StructuralSubscription object at ...>
1304 >>> milestone_struct_sub_search = BugTaskSearchParams(1304 >>> milestone_struct_sub_search = BugTaskSearchParams(
1305 ... user=None, structural_subscriber=milestone_struct_subber)1305 ... user=None, structural_subscriber=milestone_struct_subber)
1306 >>> found_bugtasks = bugtask_set.search(milestone_struct_sub_search)1306 >>> found_bugtasks = bugtask_set.search(milestone_struct_sub_search)
@@ -1315,7 +1315,7 @@
1315 ... name='distro-struct-subber')1315 ... name='distro-struct-subber')
1316 >>> ubuntu.addBugSubscription(distro_struct_subber,1316 >>> ubuntu.addBugSubscription(distro_struct_subber,
1317 ... distro_struct_subber)1317 ... distro_struct_subber)
1318 <StructuralSubscription at ...>1318 <...StructuralSubscription object at ...>
1319 >>> distro_struct_sub_search = BugTaskSearchParams(1319 >>> distro_struct_sub_search = BugTaskSearchParams(
1320 ... user=None, structural_subscriber=distro_struct_subber)1320 ... user=None, structural_subscriber=distro_struct_subber)
1321 >>> found_bugtasks = bugtask_set.search(distro_struct_sub_search)1321 >>> found_bugtasks = bugtask_set.search(distro_struct_sub_search)
@@ -1332,7 +1332,7 @@
1332 ... name='distro-series-struct-subber')1332 ... name='distro-series-struct-subber')
1333 >>> hoary.addBugSubscription(distro_series_struct_subber,1333 >>> hoary.addBugSubscription(distro_series_struct_subber,
1334 ... distro_series_struct_subber)1334 ... distro_series_struct_subber)
1335 <StructuralSubscription at ...>1335 <...StructuralSubscription object at ...>
1336 >>> distro_series_struct_sub_search = BugTaskSearchParams(1336 >>> distro_series_struct_sub_search = BugTaskSearchParams(
1337 ... user=None, structural_subscriber=distro_series_struct_subber)1337 ... user=None, structural_subscriber=distro_series_struct_subber)
1338 >>> found_bugtasks = bugtask_set.search(distro_series_struct_sub_search)1338 >>> found_bugtasks = bugtask_set.search(distro_series_struct_sub_search)
@@ -1349,7 +1349,7 @@
1349 ... name='package-struct-subber')1349 ... name='package-struct-subber')
1350 >>> ubuntu_firefox.addBugSubscription(package_struct_subber,1350 >>> ubuntu_firefox.addBugSubscription(package_struct_subber,
1351 ... package_struct_subber)1351 ... package_struct_subber)
1352 <StructuralSubscription at ...>1352 <...StructuralSubscription object at ...>
1353 >>> package_struct_sub_search = BugTaskSearchParams(1353 >>> package_struct_sub_search = BugTaskSearchParams(
1354 ... user=None, structural_subscriber=package_struct_subber)1354 ... user=None, structural_subscriber=package_struct_subber)
1355 >>> found_bugtasks = bugtask_set.search(package_struct_sub_search)1355 >>> found_bugtasks = bugtask_set.search(package_struct_sub_search)
@@ -1363,7 +1363,7 @@
13631363
1364 >>> product_series.addBugSubscription(package_struct_subber,1364 >>> product_series.addBugSubscription(package_struct_subber,
1365 ... package_struct_subber)1365 ... package_struct_subber)
1366 <StructuralSubscription at ...>1366 <...StructuralSubscription object at ...>
1367 >>> package_struct_sub_search = BugTaskSearchParams(1367 >>> package_struct_sub_search = BugTaskSearchParams(
1368 ... user=None, structural_subscriber=package_struct_subber)1368 ... user=None, structural_subscriber=package_struct_subber)
1369 >>> found_bugtasks = bugtask_set.search(package_struct_sub_search)1369 >>> found_bugtasks = bugtask_set.search(package_struct_sub_search)
13701370
=== modified file 'lib/lp/bugs/doc/initial-bug-contacts.txt'
--- lib/lp/bugs/doc/initial-bug-contacts.txt 2010-12-24 11:02:19 +0000
+++ lib/lp/bugs/doc/initial-bug-contacts.txt 2011-01-20 17:50:31 +0000
@@ -38,7 +38,7 @@
38 >>> login("foo.bar@canonical.com")38 >>> login("foo.bar@canonical.com")
3939
40 >>> debian_firefox.addBugSubscription(sample_person, sample_person)40 >>> debian_firefox.addBugSubscription(sample_person, sample_person)
41 <StructuralSubscription ...>41 <...StructuralSubscription object at ...>
42 >>> [pbc.subscriber.name for pbc in debian_firefox.bug_subscriptions]42 >>> [pbc.subscriber.name for pbc in debian_firefox.bug_subscriptions]
43 [u'name12']43 [u'name12']
4444
@@ -46,13 +46,13 @@
46already subscribe to that package will return the existing subscription.46already subscribe to that package will return the existing subscription.
4747
48 >>> debian_firefox.addBugSubscription(sample_person, sample_person)48 >>> debian_firefox.addBugSubscription(sample_person, sample_person)
49 <StructuralSubscription ...>49 <...StructuralSubscription object at ...>
5050
51Let's add an ITeam as one of the subscribers:51Let's add an ITeam as one of the subscribers:
5252
53 >>> ubuntu_team = personset.getByName("ubuntu-team")53 >>> ubuntu_team = personset.getByName("ubuntu-team")
54 >>> debian_firefox.addBugSubscription(ubuntu_team, ubuntu_team)54 >>> debian_firefox.addBugSubscription(ubuntu_team, ubuntu_team)
55 <StructuralSubscription ...>55 <...StructuralSubscription object at ...>
5656
57 >>> sorted([sub.subscriber.name for sub in debian_firefox.bug_subscriptions])57 >>> sorted([sub.subscriber.name for sub in debian_firefox.bug_subscriptions])
58 [u'name12', u'ubuntu-team']58 [u'name12', u'ubuntu-team']
@@ -125,9 +125,9 @@
125125
126 >>> daf = personset.getByName("daf")126 >>> daf = personset.getByName("daf")
127 >>> ubuntu_pmount.addBugSubscription(daf, daf)127 >>> ubuntu_pmount.addBugSubscription(daf, daf)
128 <StructuralSubscription ...>128 <...StructuralSubscription object at ...>
129 >>> ubuntu_pmount.addBugSubscription(sample_person, sample_person)129 >>> ubuntu_pmount.addBugSubscription(sample_person, sample_person)
130 <StructuralSubscription ...>130 <...StructuralSubscription object at ...>
131131
132 >>> old_state = Snapshot(132 >>> old_state = Snapshot(
133 ... bug_one_in_ubuntu_firefox, providing=IDistroBugTask)133 ... bug_one_in_ubuntu_firefox, providing=IDistroBugTask)
@@ -247,7 +247,7 @@
247 >>> ubuntu_gnome.preferredemail is None247 >>> ubuntu_gnome.preferredemail is None
248 True248 True
249 >>> ubuntu_pmount.addBugSubscription(ubuntu_gnome, ubuntu_gnome)249 >>> ubuntu_pmount.addBugSubscription(ubuntu_gnome, ubuntu_gnome)
250 <StructuralSubscription ...>250 <...StructuralSubscription object at ...>
251251
252 >>> old_state = Snapshot(252 >>> old_state = Snapshot(
253 ... bug_one_in_ubuntu_firefox, providing=IDistroBugTask)253 ... bug_one_in_ubuntu_firefox, providing=IDistroBugTask)
@@ -279,7 +279,7 @@
279279
280 >>> pitti = personset.getByName("martin-pitt")280 >>> pitti = personset.getByName("martin-pitt")
281 >>> ubuntu_firefox.addBugSubscription(pitti, pitti)281 >>> ubuntu_firefox.addBugSubscription(pitti, pitti)
282 <StructuralSubscription ...>282 <...StructuralSubscription object at ...>
283283
284and then the bug gets reassigned to mozilla firefox:284and then the bug gets reassigned to mozilla firefox:
285285
286286
=== modified file 'lib/lp/bugs/stories/patches-view/patches-view.txt'
--- lib/lp/bugs/stories/patches-view/patches-view.txt 2010-07-17 21:02:11 +0000
+++ lib/lp/bugs/stories/patches-view/patches-view.txt 2011-01-20 17:50:31 +0000
@@ -420,7 +420,7 @@
420 ... name="project-subscriber", displayname="Project Subscriber")420 ... name="project-subscriber", displayname="Project Subscriber")
421 >>> add_bug_sub = lambda *args: patchy_product.addBugSubscription(*args)421 >>> add_bug_sub = lambda *args: patchy_product.addBugSubscription(*args)
422 >>> with_anybody(add_bug_sub)(project_subscriber, project_subscriber)422 >>> with_anybody(add_bug_sub)(project_subscriber, project_subscriber)
423 <StructuralSubscription at ...>423 <...StructuralSubscription object at ...>
424 >>> from zope.security.proxy import removeSecurityProxy424 >>> from zope.security.proxy import removeSecurityProxy
425 >>> subscriber_name = removeSecurityProxy(project_subscriber).name425 >>> subscriber_name = removeSecurityProxy(project_subscriber).name
426 >>> anon_browser.open(426 >>> anon_browser.open(
427427
=== modified file 'lib/lp/bugs/tests/bugs-emailinterface.txt'
--- lib/lp/bugs/tests/bugs-emailinterface.txt 2010-12-13 13:40:38 +0000
+++ lib/lp/bugs/tests/bugs-emailinterface.txt 2011-01-20 17:50:31 +0000
@@ -1679,7 +1679,7 @@
1679 >>> helge = getUtility(IPersonSet).getByName('kreutzm')1679 >>> helge = getUtility(IPersonSet).getByName('kreutzm')
1680 >>> mozilla_package = ubuntu.getSourcePackage(mozilla_name)1680 >>> mozilla_package = ubuntu.getSourcePackage(mozilla_name)
1681 >>> mozilla_package.addBugSubscription(helge, helge)1681 >>> mozilla_package.addBugSubscription(helge, helge)
1682 <StructuralSubscription ...>1682 <...StructuralSubscription ...>
1683 >>> commit()1683 >>> commit()
1684 >>> LaunchpadZopelessLayer.switchDbUser(config.processmail.dbuser)1684 >>> LaunchpadZopelessLayer.switchDbUser(config.processmail.dbuser)
16851685
16861686
=== modified file 'lib/lp/registry/browser/tests/milestone-views.txt'
--- lib/lp/registry/browser/tests/milestone-views.txt 2010-11-02 20:10:56 +0000
+++ lib/lp/registry/browser/tests/milestone-views.txt 2011-01-20 17:50:31 +0000
@@ -682,7 +682,7 @@
682 >>> bugtask.milestone = milestone682 >>> bugtask.milestone = milestone
683 >>> subscription = milestone.addSubscription(owner, owner)683 >>> subscription = milestone.addSubscription(owner, owner)
684 >>> [subscription for subscription in owner.structural_subscriptions]684 >>> [subscription for subscription in owner.structural_subscriptions]
685 [<StructuralSubscription ...>]685 [<...StructuralSubscription ...>]
686686
687 >>> view = create_initialized_view(milestone, '+delete')687 >>> view = create_initialized_view(milestone, '+delete')
688 >>> [bugtask.milestone.name for bugtask in view.bugtasks]688 >>> [bugtask.milestone.name for bugtask in view.bugtasks]
689689
=== modified file 'lib/lp/registry/doc/milestone.txt'
--- lib/lp/registry/doc/milestone.txt 2010-12-22 00:07:36 +0000
+++ lib/lp/registry/doc/milestone.txt 2011-01-20 17:50:31 +0000
@@ -348,10 +348,10 @@
348 >>> cprov = getUtility(IPersonSet).getByName('cprov')348 >>> cprov = getUtility(IPersonSet).getByName('cprov')
349 >>> ddaa = getUtility(IPersonSet).getByName('ddaa')349 >>> ddaa = getUtility(IPersonSet).getByName('ddaa')
350 >>> milestone_one.addBugSubscription(cprov, cprov)350 >>> milestone_one.addBugSubscription(cprov, cprov)
351 <StructuralSubscription at ...>351 <...StructuralSubscription object at ...>
352352
353 >>> milestone_two.addBugSubscription(ddaa, ddaa)353 >>> milestone_two.addBugSubscription(ddaa, ddaa)
354 <StructuralSubscription at ...>354 <...StructuralSubscription object at ...>
355355
356We change the milestone for the task from 1.0 to 2.0, and fire the356We change the milestone for the task from 1.0 to 2.0, and fire the
357change event.357change event.
358358
=== modified file 'lib/lp/registry/doc/person.txt'
--- lib/lp/registry/doc/person.txt 2010-12-07 00:58:46 +0000
+++ lib/lp/registry/doc/person.txt 2011-01-20 17:50:31 +0000
@@ -1027,11 +1027,11 @@
1027 >>> ubuntu = getUtility(IDistributionSet).getByName('ubuntu')1027 >>> ubuntu = getUtility(IDistributionSet).getByName('ubuntu')
1028 >>> pmount = ubuntu.getSourcePackage('pmount')1028 >>> pmount = ubuntu.getSourcePackage('pmount')
1029 >>> pmount.addBugSubscription(no_priv, no_priv)1029 >>> pmount.addBugSubscription(no_priv, no_priv)
1030 <StructuralSubscription at ...>1030 <...StructuralSubscription object at ...>
10311031
1032 >>> mozilla_firefox = ubuntu.getSourcePackage('mozilla-firefox')1032 >>> mozilla_firefox = ubuntu.getSourcePackage('mozilla-firefox')
1033 >>> mozilla_firefox.addBugSubscription(no_priv, no_priv)1033 >>> mozilla_firefox.addBugSubscription(no_priv, no_priv)
1034 <StructuralSubscription at ...>1034 <...StructuralSubscription object at ...>
10351035
1036 >>> [package.name for package in no_priv.getBugSubscriberPackages()]1036 >>> [package.name for package in no_priv.getBugSubscriberPackages()]
1037 [u'mozilla-firefox', u'pmount']1037 [u'mozilla-firefox', u'pmount']
10381038
=== modified file 'lib/lp/registry/model/person.py'
--- lib/lp/registry/model/person.py 2011-01-18 04:40:41 +0000
+++ lib/lp/registry/model/person.py 2011-01-20 17:50:31 +0000
@@ -2713,8 +2713,10 @@
2713 @property2713 @property
2714 def structural_subscriptions(self):2714 def structural_subscriptions(self):
2715 """See `IPerson`."""2715 """See `IPerson`."""
2716 return StructuralSubscription.selectBy(2716 return IStore(self).find(
2717 subscriber=self, orderBy=['-date_created'])2717 StructuralSubscription,
2718 StructuralSubscription.subscriberID==self.id).order_by(
2719 Desc(StructuralSubscription.date_created))
27182720
2719 def autoSubscribeToMailingList(self, mailinglist, requester=None):2721 def autoSubscribeToMailingList(self, mailinglist, requester=None):
2720 """See `IPerson`."""2722 """See `IPerson`."""
27212723
=== modified file 'lib/lp/registry/model/structuralsubscription.py'
--- lib/lp/registry/model/structuralsubscription.py 2011-01-19 18:12:07 +0000
+++ lib/lp/registry/model/structuralsubscription.py 2011-01-20 17:50:31 +0000
@@ -15,6 +15,7 @@
15 Reference,15 Reference,
16 )16 )
1717
18from storm.base import Storm
18from storm.expr import (19from storm.expr import (
19 Alias,20 Alias,
20 And,21 And,
@@ -39,10 +40,7 @@
3940
40from canonical.database.constants import UTC_NOW41from canonical.database.constants import UTC_NOW
41from canonical.database.enumcol import DBEnum42from canonical.database.enumcol import DBEnum
42from canonical.database.sqlbase import (43from canonical.database.sqlbase import quote
43 quote,
44 SQLBase,
45 )
46from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities44from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
47from canonical.launchpad.interfaces.lpstorm import IStore45from canonical.launchpad.interfaces.lpstorm import IStore
48from lp.bugs.model.bugsubscriptionfilter import BugSubscriptionFilter46from lp.bugs.model.bugsubscriptionfilter import BugSubscriptionFilter
@@ -80,13 +78,11 @@
80from lp.services.propertycache import cachedproperty78from lp.services.propertycache import cachedproperty
8179
8280
83class StructuralSubscription(SQLBase):81class StructuralSubscription(Storm):
84 """A subscription to a Launchpad structure."""82 """A subscription to a Launchpad structure."""
8583
86 implements(IStructuralSubscription)84 implements(IStructuralSubscription)
8785
88 _table = 'StructuralSubscription'
89
90 __storm_table__ = 'StructuralSubscription'86 __storm_table__ = 'StructuralSubscription'
9187
92 id = Int(primary=True)88 id = Int(primary=True)
@@ -135,6 +131,12 @@
135 "date_last_updated", allow_none=False, default=UTC_NOW,131 "date_last_updated", allow_none=False, default=UTC_NOW,
136 tzinfo=pytz.UTC)132 tzinfo=pytz.UTC)
137133
134 def __init__(self, subscriber, subscribed_by, **kwargs):
135 self.subscriber = subscriber
136 self.subscribed_by = subscribed_by
137 for arg, value in kwargs.iteritems():
138 setattr(self, arg, value)
139
138 @property140 @property
139 def target(self):141 def target(self):
140 """See `IStructuralSubscription`."""142 """See `IStructuralSubscription`."""
@@ -427,13 +429,9 @@
427 '%s does not have permission to unsubscribe %s.' % (429 '%s does not have permission to unsubscribe %s.' % (
428 unsubscribed_by.name, subscriber.name))430 unsubscribed_by.name, subscriber.name))
429431
430 subscription_to_remove = None432 subscription_to_remove = self.getSubscriptions(
431 for subscription in self.getSubscriptions(433 min_bug_notification_level=BugNotificationLevel.METADATA,
432 min_bug_notification_level=BugNotificationLevel.METADATA):434 subscriber=subscriber).one()
433 # Only search for bug subscriptions
434 if subscription.subscriber == subscriber:
435 subscription_to_remove = subscription
436 break
437435
438 if subscription_to_remove is None:436 if subscription_to_remove is None:
439 raise DeleteSubscriptionError(437 raise DeleteSubscriptionError(
@@ -447,39 +445,44 @@
447 subscription_to_remove.bug_notification_level = (445 subscription_to_remove.bug_notification_level = (
448 BugNotificationLevel.NOTHING)446 BugNotificationLevel.NOTHING)
449 else:447 else:
450 subscription_to_remove.destroySelf()448 store = Store.of(subscription_to_remove)
449 store.remove(subscription_to_remove)
451450
452 def getSubscription(self, person):451 def getSubscription(self, person):
453 """See `IStructuralSubscriptionTarget`."""452 """See `IStructuralSubscriptionTarget`."""
454 all_subscriptions = self.getSubscriptions()453 # getSubscriptions returns all subscriptions regardless of
455 for subscription in all_subscriptions:454 # the person for person==None, so we special-case that.
456 if subscription.subscriber == person:455 if person is None:
457 return subscription456 return None
458 return None457 all_subscriptions = self.getSubscriptions(subscriber=person)
458 return all_subscriptions.one()
459459
460 def getSubscriptions(self,460 def getSubscriptions(self,
461 min_bug_notification_level=461 min_bug_notification_level=
462 BugNotificationLevel.NOTHING,462 BugNotificationLevel.NOTHING,
463 min_blueprint_notification_level=463 min_blueprint_notification_level=
464 BlueprintNotificationLevel.NOTHING):464 BlueprintNotificationLevel.NOTHING,
465 subscriber=None):
465 """See `IStructuralSubscriptionTarget`."""466 """See `IStructuralSubscriptionTarget`."""
467 from lp.registry.model.person import Person
466 clauses = [468 clauses = [
467 "StructuralSubscription.subscriber = Person.id",469 StructuralSubscription.subscriberID==Person.id,
468 "StructuralSubscription.bug_notification_level "470 (StructuralSubscription.bug_notification_level >=
469 ">= %s" % quote(min_bug_notification_level),471 min_bug_notification_level),
470 "StructuralSubscription.blueprint_notification_level "472 (StructuralSubscription.blueprint_notification_level >=
471 ">= %s" % quote(min_blueprint_notification_level),473 min_blueprint_notification_level),
472 ]474 ]
473 for key, value in self._target_args.iteritems():475 for key, value in self._target_args.iteritems():
474 if value is None:476 clauses.append(
475 clauses.append(477 getattr(StructuralSubscription, key)==value)
476 "StructuralSubscription.%s IS NULL" % (key,))478
477 else:479 if subscriber is not None:
478 clauses.append(480 clauses.append(
479 "StructuralSubscription.%s = %s" % (key, quote(value)))481 StructuralSubscription.subscriberID==subscriber.id)
480 query = " AND ".join(clauses)482
481 return StructuralSubscription.select(483 store = Store.of(self.__helper.pillar)
482 query, orderBy='Person.displayname', clauseTables=['Person'])484 return store.find(
485 StructuralSubscription, *clauses).order_by('Person.displayname')
483486
484 @property487 @property
485 def bug_subscriptions(self):488 def bug_subscriptions(self):
486489
=== modified file 'lib/lp/registry/templates/person-structural-subscriptions.pt'
--- lib/lp/registry/templates/person-structural-subscriptions.pt 2011-01-13 16:30:15 +0000
+++ lib/lp/registry/templates/person-structural-subscriptions.pt 2011-01-20 17:50:31 +0000
@@ -23,7 +23,7 @@
23 <div class="yui-g">23 <div class="yui-g">
24 <div class="portlet" id="structural-subscriptions">24 <div class="portlet" id="structural-subscriptions">
2525
26 <ul tal:condition="structural_subscriptions">26 <ul tal:condition="python:not structural_subscriptions.is_empty()">
27 <li tal:repeat="subscription structural_subscriptions"27 <li tal:repeat="subscription structural_subscriptions"
28 style="margin: 1em 0em; padding: 1em; border: 1px solid #ddd;">28 style="margin: 1em 0em; padding: 1em; border: 1px solid #ddd;">
29 <span tal:replace="structure subscription/target/fmt:link" />29 <span tal:replace="structure subscription/target/fmt:link" />
@@ -74,7 +74,7 @@
74 </li>74 </li>
75 </ul>75 </ul>
7676
77 <p tal:condition="not: structural_subscriptions">77 <p tal:condition="python: structural_subscriptions.is_empty()">
78 <tal:person content="context/fmt:displayname" /> does not78 <tal:person content="context/fmt:displayname" /> does not
79 have any structural subscriptions.79 have any structural subscriptions.
80 </p>80 </p>
8181
=== modified file 'lib/lp/registry/tests/structural-subscription-target.txt'
--- lib/lp/registry/tests/structural-subscription-target.txt 2010-10-17 15:44:08 +0000
+++ lib/lp/registry/tests/structural-subscription-target.txt 2011-01-20 17:50:31 +0000
@@ -11,7 +11,7 @@
11 >>> from canonical.launchpad.ftests import login11 >>> from canonical.launchpad.ftests import login
12 >>> login("foo.bar@canonical.com")12 >>> login("foo.bar@canonical.com")
13 >>> target.addBugSubscription(ubuntu_team, foobar)13 >>> target.addBugSubscription(ubuntu_team, foobar)
14 <StructuralSubscription ...>14 <...StructuralSubscription ...>
1515
16Trying to remove a bug subscription when notification levels for other16Trying to remove a bug subscription when notification levels for other
17applications are set, doesn't remove the subscription. Instead the17applications are set, doesn't remove the subscription. Instead the
@@ -41,7 +41,7 @@
41IStructuralSubscriptionTarget.getSubscription.41IStructuralSubscriptionTarget.getSubscription.
4242
43 >>> target.getSubscription(ubuntu_team)43 >>> target.getSubscription(ubuntu_team)
44 <StructuralSubscription ...>44 <...StructuralSubscription ...>
45 >>> print target.getSubscription(no_priv)45 >>> print target.getSubscription(no_priv)
46 None46 None
4747