Merge lp:~thekorn/launchpad/make_iperson_ihasbugs into lp:launchpad

Proposed by Markus Korn
Status: Merged
Approved by: Gavin Panella
Approved revision: not available
Merge reported by: Gavin Panella
Merged at revision: not available
Proposed branch: lp:~thekorn/launchpad/make_iperson_ihasbugs
Merge into: lp:launchpad
Diff against target: 975 lines (+372/-88)
22 files modified
lib/canonical/launchpad/interfaces/_schema_circular_imports.py (+55/-2)
lib/canonical/launchpad/interfaces/message.py (+4/-6)
lib/lp/bugs/doc/bugtask-search.txt (+12/-1)
lib/lp/bugs/interfaces/bug.py (+5/-6)
lib/lp/bugs/interfaces/bugtarget.py (+20/-18)
lib/lp/bugs/interfaces/bugtask.py (+9/-2)
lib/lp/bugs/interfaces/bugtracker.py (+1/-2)
lib/lp/bugs/interfaces/bugwatch.py (+1/-2)
lib/lp/bugs/model/bugtask.py (+46/-2)
lib/lp/bugs/stories/webservice/xx-bug.txt (+74/-4)
lib/lp/registry/configure.zcml (+2/-0)
lib/lp/registry/interfaces/distribution.py (+0/-7)
lib/lp/registry/interfaces/distributionsourcepackage.py (+4/-3)
lib/lp/registry/interfaces/distroseries.py (+5/-3)
lib/lp/registry/interfaces/milestone.py (+3/-2)
lib/lp/registry/interfaces/person.py (+2/-13)
lib/lp/registry/interfaces/product.py (+0/-7)
lib/lp/registry/interfaces/productseries.py (+3/-2)
lib/lp/registry/interfaces/project.py (+3/-2)
lib/lp/registry/interfaces/sourcepackage.py (+3/-2)
lib/lp/registry/model/person.py (+18/-2)
lib/lp/registry/tests/test_person.py (+102/-0)
To merge this branch: bzr merge lp:~thekorn/launchpad/make_iperson_ihasbugs
Reviewer Review Type Date Requested Status
Gavin Panella (community) Approve
Markus Korn (community) Needs Resubmitting
Eleanor Berger (community) code Approve
Review via email: mp+18541@code.launchpad.net

Commit message

Person.searchTasks() is now exposed in the webservice API. This method queries the tasks related to a user. As a side effect of this implementation the official_bug_tags attribute was moved away from IHasBugs to IHasOfficialBugTags. Fixes bug 282178: "Make IPerson an IHasBugs and make sure calling searchTasks on it works"

To post a comment you must log in.
Revision history for this message
Markus Korn (thekorn) wrote :

This is the first step to fix bug 282178 (making Person.searchTasks available over the webservice API)

This branch includes three different types of changes:
 * implemented Person.searchTasks() in a way that it can be called without BugTaskSearchParams and only by giving keyword arguments. Person.searchTasks() returns the same list of tasks which is shown as "related Bugs" in bugs.launchpad.net/~username.
 * as a person cannot have official_bug_tags this field is moved away from IHasBugs - thanks to Gavin Panella for helping me with the related changes.
 * added testcases for the internal API and the webservice API

The seconds step to fix the bug mentioned above will be to add (alias like) methods to IPerson (searchCommentedTasks(), searchSubscribedTasks() ...)

Revision history for this message
Gavin Panella (allenap) wrote :
Download full text (28.8 KiB)

Hi Markus,

I've added some comments to the diff below. It looks pretty good,
thank you so much for doing this.

Because I worked closely on you on this branch, I'm going to ask
someone else to review it too.

Gavin.

> === modified file 'lib/canonical/launchpad/interfaces/_schema_circular_imports.py'
> --- lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2010-02-01 19:36:23 +0000
> +++ lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2010-02-04 11:12:28 +0000
> @@ -24,11 +24,11 @@
>
> from lp.registry.interfaces.structuralsubscription import (
> IStructuralSubscription, IStructuralSubscriptionTarget)
> -from lp.bugs.interfaces.bug import IBug
> +from lp.bugs.interfaces.bug import IBug, IFrontPageBugAddForm
> from lp.bugs.interfaces.bugbranch import IBugBranch
> from lp.bugs.interfaces.bugnomination import IBugNomination
> from lp.bugs.interfaces.bugtask import IBugTask
> -from lp.bugs.interfaces.bugtarget import IHasBugs
> +from lp.bugs.interfaces.bugtarget import IHasBugs, IBugTarget
> from lp.soyuz.interfaces.build import (
> BuildStatus, IBuild)
> from lp.soyuz.interfaces.buildrecords import IHasBuildRecords
> @@ -69,6 +69,11 @@
> from lp.soyuz.interfaces.queue import (
> IPackageUpload, PackageUploadCustomFormat, PackageUploadStatus)
> from lp.registry.interfaces.sourcepackage import ISourcePackage
> +from canonical.launchpad.interfaces.message import (
> + IIndexedMessage, IMessage, IUserToUserEmail)
> +
> +from lp.bugs.interfaces.bugtracker import IBugTracker
> +from lp.bugs.interfaces.bugwatch import IBugWatch
>
>
> IBranch['bug_branches'].value_type.schema = IBugBranch
> @@ -312,5 +317,53 @@
> patch_reference_property(
> IStructuralSubscriptionTarget, 'parent_subscription_target',
> IStructuralSubscriptionTarget)
> -
> +
> IBuildBase['buildstate'].vocabulary = BuildStatus
> +
> +# IHasBugs
> +patch_plain_parameter_type(
> + IHasBugs, 'searchTasks', 'assignee', IPerson)
> +patch_plain_parameter_type(
> + IHasBugs, 'searchTasks', 'bug_reporter', IPerson)
> +patch_plain_parameter_type(
> + IHasBugs, 'searchTasks', 'bug_supervisor', IPerson)
> +patch_plain_parameter_type(
> + IHasBugs, 'searchTasks', 'bug_commenter', IPerson)
> +patch_plain_parameter_type(
> + IHasBugs, 'searchTasks', 'bug_subscriber', IPerson)
> +patch_plain_parameter_type(
> + IHasBugs, 'searchTasks', 'owner', IPerson)
> +patch_plain_parameter_type(
> + IHasBugs, 'searchTasks', 'affected_user', IPerson)
> +
> +# IBugTask
> +patch_reference_property(IBugTask, 'owner', IPerson)
> +
> +# IBugWatch
> +patch_reference_property(IBugWatch, 'owner', IPerson)
> +
> +# IIndexedMessage
> +patch_reference_property(IIndexedMessage, 'inside', IBugTask)
> +
> +# IMessage
> +patch_reference_property(IMessage, 'owner', IPerson)
> +
> +# IUserToUserEmail
> +patch_reference_property(IUserToUserEmail, 'sender', IPerson)
> +patch_reference_property(IUserToUserEmail, 'recipient', IPerson)
> +
> +# IBug
> +patch_plain_parameter_type(
> + IBug, 'addNomination', 'target', IBugTarget)
> +patch_plain_parameter_type(
> + IBug, 'canBeNominatedFor', 'target', IBugTarget)
> +patch_plain_parameter_type(
> +...

review: Abstain
Revision history for this message
Eleanor Berger (intellectronica) wrote :

Markus (and Gavin), thanks a lot for picking this up. Excellent branch! I have no comments beyond what Gavin already mentioned in his review. I'm leaving the branch in NEEDS FIXING, since you'll need to fix them and then ask one of us to merge it on your behalf, but this shouldn't be much work.

review: Needs Fixing (code)
10114. By Markus Korn

fixed typo

10115. By Markus Korn

started to address some comments of the review

10116. By Markus Korn

merged devel

10117. By Markus Korn

* merged devel
* fixed conflicting base classes of IPersonPublic

10118. By Markus Korn

* Added IllegalRelatedBugTasksParams Exception which translates to a 400
  HTTP error in the webservice API and will be raised in cases of invalid
  queries for related tasks

10119. By Markus Korn

* moved creation of BugTaskSearchParams for Person.searchTasks() to an
  external function.
* added more comments to document the behaviour of this function
* raise IllegalRelatedBugTasksParams exception in case it is impossible to
  execute Person.searchTasks() because of invalid number of parameters

10120. By Markus Korn

* moved doctest for Person.searchTasks() to xx-bug.txt

10121. By Markus Korn

* added unittests for getRelatedBugTasksParams

Revision history for this message
Markus Korn (thekorn) wrote :

Thanks for your review Gavin, I tried to address as many points as possible in the recent commits, most importantly I moved the logic to create search parameters into a separate function, added a few more descriptive comments/docstrings, moved the doctest into xx-bug.txt and added unittests for the new function.

review: Needs Resubmitting
Revision history for this message
Eleanor Berger (intellectronica) wrote :

Very nice. r=me.

Set the commit message and I'll land this on your behalf.

Thanks a lot for working on this!

review: Approve (code)
Revision history for this message
Gavin Panella (allenap) wrote :
Download full text (14.1 KiB)

Hi Markus,

I've gone through each revision in turn since the last review; I
couldn't figure out how to get a cumulative diff without pulling in
all the merges from devel.

There are still a few things left to do to get this ready for landing,
but they're not huge, and I've done some of them (a few more unit
tests). It's really close though.

Gavin.

Revision 10114:
> === modified file 'lib/lp/bugs/doc/bugtask-search.txt'
> --- lib/lp/bugs/doc/bugtask-search.txt 2010-02-02 03:23:24 +0000
> +++ lib/lp/bugs/doc/bugtask-search.txt 2010-02-04 15:33:37 +0000
> @@ -65,7 +65,7 @@
> == Person bugs ==
>
> To get all related tasks to a person call searchTasks() on the person
> -pbject:
> +object:
>
> >>> from canonical.launchpad.interfaces import IPersonSet
> >>> user = getUtility(IPersonSet).getByName('name16')
>

Revision 10115:
> === modified file 'lib/lp/registry/model/person.py'
> --- lib/lp/registry/model/person.py 2010-02-03 14:18:27 +0000
> +++ lib/lp/registry/model/person.py 2010-02-04 16:34:53 +0000
> @@ -31,7 +31,6 @@
> import random
> import re
> import weakref
> -import copy
>
> from zope.lifecycleevent import ObjectCreatedEvent
> from zope.interface import alsoProvides, implementer, implements
> @@ -838,16 +837,20 @@
>
> def searchTasks(self, search_params, *args, **kwargs):
> """See `IHasBugs`."""
> - if search_params is None and not args:
> + if search_params is None and len(args) == 0:
> # this method is called via webapi directly
> - args = list()
> + args = []
> for key in ('assignee', 'bug_subscriber', 'owner', 'bug_commenter'):
> - if kwargs.get(key, None) is None:
> - arguments = copy.copy(kwargs)
> + # all these parameter default to None
> +
> + if kwargs.get(key) is None:
> +
> + arguments = kwargs.copy()
> arguments[key] = self
> if key == 'owner':
> - # Specify both owner and bug_reporter to try to prevent the same
> - # bug (but different tasks) being displayed.
> + # Specify both owner and bug_reporter to try to
> + # prevent the same bug (but different tasks)
> + # being displayed.
> # see `PersonRelatedBugTaskSearchListingView.searchUnbatched`
> arguments['bug_reporter'] = self
> args.append(BugTaskSearchParams.fromSearchForm(**arguments))
>

Revision 10118:
> === modified file 'lib/lp/registry/interfaces/person.py'
> --- lib/lp/registry/interfaces/person.py 2010-02-10 07:36:25 +0000
> +++ lib/lp/registry/interfaces/person.py 2010-02-10 10:51:00 +0000
> @@ -25,6 +25,7 @@
> 'ITeamContactAddressForm',
> 'ITeamCreation',
> 'ITeamReassignment',
> + 'IllegalRelatedBugTasksParams',
> 'ImmutableVisibilityError',
> 'InvalidName',
> 'JoinNotAllowed',
> @@ -2057,6 +2058,12 @@
> webservice_error(409)
>
>
> +class IllegalRelatedBugTasksParams(Exception):
> + """Exception raised when trying to ov...

review: Needs Fixing
10122. By Markus Korn

fixed typo in docstring of IllegalRelatedBugTasksParams

10123. By Markus Korn

* renamed `getRelatedBugTasksParams` into
  `get_related_bugtasks_search_params` and moved this function to
  lp.bugs.model.bugtask
* checking type of context argument of get_related_bugtasks_search_params()
*

10124. By Markus Korn

* moved IllegalRelatedBugTasksParams to lp.bugs.interfaces.bugtask

10125. By Markus Korn

* added webservice API test for IllegalRelatedBugTasksParams

10126. By Markus Korn

* Merged Gavin Panella's changes to the unittests for the related bug task
  search.

10127. By Markus Korn

added more detailed doctests to Person.searchTasks()

Revision history for this message
Markus Korn (thekorn) wrote :

With r10127 I *think* I've addressed all of Gavin's comments

review: Needs Resubmitting
10128. By Markus Korn

removed trailing whitespaces from lib/canonical/launchpad/interfaces/_schema_circular_imports.py

10129. By Markus Korn

removed trailing whitespaces from lib/lp/bugs/doc/bugtask-search.txt

10130. By Markus Korn

removed trailing whitespaces from lib/lp/bugs/interfaces/bugtask.py

10131. By Markus Korn

removed trailing whitespaces from lib/lp/bugs/stories/webservice/xx-bug.txt

10132. By Markus Korn

merged with devel

10133. By Markus Korn

removed trailing whitespaces from lib/lp/registry/interfaces/distributionsourcepackage.py

10134. By Markus Korn

removed trailing whitespaces from lib/lp/registry/interfaces/distroseries.py

Revision history for this message
Markus Korn (thekorn) wrote :

fixed bunch of trailing whitespaces.

Now I get messages from `make lint` for code I did not even touch directly

review: Needs Resubmitting
Revision history for this message
Gavin Panella (allenap) wrote :

As discussed on IRC earlier, you accidentally merged db-devel instead of devel at revision 10132. My branch lp:~allenap/launchpad/make_iperson_ihasbugs fixes this and also removes the remaining trailing white-space from the branch.

Right, I'll get it landed now :)

review: Approve
Revision history for this message
Gavin Panella (allenap) wrote :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/canonical/launchpad/interfaces/_schema_circular_imports.py'
--- lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2010-02-11 00:54:32 +0000
+++ lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2010-02-12 10:32:22 +0000
@@ -24,11 +24,11 @@
2424
25from lp.registry.interfaces.structuralsubscription import (25from lp.registry.interfaces.structuralsubscription import (
26 IStructuralSubscription, IStructuralSubscriptionTarget)26 IStructuralSubscription, IStructuralSubscriptionTarget)
27from lp.bugs.interfaces.bug import IBug27from lp.bugs.interfaces.bug import IBug, IFrontPageBugAddForm
28from lp.bugs.interfaces.bugbranch import IBugBranch28from lp.bugs.interfaces.bugbranch import IBugBranch
29from lp.bugs.interfaces.bugnomination import IBugNomination29from lp.bugs.interfaces.bugnomination import IBugNomination
30from lp.bugs.interfaces.bugtask import IBugTask30from lp.bugs.interfaces.bugtask import IBugTask
31from lp.bugs.interfaces.bugtarget import IHasBugs31from lp.bugs.interfaces.bugtarget import IHasBugs, IBugTarget
32from lp.soyuz.interfaces.build import (32from lp.soyuz.interfaces.build import (
33 BuildStatus, IBuild)33 BuildStatus, IBuild)
34from lp.soyuz.interfaces.buildrecords import IHasBuildRecords34from lp.soyuz.interfaces.buildrecords import IHasBuildRecords
@@ -70,6 +70,11 @@
70from lp.soyuz.interfaces.queue import (70from lp.soyuz.interfaces.queue import (
71 IPackageUpload, PackageUploadCustomFormat, PackageUploadStatus)71 IPackageUpload, PackageUploadCustomFormat, PackageUploadStatus)
72from lp.registry.interfaces.sourcepackage import ISourcePackage72from lp.registry.interfaces.sourcepackage import ISourcePackage
73from canonical.launchpad.interfaces.message import (
74 IIndexedMessage, IMessage, IUserToUserEmail)
75
76from lp.bugs.interfaces.bugtracker import IBugTracker
77from lp.bugs.interfaces.bugwatch import IBugWatch
7378
7479
75IBranch['bug_branches'].value_type.schema = IBugBranch80IBranch['bug_branches'].value_type.schema = IBugBranch
@@ -315,3 +320,51 @@
315 IStructuralSubscriptionTarget)320 IStructuralSubscriptionTarget)
316321
317IBuildBase['buildstate'].vocabulary = BuildStatus322IBuildBase['buildstate'].vocabulary = BuildStatus
323
324# IHasBugs
325patch_plain_parameter_type(
326 IHasBugs, 'searchTasks', 'assignee', IPerson)
327patch_plain_parameter_type(
328 IHasBugs, 'searchTasks', 'bug_reporter', IPerson)
329patch_plain_parameter_type(
330 IHasBugs, 'searchTasks', 'bug_supervisor', IPerson)
331patch_plain_parameter_type(
332 IHasBugs, 'searchTasks', 'bug_commenter', IPerson)
333patch_plain_parameter_type(
334 IHasBugs, 'searchTasks', 'bug_subscriber', IPerson)
335patch_plain_parameter_type(
336 IHasBugs, 'searchTasks', 'owner', IPerson)
337patch_plain_parameter_type(
338 IHasBugs, 'searchTasks', 'affected_user', IPerson)
339
340# IBugTask
341patch_reference_property(IBugTask, 'owner', IPerson)
342
343# IBugWatch
344patch_reference_property(IBugWatch, 'owner', IPerson)
345
346# IIndexedMessage
347patch_reference_property(IIndexedMessage, 'inside', IBugTask)
348
349# IMessage
350patch_reference_property(IMessage, 'owner', IPerson)
351
352# IUserToUserEmail
353patch_reference_property(IUserToUserEmail, 'sender', IPerson)
354patch_reference_property(IUserToUserEmail, 'recipient', IPerson)
355
356# IBug
357patch_plain_parameter_type(
358 IBug, 'addNomination', 'target', IBugTarget)
359patch_plain_parameter_type(
360 IBug, 'canBeNominatedFor', 'target', IBugTarget)
361patch_plain_parameter_type(
362 IBug, 'getNominationFor', 'target', IBugTarget)
363patch_plain_parameter_type(
364 IBug, 'getNominations', 'target', IBugTarget)
365
366# IFrontPageBugAddForm
367patch_reference_property(IFrontPageBugAddForm, 'bugtarget', IBugTarget)
368
369# IBugTracker
370patch_reference_property(IBugTracker, 'owner', IPerson)
318371
=== modified file 'lib/canonical/launchpad/interfaces/message.py'
--- lib/canonical/launchpad/interfaces/message.py 2009-12-01 11:53:59 +0000
+++ lib/canonical/launchpad/interfaces/message.py 2010-02-12 10:32:22 +0000
@@ -27,10 +27,8 @@
2727
28from canonical.launchpad import _28from canonical.launchpad import _
29from canonical.launchpad.interfaces import NotFoundError29from canonical.launchpad.interfaces import NotFoundError
30from lp.bugs.interfaces.bugtask import IBugTask
31from lp.services.job.interfaces.job import IJob30from lp.services.job.interfaces.job import IJob
32from canonical.launchpad.interfaces.librarian import ILibraryFileAlias31from canonical.launchpad.interfaces.librarian import ILibraryFileAlias
33from lp.registry.interfaces.person import IPerson
3432
35from lazr.delegates import delegates33from lazr.delegates import delegates
36from lazr.restful.fields import CollectionField, Reference34from lazr.restful.fields import CollectionField, Reference
@@ -57,7 +55,7 @@
57 # add form used by MessageAddView.55 # add form used by MessageAddView.
58 content = Text(title=_("Message"), required=True, readonly=True)56 content = Text(title=_("Message"), required=True, readonly=True)
59 owner = exported(57 owner = exported(
60 Reference(title=_('Person'), schema=IPerson,58 Reference(title=_('Person'), schema=Interface,
61 required=False, readonly=True))59 required=False, readonly=True))
6260
63 # Schema is really IMessage, but this cannot be declared here. It's61 # Schema is really IMessage, but this cannot be declared here. It's
@@ -181,7 +179,7 @@
181179
182class IIndexedMessage(Interface):180class IIndexedMessage(Interface):
183 """An `IMessage` decorated with its index and context."""181 """An `IMessage` decorated with its index and context."""
184 inside = Reference(title=_('Inside'), schema=IBugTask,182 inside = Reference(title=_('Inside'), schema=Interface,
185 description=_("The bug task which is "183 description=_("The bug task which is "
186 "the context for this message."),184 "the context for this message."),
187 required=True, readonly=True)185 required=True, readonly=True)
@@ -227,12 +225,12 @@
227 """User to user direct email communications."""225 """User to user direct email communications."""
228226
229 sender = Object(227 sender = Object(
230 schema=IPerson,228 schema=Interface,
231 title=_("The message sender"),229 title=_("The message sender"),
232 required=True, readonly=True)230 required=True, readonly=True)
233231
234 recipient = Object(232 recipient = Object(
235 schema=IPerson,233 schema=Interface,
236 title=_("The message recipient"),234 title=_("The message recipient"),
237 required=True, readonly=True)235 required=True, readonly=True)
238236
239237
=== modified file 'lib/lp/bugs/doc/bugtask-search.txt'
--- lib/lp/bugs/doc/bugtask-search.txt 2010-02-02 17:12:29 +0000
+++ lib/lp/bugs/doc/bugtask-search.txt 2010-02-12 10:32:22 +0000
@@ -62,6 +62,17 @@
62 >>> ubuntu_firefox_bugs.count() > 062 >>> ubuntu_firefox_bugs.count() > 0
63 True63 True
6464
65== Person bugs ==
66
67To get all related tasks to a person call searchTasks() on the person
68object:
69
70 >>> from canonical.launchpad.interfaces import IPersonSet
71 >>> user = getUtility(IPersonSet).getByName('name16')
72 >>> user_bugs = user.searchTasks(None, user=None)
73 >>> user_bugs.count() > 0
74 True
75
65== Dupes and Conjoined tasks ==76== Dupes and Conjoined tasks ==
6677
67You can set flags to omit duplicates:78You can set flags to omit duplicates:
@@ -962,7 +973,7 @@
962 >>> print reduce(973 >>> print reduce(
963 ... lambda x, y: x and y,974 ... lambda x, y: x and y,
964 ... [task.bug.isUserAffected(foo_bar)975 ... [task.bug.isUserAffected(foo_bar)
965 ... for task in firefox.searchTasks(search_params)]) 976 ... for task in firefox.searchTasks(search_params)])
966 True977 True
967978
968979
969980
=== modified file 'lib/lp/bugs/interfaces/bug.py'
--- lib/lp/bugs/interfaces/bug.py 2010-01-23 21:42:36 +0000
+++ lib/lp/bugs/interfaces/bug.py 2010-02-12 10:32:22 +0000
@@ -33,7 +33,6 @@
33from canonical.launchpad.fields import (33from canonical.launchpad.fields import (
34 BugField, ContentNameField, DuplicateBug, PublicPersonChoice, Tag, Title)34 BugField, ContentNameField, DuplicateBug, PublicPersonChoice, Tag, Title)
35from lp.bugs.interfaces.bugattachment import IBugAttachment35from lp.bugs.interfaces.bugattachment import IBugAttachment
36from lp.bugs.interfaces.bugtarget import IBugTarget
37from lp.bugs.interfaces.bugtask import (36from lp.bugs.interfaces.bugtask import (
38 BugTaskImportance, BugTaskStatus, IBugTask)37 BugTaskImportance, BugTaskStatus, IBugTask)
39from lp.bugs.interfaces.bugwatch import IBugWatch38from lp.bugs.interfaces.bugwatch import IBugWatch
@@ -587,7 +586,7 @@
587 """Create an INullBugTask and return it for the given parameters."""586 """Create an INullBugTask and return it for the given parameters."""
588587
589 @operation_parameters(588 @operation_parameters(
590 target=Reference(schema=IBugTarget, title=_('Target')))589 target=Reference(schema=Interface, title=_('Target')))
591 @call_with(owner=REQUEST_USER)590 @call_with(owner=REQUEST_USER)
592 @export_factory_operation(Interface, [])591 @export_factory_operation(Interface, [])
593 def addNomination(owner, target):592 def addNomination(owner, target):
@@ -601,7 +600,7 @@
601 """600 """
602601
603 @operation_parameters(602 @operation_parameters(
604 target=Reference(schema=IBugTarget, title=_('Target')))603 target=Reference(schema=Interface, title=_('Target')))
605 @export_read_operation()604 @export_read_operation()
606 def canBeNominatedFor(target):605 def canBeNominatedFor(target):
607 """Can this bug nominated for this target?606 """Can this bug nominated for this target?
@@ -612,7 +611,7 @@
612 """611 """
613612
614 @operation_parameters(613 @operation_parameters(
615 target=Reference(schema=IBugTarget, title=_('Target')))614 target=Reference(schema=Interface, title=_('Target')))
616 @operation_returns_entry(Interface)615 @operation_returns_entry(Interface)
617 @export_read_operation()616 @export_read_operation()
618 def getNominationFor(target):617 def getNominationFor(target):
@@ -625,7 +624,7 @@
625624
626 @operation_parameters(625 @operation_parameters(
627 target=Reference(626 target=Reference(
628 schema=IBugTarget, title=_('Target'), required=False),627 schema=Interface, title=_('Target'), required=False),
629 nominations=List(628 nominations=List(
630 title=_("Nominations to search through."),629 title=_("Nominations to search through."),
631 value_type=Reference(schema=Interface), # IBugNomination630 value_type=Reference(schema=Interface), # IBugNomination
@@ -895,7 +894,7 @@
895 """Create a bug for any bug target."""894 """Create a bug for any bug target."""
896895
897 bugtarget = Reference(896 bugtarget = Reference(
898 schema=IBugTarget, title=_("Where did you find the bug?"),897 schema=Interface, title=_("Where did you find the bug?"),
899 required=True)898 required=True)
900899
901900
902901
=== modified file 'lib/lp/bugs/interfaces/bugtarget.py'
--- lib/lp/bugs/interfaces/bugtarget.py 2009-08-18 11:12:06 +0000
+++ lib/lp/bugs/interfaces/bugtarget.py 2010-02-12 10:32:22 +0000
@@ -25,7 +25,6 @@
25from canonical.launchpad.fields import Tag25from canonical.launchpad.fields import Tag
26from lp.bugs.interfaces.bugtask import (26from lp.bugs.interfaces.bugtask import (
27 BugTagsSearchCombinator, IBugTask, IBugTaskSearch)27 BugTagsSearchCombinator, IBugTask, IBugTaskSearch)
28from lp.registry.interfaces.person import IPerson
29from lazr.enum import DBEnumeratedType28from lazr.enum import DBEnumeratedType
30from lazr.restful.fields import Reference29from lazr.restful.fields import Reference
31from lazr.restful.interface import copy_field30from lazr.restful.interface import copy_field
@@ -56,11 +55,6 @@
56 "A list of unassigned BugTasks for this target.")55 "A list of unassigned BugTasks for this target.")
57 all_bugtasks = Attribute(56 all_bugtasks = Attribute(
58 "A list of all BugTasks ever reported for this target.")57 "A list of all BugTasks ever reported for this target.")
59 official_bug_tags = exported(List(
60 title=_("Official Bug Tags"),
61 description=_("The list of bug tags defined as official."),
62 value_type=Tag(),
63 readonly=True))
6458
65 @call_with(search_params=None, user=REQUEST_USER)59 @call_with(search_params=None, user=REQUEST_USER)
66 @operation_parameters(60 @operation_parameters(
@@ -71,13 +65,13 @@
71 search_text=copy_field(IBugTaskSearch['searchtext']),65 search_text=copy_field(IBugTaskSearch['searchtext']),
72 status=copy_field(IBugTaskSearch['status']),66 status=copy_field(IBugTaskSearch['status']),
73 importance=copy_field(IBugTaskSearch['importance']),67 importance=copy_field(IBugTaskSearch['importance']),
74 assignee=Reference(schema=IPerson),68 assignee=Reference(schema=Interface),
75 bug_reporter=Reference(schema=IPerson),69 bug_reporter=Reference(schema=Interface),
76 bug_supervisor=Reference(schema=IPerson),70 bug_supervisor=Reference(schema=Interface),
77 bug_commenter=Reference(schema=IPerson),71 bug_commenter=Reference(schema=Interface),
78 bug_subscriber=Reference(schema=IPerson),72 bug_subscriber=Reference(schema=Interface),
79 owner=Reference(schema=IPerson),73 owner=Reference(schema=Interface),
80 affected_user=Reference(schema=IPerson),74 affected_user=Reference(schema=Interface),
81 has_patch=copy_field(IBugTaskSearch['has_patch']),75 has_patch=copy_field(IBugTaskSearch['has_patch']),
82 has_cve=copy_field(IBugTaskSearch['has_cve']),76 has_cve=copy_field(IBugTaskSearch['has_cve']),
83 tags=copy_field(IBugTaskSearch['tag']),77 tags=copy_field(IBugTaskSearch['tag']),
@@ -278,13 +272,21 @@
278 self.status = status272 self.status = status
279273
280274
281class IOfficialBugTagTargetPublic(Interface):275class IHasOfficialBugTags(Interface):
276 """An entity that exposes a set of official bug tags."""
277
278 official_bug_tags = exported(List(
279 title=_("Official Bug Tags"),
280 description=_("The list of bug tags defined as official."),
281 value_type=Tag(),
282 readonly=True))
283
284
285class IOfficialBugTagTargetPublic(IHasOfficialBugTags):
282 """Public attributes for `IOfficialBugTagTarget`."""286 """Public attributes for `IOfficialBugTagTarget`."""
283287
284 official_bug_tags = exported(List(288 official_bug_tags = copy_field(
285 title=_("Official Bug Tags"),289 IHasOfficialBugTags['official_bug_tags'], readonly=False)
286 description=_("The list of bug tags defined as official."),
287 value_type=Tag()))
288290
289291
290class IOfficialBugTagTargetRestricted(Interface):292class IOfficialBugTagTargetRestricted(Interface):
291293
=== modified file 'lib/lp/bugs/interfaces/bugtask.py'
--- lib/lp/bugs/interfaces/bugtask.py 2010-02-05 16:00:51 +0000
+++ lib/lp/bugs/interfaces/bugtask.py 2010-02-12 10:32:22 +0000
@@ -26,6 +26,7 @@
26 'IDistroBugTask',26 'IDistroBugTask',
27 'IDistroSeriesBugTask',27 'IDistroSeriesBugTask',
28 'IFrontPageBugTaskSearch',28 'IFrontPageBugTaskSearch',
29 'IllegalRelatedBugTasksParams',
29 'IllegalTarget',30 'IllegalTarget',
30 'INominationsReviewTableBatchNavigator',31 'INominationsReviewTableBatchNavigator',
31 'INullBugTask',32 'INullBugTask',
@@ -60,7 +61,6 @@
60from lp.soyuz.interfaces.component import IComponent61from lp.soyuz.interfaces.component import IComponent
61from canonical.launchpad.interfaces.launchpad import IHasDateCreated, IHasBug62from canonical.launchpad.interfaces.launchpad import IHasDateCreated, IHasBug
62from lp.registry.interfaces.mentoringoffer import ICanBeMentored63from lp.registry.interfaces.mentoringoffer import ICanBeMentored
63from lp.registry.interfaces.person import IPerson
64from canonical.launchpad.searchbuilder import all, any, NULL64from canonical.launchpad.searchbuilder import all, any, NULL
65from canonical.launchpad.validators import LaunchpadValidationError65from canonical.launchpad.validators import LaunchpadValidationError
66from canonical.launchpad.validators.name import name_validator66from canonical.launchpad.validators.name import name_validator
@@ -340,6 +340,13 @@
340 """Exception raised when trying to set an illegal bug task target."""340 """Exception raised when trying to set an illegal bug task target."""
341 webservice_error(400) #Bad request.341 webservice_error(400) #Bad request.
342342
343
344class IllegalRelatedBugTasksParams(Exception):
345 """Exception raised when trying to overwrite all relevant parameters
346 in a search for related bug tasks"""
347 webservice_error(400) #Bad request.
348
349
343class IBugTask(IHasDateCreated, IHasBug, ICanBeMentored):350class IBugTask(IHasDateCreated, IHasBug, ICanBeMentored):
344 """A bug needing fixing in a particular product or package."""351 """A bug needing fixing in a particular product or package."""
345 export_as_webservice_entry()352 export_as_webservice_entry()
@@ -474,7 +481,7 @@
474 description=_("The age of this task in seconds, a delta between "481 description=_("The age of this task in seconds, a delta between "
475 "now and the date the bug task was created."))482 "now and the date the bug task was created."))
476 owner = exported(483 owner = exported(
477 Reference(title=_("The owner"), schema=IPerson, readonly=True))484 Reference(title=_("The owner"), schema=Interface, readonly=True))
478 target = exported(Reference(485 target = exported(Reference(
479 title=_('Target'), required=True, schema=Interface, # IBugTarget486 title=_('Target'), required=True, schema=Interface, # IBugTarget
480 readonly=True,487 readonly=True,
481488
=== modified file 'lib/lp/bugs/interfaces/bugtracker.py'
--- lib/lp/bugs/interfaces/bugtracker.py 2009-12-14 13:51:00 +0000
+++ lib/lp/bugs/interfaces/bugtracker.py 2010-02-12 10:32:22 +0000
@@ -27,7 +27,6 @@
27from canonical.launchpad import _27from canonical.launchpad import _
28from canonical.launchpad.fields import (28from canonical.launchpad.fields import (
29 ContentNameField, StrippedTextLine, URIField)29 ContentNameField, StrippedTextLine, URIField)
30from lp.registry.interfaces.person import IPerson
31from canonical.launchpad.validators import LaunchpadValidationError30from canonical.launchpad.validators import LaunchpadValidationError
32from canonical.launchpad.validators.name import name_validator31from canonical.launchpad.validators.name import name_validator
3332
@@ -216,7 +215,7 @@
216 required=False),215 required=False),
217 exported_as='base_url_aliases')216 exported_as='base_url_aliases')
218 owner = exported(217 owner = exported(
219 Reference(title=_('Owner'), schema=IPerson),218 Reference(title=_('Owner'), schema=Interface),
220 exported_as='registrant')219 exported_as='registrant')
221 contactdetails = exported(220 contactdetails = exported(
222 Text(221 Text(
223222
=== modified file 'lib/lp/bugs/interfaces/bugwatch.py'
--- lib/lp/bugs/interfaces/bugwatch.py 2009-12-22 16:32:42 +0000
+++ lib/lp/bugs/interfaces/bugwatch.py 2010-02-12 10:32:22 +0000
@@ -22,7 +22,6 @@
22from canonical.launchpad import _22from canonical.launchpad import _
23from canonical.launchpad.fields import StrippedTextLine23from canonical.launchpad.fields import StrippedTextLine
24from canonical.launchpad.interfaces.launchpad import IHasBug24from canonical.launchpad.interfaces.launchpad import IHasBug
25from lp.registry.interfaces.person import IPerson
26from lp.bugs.interfaces.bugtracker import IBugTracker25from lp.bugs.interfaces.bugtracker import IBugTracker
2726
28from lazr.restful.declarations import (27from lazr.restful.declarations import (
@@ -134,7 +133,7 @@
134 exported_as='date_created')133 exported_as='date_created')
135 owner = exported(134 owner = exported(
136 Reference(title=_('Owner'), required=True,135 Reference(title=_('Owner'), required=True,
137 readonly=True, schema=IPerson))136 readonly=True, schema=Interface))
138137
139 # Useful joins.138 # Useful joins.
140 bugtasks = exported(139 bugtasks = exported(
141140
=== modified file 'lib/lp/bugs/model/bugtask.py'
--- lib/lp/bugs/model/bugtask.py 2010-02-02 17:12:29 +0000
+++ lib/lp/bugs/model/bugtask.py 2010-02-12 10:32:22 +0000
@@ -16,6 +16,7 @@
16 'NullBugTask',16 'NullBugTask',
17 'bugtask_sort_key',17 'bugtask_sort_key',
18 'get_bug_privacy_filter',18 'get_bug_privacy_filter',
19 'get_related_bugtasks_search_params',
19 'search_value_to_where_condition']20 'search_value_to_where_condition']
2021
2122
@@ -61,7 +62,7 @@
61 INullBugTask, IProductSeriesBugTask, IUpstreamBugTask, IllegalTarget,62 INullBugTask, IProductSeriesBugTask, IUpstreamBugTask, IllegalTarget,
62 RESOLVED_BUGTASK_STATUSES, UNRESOLVED_BUGTASK_STATUSES,63 RESOLVED_BUGTASK_STATUSES, UNRESOLVED_BUGTASK_STATUSES,
63 UserCannotEditBugTaskImportance, UserCannotEditBugTaskMilestone,64 UserCannotEditBugTaskImportance, UserCannotEditBugTaskMilestone,
64 UserCannotEditBugTaskStatus)65 UserCannotEditBugTaskStatus, IllegalRelatedBugTasksParams)
65from lp.bugs.model.bugsubscription import BugSubscription66from lp.bugs.model.bugsubscription import BugSubscription
66from lp.registry.interfaces.distribution import (67from lp.registry.interfaces.distribution import (
67 IDistribution, IDistributionSet)68 IDistribution, IDistributionSet)
@@ -82,7 +83,8 @@
82from canonical.launchpad.searchbuilder import (83from canonical.launchpad.searchbuilder import (
83 all, any, greater_than, NULL, not_equals)84 all, any, greater_than, NULL, not_equals)
84from lp.registry.interfaces.person import (85from lp.registry.interfaces.person import (
85 validate_person_not_private_membership, validate_public_person)86 IPerson, validate_person_not_private_membership,
87 validate_public_person)
86from canonical.launchpad.webapp.interfaces import (88from canonical.launchpad.webapp.interfaces import (
87 IStoreSelector, DEFAULT_FLAVOR, MAIN_STORE, NotFoundError)89 IStoreSelector, DEFAULT_FLAVOR, MAIN_STORE, NotFoundError)
8890
@@ -137,6 +139,48 @@
137 return (139 return (
138 bugtask.bug.id, distribution_name, product_name, productseries_name,140 bugtask.bug.id, distribution_name, product_name, productseries_name,
139 distroseries_name, sourcepackage_name)141 distroseries_name, sourcepackage_name)
142
143def get_related_bugtasks_search_params(user, context, **kwargs):
144 """Returns a list of `BugTaskSearchParams` which can be used to
145 search for all tasks related to a user given by `context`.
146
147 Which tasks are related to a user?
148 * the user has to be either assignee or owner of this tasks
149 OR
150 * the user has to be subscriber or commenter to the underlying bug
151 OR
152 * the user is reporter of the underlying bug, but this condition
153 is automatically fulfilled by the first one as each new bug
154 always get one task owned by the bug reporter
155 """
156 assert IPerson.providedBy(context), "Context argument needs to be IPerson"
157 relevant_fields = ('assignee', 'bug_subscriber', 'owner', 'bug_commenter')
158 search_params = []
159 for key in relevant_fields:
160 # all these parameter default to None
161 user_param = kwargs.get(key)
162 if user_param is None or user_param == context:
163 # we are only creating a `BugTaskSearchParams` object if
164 # the field is None or equal to the context
165 arguments = kwargs.copy()
166 arguments[key] = context
167 if key == 'owner':
168 # Specify both owner and bug_reporter to try to
169 # prevent the same bug (but different tasks)
170 # being displayed.
171 # see `PersonRelatedBugTaskSearchListingView.searchUnbatched`
172 arguments['bug_reporter'] = context
173 search_params.append(
174 BugTaskSearchParams.fromSearchForm(user, **arguments))
175 if len(search_params) == 0:
176 # unable to search for related tasks to user_context because user
177 # modified the query in an invalid way by overwriting all user
178 # related parameters
179 raise IllegalRelatedBugTasksParams(
180 ('Cannot search for related tasks to \'%s\', at least one '
181 'of these parameter has to be empty: %s'
182 %(context.name, ", ".join(relevant_fields))))
183 return search_params
140184
141185
142class BugTaskDelta:186class BugTaskDelta:
143187
=== modified file 'lib/lp/bugs/stories/webservice/xx-bug.txt'
--- lib/lp/bugs/stories/webservice/xx-bug.txt 2010-02-04 21:18:35 +0000
+++ lib/lp/bugs/stories/webservice/xx-bug.txt 2010-02-12 10:32:22 +0000
@@ -676,7 +676,7 @@
676 ...676 ...
677 Location: http://.../bugs/.../nominations/...677 Location: http://.../bugs/.../nominations/...
678 ...678 ...
679 679
680 >>> nominations = webservice.named_get(680 >>> nominations = webservice.named_get(
681 ... '/bugs/%d' % bug.id, 'getNominations').jsonBody()681 ... '/bugs/%d' % bug.id, 'getNominations').jsonBody()
682 >>> pprint_collection(nominations)682 >>> pprint_collection(nominations)
@@ -699,7 +699,7 @@
699John cannot approve or decline the nomination.699John cannot approve or decline the nomination.
700700
701 >>> nom_url = nominations['entries'][0]['self_link']701 >>> nom_url = nominations['entries'][0]['self_link']
702 702
703 >>> print john_webservice.named_get(nom_url, 'canApprove').jsonBody()703 >>> print john_webservice.named_get(nom_url, 'canApprove').jsonBody()
704 False704 False
705705
@@ -715,9 +715,9 @@
715 >>> logout()715 >>> logout()
716716
717Eric, however, can and does decline the nomination.717Eric, however, can and does decline the nomination.
718 718
719 >>> eric_webservice = webservice_for_person(719 >>> eric_webservice = webservice_for_person(
720 ... eric, permission=OAuthPermission.WRITE_PRIVATE) 720 ... eric, permission=OAuthPermission.WRITE_PRIVATE)
721 >>> print eric_webservice.named_post(nom_url, 'decline')721 >>> print eric_webservice.named_post(nom_url, 'decline')
722 HTTP/1.1 200 Ok...722 HTTP/1.1 200 Ok...
723723
@@ -1645,6 +1645,76 @@
1645 http://api.launchpad.dev/beta/ubuntu/+source/linux-source-2.6.15/+bug/101645 http://api.launchpad.dev/beta/ubuntu/+source/linux-source-2.6.15/+bug/10
16461646
16471647
1648User related bug tasks
1649~~~~~~~~~~~~~~~~~~~~~~
1650
1651Calling searchTasks() on a Person object returns a collection of tasks
1652related to this person.
1653
1654First create some sample data
1655
1656 >>> login('foo.bar@canonical.com')
1657 >>> testuser1 = factory.makePerson(name='testuser1')
1658 >>> testuser2 = factory.makePerson(name='testuser2')
1659 >>> testuser3 = factory.makePerson(name='testuser3')
1660 >>> testbug1 = factory.makeBug(owner=testuser1)
1661 >>> testbug2 = factory.makeBug(owner=testuser1)
1662 >>> subscription = testbug2.subscribe(testuser2, testuser2)
1663 >>> logout()
1664
1665There are two tasks related to `testuser1`, the initial tasks of both
1666bugs:
1667
1668 >>> related = webservice.named_get(
1669 ... '/~testuser1', 'searchTasks'
1670 ... ).jsonBody()
1671 >>> pprint_collection(related)
1672 start: 0
1673 total_size: 2
1674 ---
1675 ...
1676 owner_link: u'http://api.launchpad.dev/beta/~testuser1'
1677 ...
1678 ---
1679 ...
1680 owner_link: u'http://api.launchpad.dev/beta/~testuser1'
1681 ...
1682
1683`testuser2` is subscribed to `testbugs2`, so this bug is related to this
1684user:
1685
1686 >>> related = webservice.named_get(
1687 ... '/~testuser2', 'searchTasks'
1688 ... ).jsonBody()
1689 >>> len(related['entries']) == 1
1690 True
1691 >>> int(related['entries'][0]['bug_link'].split('/')[-1]) == testbug2.id
1692 True
1693
1694`testuser3` is not active, so the collection of related tasks to him is
1695empty:
1696
1697 >>> related = webservice.named_get(
1698 ... '/~testuser3', 'searchTasks'
1699 ... ).jsonBody()
1700 >>> pprint_collection(related)
1701 start: None
1702 total_size: 0
1703 ---
1704
1705You are not allowed to overwrite all user related parameter in the same
1706query, because in this case this bug will no be related to the person
1707anymore. In this case a `400 Bad Request`-Error will be returned
1708
1709 >>> name12 = webservice.get("/~name12").jsonBody()
1710 >>> print webservice.named_get(
1711 ... '/~name16', 'searchTasks', assignee=name12['self_link'],
1712 ... owner=name12['self_link'], bug_subscriber=name12['self_link'],
1713 ... bug_commenter=name12['self_link']
1714 ... )
1715 HTTP/1.1 400 Bad Request...
1716
1717
1648Affected users1718Affected users
1649--------------1719--------------
16501720
16511721
=== modified file 'lib/lp/registry/configure.zcml'
--- lib/lp/registry/configure.zcml 2010-02-10 10:37:16 +0000
+++ lib/lp/registry/configure.zcml 2010-02-12 10:32:22 +0000
@@ -905,6 +905,8 @@
905 <allow905 <allow
906 interface="lp.bugs.interfaces.bugtarget.IHasBugs"/>906 interface="lp.bugs.interfaces.bugtarget.IHasBugs"/>
907 <allow907 <allow
908 interface="lp.bugs.interfaces.bugtarget.IHasOfficialBugTags"/>
909 <allow
908 interface="canonical.launchpad.webapp.interfaces.IAuthorization"/>910 interface="canonical.launchpad.webapp.interfaces.IAuthorization"/>
909 <require911 <require
910 permission="launchpad.Edit"912 permission="launchpad.Edit"
911913
=== modified file 'lib/lp/registry/interfaces/distribution.py'
--- lib/lp/registry/interfaces/distribution.py 2010-02-04 21:18:32 +0000
+++ lib/lp/registry/interfaces/distribution.py 2010-02-12 10:32:22 +0000
@@ -532,13 +532,6 @@
532 """An operating system distribution."""532 """An operating system distribution."""
533 export_as_webservice_entry()533 export_as_webservice_entry()
534534
535# Patch the official_bug_tags field to make sure that it's
536# writable from the API, and not readonly like its definition
537# in IHasBugs.
538writable_obt_field = copy_field(IDistribution['official_bug_tags'])
539writable_obt_field.readonly = False
540IDistribution._v_attrs['official_bug_tags'] = writable_obt_field
541
542535
543class IBaseDistribution(IDistribution):536class IBaseDistribution(IDistribution):
544 """A Distribution that is the base for other Distributions."""537 """A Distribution that is the base for other Distributions."""
545538
=== modified file 'lib/lp/registry/interfaces/distributionsourcepackage.py'
--- lib/lp/registry/interfaces/distributionsourcepackage.py 2009-12-05 18:37:28 +0000
+++ lib/lp/registry/interfaces/distributionsourcepackage.py 2010-02-12 10:32:22 +0000
@@ -21,7 +21,7 @@
21 rename_parameters_as)21 rename_parameters_as)
2222
23from canonical.launchpad import _23from canonical.launchpad import _
24from lp.bugs.interfaces.bugtarget import IBugTarget24from lp.bugs.interfaces.bugtarget import IBugTarget, IHasOfficialBugTags
25from lp.bugs.interfaces.bugtask import IBugTask25from lp.bugs.interfaces.bugtask import IBugTask
26from lp.code.interfaces.hasbranches import IHasBranches, IHasMergeProposals26from lp.code.interfaces.hasbranches import IHasBranches, IHasMergeProposals
27from lp.registry.interfaces.distribution import IDistribution27from lp.registry.interfaces.distribution import IDistribution
@@ -31,7 +31,8 @@
3131
3232
33class IDistributionSourcePackage(IBugTarget, IHasBranches, IHasMergeProposals,33class IDistributionSourcePackage(IBugTarget, IHasBranches, IHasMergeProposals,
34 IStructuralSubscriptionTarget):34 IStructuralSubscriptionTarget,
35 IHasOfficialBugTags):
35 """Represents a source package in a distribution.36 """Represents a source package in a distribution.
3637
37 Create IDistributionSourcePackages by invoking38 Create IDistributionSourcePackages by invoking
@@ -101,7 +102,7 @@
101 """102 """
102103
103 def get_distroseries_packages(active_only=True):104 def get_distroseries_packages(active_only=True):
104 """Return a list of DistroSeriesSourcePackage objects, each 105 """Return a list of DistroSeriesSourcePackage objects, each
105 representing this same source package in the series of this106 representing this same source package in the series of this
106 distribution.107 distribution.
107108
108109
=== modified file 'lib/lp/registry/interfaces/distroseries.py'
--- lib/lp/registry/interfaces/distroseries.py 2010-01-21 17:49:38 +0000
+++ lib/lp/registry/interfaces/distroseries.py 2010-02-12 10:32:22 +0000
@@ -28,7 +28,8 @@
28from lp.registry.interfaces.series import SeriesStatus28from lp.registry.interfaces.series import SeriesStatus
29from lp.registry.interfaces.structuralsubscription import (29from lp.registry.interfaces.structuralsubscription import (
30 IStructuralSubscriptionTarget)30 IStructuralSubscriptionTarget)
31from lp.bugs.interfaces.bugtarget import IBugTarget, IHasBugs31from lp.bugs.interfaces.bugtarget import (
32 IBugTarget, IHasBugs, IHasOfficialBugTags)
32from lp.soyuz.interfaces.buildrecords import IHasBuildRecords33from lp.soyuz.interfaces.buildrecords import IHasBuildRecords
33from lp.translations.interfaces.languagepack import ILanguagePack34from lp.translations.interfaces.languagepack import ILanguagePack
34from canonical.launchpad.interfaces.launchpad import (35from canonical.launchpad.interfaces.launchpad import (
@@ -154,7 +155,8 @@
154155
155class IDistroSeriesPublic(IHasAppointedDriver, IHasDrivers, IHasOwner,156class IDistroSeriesPublic(IHasAppointedDriver, IHasDrivers, IHasOwner,
156 IBugTarget, ISpecificationGoal, IHasMilestones,157 IBugTarget, ISpecificationGoal, IHasMilestones,
157 IHasBuildRecords, ISeriesMixin):158 IHasBuildRecords, ISeriesMixin,
159 IHasOfficialBugTags):
158 """Public IDistroSeries properties."""160 """Public IDistroSeries properties."""
159161
160 id = Attribute("The distroseries's unique number.")162 id = Attribute("The distroseries's unique number.")
@@ -389,7 +391,7 @@
389 def getDistroArchSeriesByProcessor(processor):391 def getDistroArchSeriesByProcessor(processor):
390 """Return the distroarchseries for this distroseries with the392 """Return the distroarchseries for this distroseries with the
391 given architecturetag from a `IProcessor`.393 given architecturetag from a `IProcessor`.
392 394
393 :param processor: An `IProcessor`395 :param processor: An `IProcessor`
394 :return: An `IDistroArchSeries` or None when none was found.396 :return: An `IDistroArchSeries` or None when none was found.
395 """397 """
396398
=== modified file 'lib/lp/registry/interfaces/milestone.py'
--- lib/lp/registry/interfaces/milestone.py 2010-01-27 19:12:10 +0000
+++ lib/lp/registry/interfaces/milestone.py 2010-02-12 10:32:22 +0000
@@ -21,7 +21,7 @@
21from lp.registry.interfaces.structuralsubscription import (21from lp.registry.interfaces.structuralsubscription import (
22 IStructuralSubscriptionTarget)22 IStructuralSubscriptionTarget)
23from lp.registry.interfaces.productrelease import IProductRelease23from lp.registry.interfaces.productrelease import IProductRelease
24from lp.bugs.interfaces.bugtarget import IHasBugs24from lp.bugs.interfaces.bugtarget import IHasBugs, IHasOfficialBugTags
25from lp.bugs.interfaces.bugtask import IBugTask25from lp.bugs.interfaces.bugtask import IBugTask
26from canonical.launchpad import _26from canonical.launchpad import _
27from canonical.launchpad.fields import (27from canonical.launchpad.fields import (
@@ -70,7 +70,8 @@
70 return milestone70 return milestone
7171
7272
73class IMilestone(IHasBugs, IStructuralSubscriptionTarget):73class IMilestone(IHasBugs, IStructuralSubscriptionTarget,
74 IHasOfficialBugTags):
74 """A milestone, or a targeting point for bugs and other75 """A milestone, or a targeting point for bugs and other
75 release-management items that need coordination.76 release-management items that need coordination.
76 """77 """
7778
=== modified file 'lib/lp/registry/interfaces/person.py'
--- lib/lp/registry/interfaces/person.py 2010-02-08 14:37:50 +0000
+++ lib/lp/registry/interfaces/person.py 2010-02-12 10:32:22 +0000
@@ -97,6 +97,7 @@
97from canonical.launchpad.webapp.interfaces import NameLookupFailed97from canonical.launchpad.webapp.interfaces import NameLookupFailed
98from canonical.launchpad.webapp.authorization import check_permission98from canonical.launchpad.webapp.authorization import check_permission
9999
100from lp.bugs.interfaces.bugtarget import IHasBugs
100101
101PRIVATE_TEAM_PREFIX = 'private-'102PRIVATE_TEAM_PREFIX = 'private-'
102103
@@ -478,7 +479,7 @@
478class IPersonPublic(IHasBranches, IHasSpecifications, IHasMentoringOffers,479class IPersonPublic(IHasBranches, IHasSpecifications, IHasMentoringOffers,
479 IHasMergeProposals, IHasLogo, IHasMugshot, IHasIcon,480 IHasMergeProposals, IHasLogo, IHasMugshot, IHasIcon,
480 IHasLocation, IHasRequestedReviews, IObjectWithLocation,481 IHasLocation, IHasRequestedReviews, IObjectWithLocation,
481 IPrivacy):482 IPrivacy, IHasBugs):
482 """Public attributes for a Person."""483 """Public attributes for a Person."""
483484
484 id = Int(title=_('ID'), required=True, readonly=True)485 id = Int(title=_('ID'), required=True, readonly=True)
@@ -1017,18 +1018,6 @@
1017 used between TeamMembership and Person objects.1018 used between TeamMembership and Person objects.
1018 """1019 """
10191020
1020 def searchTasks(search_params, *args):
1021 """Search IBugTasks with the given search parameters.
1022
1023 :search_params: a BugTaskSearchParams object
1024 :args: any number of BugTaskSearchParams objects
1025
1026 If more than one BugTaskSearchParams is given, return the union of
1027 IBugTasks which match any of them.
1028
1029 Return an iterable of matching results.
1030 """
1031
1032 def getLatestMaintainedPackages():1021 def getLatestMaintainedPackages():
1033 """Return `SourcePackageRelease`s maintained by this person.1022 """Return `SourcePackageRelease`s maintained by this person.
10341023
10351024
=== modified file 'lib/lp/registry/interfaces/product.py'
--- lib/lp/registry/interfaces/product.py 2010-01-20 13:58:45 +0000
+++ lib/lp/registry/interfaces/product.py 2010-02-12 10:32:22 +0000
@@ -724,13 +724,6 @@
724IProject['products'].value_type = Reference(IProduct)724IProject['products'].value_type = Reference(IProduct)
725IProductRelease['product'].schema = IProduct725IProductRelease['product'].schema = IProduct
726726
727# Patch the official_bug_tags field to make sure that it's
728# writable from the API, and not readonly like its definition
729# in IHasBugs.
730writable_obt_field = copy_field(IProduct['official_bug_tags'])
731writable_obt_field.readonly = False
732IProduct._v_attrs['official_bug_tags'] = writable_obt_field
733
734727
735class IProductSet(Interface):728class IProductSet(Interface):
736 export_as_webservice_collection(IProduct)729 export_as_webservice_collection(IProduct)
737730
=== modified file 'lib/lp/registry/interfaces/productseries.py'
--- lib/lp/registry/interfaces/productseries.py 2009-12-13 11:55:40 +0000
+++ lib/lp/registry/interfaces/productseries.py 2010-02-12 10:32:22 +0000
@@ -24,7 +24,7 @@
24from lp.registry.interfaces.structuralsubscription import (24from lp.registry.interfaces.structuralsubscription import (
25 IStructuralSubscriptionTarget)25 IStructuralSubscriptionTarget)
26from lp.code.interfaces.branch import IBranch26from lp.code.interfaces.branch import IBranch
27from lp.bugs.interfaces.bugtarget import IBugTarget27from lp.bugs.interfaces.bugtarget import IBugTarget, IHasOfficialBugTags
28from lp.registry.interfaces.series import SeriesStatus28from lp.registry.interfaces.series import SeriesStatus
29from canonical.launchpad.interfaces.launchpad import (29from canonical.launchpad.interfaces.launchpad import (
30 IHasAppointedDriver, IHasDrivers)30 IHasAppointedDriver, IHasDrivers)
@@ -93,7 +93,8 @@
9393
9494
95class IProductSeriesPublic(IHasAppointedDriver, IHasDrivers, IHasOwner,95class IProductSeriesPublic(IHasAppointedDriver, IHasDrivers, IHasOwner,
96 IBugTarget, ISpecificationGoal, IHasMilestones):96 IBugTarget, ISpecificationGoal, IHasMilestones,
97 IHasOfficialBugTags):
97 """Public IProductSeries properties."""98 """Public IProductSeries properties."""
98 # XXX Mark Shuttleworth 2004-10-14: Would like to get rid of id in99 # XXX Mark Shuttleworth 2004-10-14: Would like to get rid of id in
99 # interfaces, as soon as SQLobject allows using the object directly100 # interfaces, as soon as SQLobject allows using the object directly
100101
=== modified file 'lib/lp/registry/interfaces/project.py'
--- lib/lp/registry/interfaces/project.py 2009-12-05 18:37:28 +0000
+++ lib/lp/registry/interfaces/project.py 2010-02-12 10:32:22 +0000
@@ -24,7 +24,7 @@
24from lp.code.interfaces.branchvisibilitypolicy import (24from lp.code.interfaces.branchvisibilitypolicy import (
25 IHasBranchVisibilityPolicy)25 IHasBranchVisibilityPolicy)
26from lp.code.interfaces.hasbranches import IHasBranches, IHasMergeProposals26from lp.code.interfaces.hasbranches import IHasBranches, IHasMergeProposals
27from lp.bugs.interfaces.bugtarget import IHasBugs27from lp.bugs.interfaces.bugtarget import IHasBugs, IHasOfficialBugTags
28from lp.registry.interfaces.karma import IKarmaContext28from lp.registry.interfaces.karma import IKarmaContext
29from canonical.launchpad.interfaces.launchpad import (29from canonical.launchpad.interfaces.launchpad import (
30 IHasAppointedDriver, IHasDrivers, IHasIcon, IHasLogo, IHasMugshot)30 IHasAppointedDriver, IHasDrivers, IHasIcon, IHasLogo, IHasMugshot)
@@ -64,7 +64,8 @@
64 IHasDrivers, IHasBranchVisibilityPolicy, IHasIcon, IHasLogo,64 IHasDrivers, IHasBranchVisibilityPolicy, IHasIcon, IHasLogo,
65 IHasMentoringOffers, IHasMergeProposals, IHasMilestones, IHasMugshot,65 IHasMentoringOffers, IHasMergeProposals, IHasMilestones, IHasMugshot,
66 IHasOwner, IHasSpecifications, IHasSprints, IHasTranslationGroup,66 IHasOwner, IHasSpecifications, IHasSprints, IHasTranslationGroup,
67 IMakesAnnouncements, IKarmaContext, IPillar, IRootContext):67 IMakesAnnouncements, IKarmaContext, IPillar, IRootContext,
68 IHasOfficialBugTags):
68 """Public IProject properties."""69 """Public IProject properties."""
6970
70 id = Int(title=_('ID'), readonly=True)71 id = Int(title=_('ID'), readonly=True)
7172
=== modified file 'lib/lp/registry/interfaces/sourcepackage.py'
--- lib/lp/registry/interfaces/sourcepackage.py 2010-02-04 21:18:32 +0000
+++ lib/lp/registry/interfaces/sourcepackage.py 2010-02-12 10:32:22 +0000
@@ -21,7 +21,7 @@
21from lazr.enum import DBEnumeratedType, DBItem21from lazr.enum import DBEnumeratedType, DBItem
2222
23from canonical.launchpad import _23from canonical.launchpad import _
24from lp.bugs.interfaces.bugtarget import IBugTarget24from lp.bugs.interfaces.bugtarget import IBugTarget, IHasOfficialBugTags
25from lp.code.interfaces.hasbranches import IHasBranches, IHasMergeProposals25from lp.code.interfaces.hasbranches import IHasBranches, IHasMergeProposals
26from lp.soyuz.interfaces.component import IComponent26from lp.soyuz.interfaces.component import IComponent
27from lazr.restful.fields import Reference, ReferenceChoice27from lazr.restful.fields import Reference, ReferenceChoice
@@ -31,7 +31,8 @@
31 operation_returns_entry, REQUEST_USER)31 operation_returns_entry, REQUEST_USER)
3232
3333
34class ISourcePackage(IBugTarget, IHasBranches, IHasMergeProposals):34class ISourcePackage(IBugTarget, IHasBranches, IHasMergeProposals,
35 IHasOfficialBugTags):
35 """A SourcePackage. See the MagicSourcePackage specification. This36 """A SourcePackage. See the MagicSourcePackage specification. This
36 interface preserves as much as possible of the old SourcePackage37 interface preserves as much as possible of the old SourcePackage
37 interface from the SourcePackage table, with the new table-less38 interface from the SourcePackage table, with the new table-less
3839
=== modified file 'lib/lp/registry/model/person.py'
--- lib/lp/registry/model/person.py 2010-02-10 23:14:56 +0000
+++ lib/lp/registry/model/person.py 2010-02-12 10:32:22 +0000
@@ -84,7 +84,7 @@
84from lp.code.model.hasbranches import (84from lp.code.model.hasbranches import (
85 HasBranchesMixin, HasMergeProposalsMixin, HasRequestedReviewsMixin)85 HasBranchesMixin, HasMergeProposalsMixin, HasRequestedReviewsMixin)
86from lp.bugs.interfaces.bugtask import (86from lp.bugs.interfaces.bugtask import (
87 BugTaskSearchParams, IBugTaskSet)87 BugTaskSearchParams, IBugTaskSet, IllegalRelatedBugTasksParams)
88from lp.bugs.interfaces.bugtarget import IBugTarget88from lp.bugs.interfaces.bugtarget import IBugTarget
89from lp.registry.interfaces.codeofconduct import (89from lp.registry.interfaces.codeofconduct import (
90 ISignedCodeOfConductSet)90 ISignedCodeOfConductSet)
@@ -128,7 +128,8 @@
128128
129from lp.soyuz.model.archive import Archive129from lp.soyuz.model.archive import Archive
130from lp.registry.model.codeofconduct import SignedCodeOfConduct130from lp.registry.model.codeofconduct import SignedCodeOfConduct
131from lp.bugs.model.bugtask import BugTask131from lp.bugs.model.bugtask import (
132 BugTask, get_related_bugtasks_search_params)
132from canonical.launchpad.database.emailaddress import (133from canonical.launchpad.database.emailaddress import (
133 EmailAddress, HasOwnerMixin)134 EmailAddress, HasOwnerMixin)
134from lp.registry.model.karma import KarmaCache, KarmaTotalCache135from lp.registry.model.karma import KarmaCache, KarmaTotalCache
@@ -838,6 +839,21 @@
838839
839 def searchTasks(self, search_params, *args, **kwargs):840 def searchTasks(self, search_params, *args, **kwargs):
840 """See `IHasBugs`."""841 """See `IHasBugs`."""
842 if search_params is None and len(args) == 0:
843 # this method is called via webapi directly
844 # calling this method on a Person object directly via the
845 # webservice API means searching for user related tasks
846 user = kwargs.pop('user')
847 try:
848 search_params = get_related_bugtasks_search_params(
849 user, self, **kwargs)
850 except IllegalRelatedBugTasksParams, e:
851 # dirty hack, marking an exception with a HTTP error
852 # only works if the exception is raised in the exported
853 # method, see docstring of
854 # `lazr.restful.declarations.webservice_error()`
855 raise e
856 return getUtility(IBugTaskSet).search(*search_params)
841 if len(kwargs) > 0:857 if len(kwargs) > 0:
842 # if keyword arguments are supplied, use the deault858 # if keyword arguments are supplied, use the deault
843 # implementation in HasBugsBase.859 # implementation in HasBugsBase.
844860
=== modified file 'lib/lp/registry/tests/test_person.py'
--- lib/lp/registry/tests/test_person.py 2009-12-08 17:53:37 +0000
+++ lib/lp/registry/tests/test_person.py 2010-02-12 10:32:22 +0000
@@ -29,6 +29,8 @@
29from lp.registry.model.structuralsubscription import (29from lp.registry.model.structuralsubscription import (
30 StructuralSubscription)30 StructuralSubscription)
31from lp.registry.model.person import Person31from lp.registry.model.person import Person
32from lp.bugs.model.bugtask import get_related_bugtasks_search_params
33from lp.bugs.interfaces.bugtask import IllegalRelatedBugTasksParams
32from lp.answers.model.answercontact import AnswerContact34from lp.answers.model.answercontact import AnswerContact
33from lp.blueprints.model.specification import Specification35from lp.blueprints.model.specification import Specification
34from lp.testing import TestCaseWithFactory36from lp.testing import TestCaseWithFactory
@@ -495,5 +497,105 @@
495 name='/john')497 name='/john')
496498
497499
500class TestPersonRelatedBugTaskSearch(TestCaseWithFactory):
501
502 layer = LaunchpadFunctionalLayer
503
504 def setUp(self):
505 super(TestPersonRelatedBugTaskSearch, self).setUp()
506 self.user = self.factory.makePerson(displayname="User")
507 self.context = self.factory.makePerson(displayname="Context")
508
509 def checkUserFields(
510 self, params, assignee=None, bug_subscriber=None,
511 owner=None, bug_commenter=None, bug_reporter=None):
512 self.failUnlessEqual(assignee, params.assignee)
513 # fromSearchForm() takes a bug_subscriber parameter, but saves
514 # it as subscriber on the parameter object.
515 self.failUnlessEqual(bug_subscriber, params.subscriber)
516 self.failUnlessEqual(owner, params.owner)
517 self.failUnlessEqual(bug_commenter, params.bug_commenter)
518 self.failUnlessEqual(bug_reporter, params.bug_reporter)
519
520 def test_get_related_bugtasks_search_params(self):
521 # With no specified options, get_related_bugtasks_search_params()
522 # returns 4 BugTaskSearchParams objects, each with a different
523 # user field set.
524 search_params = get_related_bugtasks_search_params(self.user, self.context)
525 self.assertEqual(len(search_params), 4)
526 self.checkUserFields(
527 search_params[0], assignee=self.context)
528 self.checkUserFields(
529 search_params[1], bug_subscriber=self.context)
530 self.checkUserFields(
531 search_params[2], owner=self.context, bug_reporter=self.context)
532 self.checkUserFields(
533 search_params[3], bug_commenter=self.context)
534
535 def test_get_related_bugtasks_search_params_with_assignee(self):
536 # With assignee specified, get_related_bugtasks_search_params() returns
537 # 3 BugTaskSearchParams objects.
538 search_params = get_related_bugtasks_search_params(
539 self.user, self.context, assignee=self.user)
540 self.assertEqual(len(search_params), 3)
541 self.checkUserFields(
542 search_params[0], assignee=self.user, bug_subscriber=self.context)
543 self.checkUserFields(
544 search_params[1], assignee=self.user, owner=self.context,
545 bug_reporter=self.context)
546 self.checkUserFields(
547 search_params[2], assignee=self.user, bug_commenter=self.context)
548
549 def test_get_related_bugtasks_search_params_with_owner(self):
550 # With owner specified, get_related_bugtasks_search_params() returns
551 # 3 BugTaskSearchParams objects.
552 search_params = get_related_bugtasks_search_params(
553 self.user, self.context, owner=self.user)
554 self.assertEqual(len(search_params), 3)
555 self.checkUserFields(
556 search_params[0], owner=self.user, assignee=self.context)
557 self.checkUserFields(
558 search_params[1], owner=self.user, bug_subscriber=self.context)
559 self.checkUserFields(
560 search_params[2], owner=self.user, bug_commenter=self.context)
561
562 def test_get_related_bugtasks_search_params_with_bug_reporter(self):
563 # With bug reporter specified, get_related_bugtasks_search_params()
564 # returns 4 BugTaskSearchParams objects, but the bug reporter
565 # is overwritten in one instance.
566 search_params = get_related_bugtasks_search_params(
567 self.user, self.context, bug_reporter=self.user)
568 self.assertEqual(len(search_params), 4)
569 self.checkUserFields(
570 search_params[0], bug_reporter=self.user,
571 assignee=self.context)
572 self.checkUserFields(
573 search_params[1], bug_reporter=self.user,
574 bug_subscriber=self.context)
575 # When a BugTaskSearchParams is prepared with the owner filled
576 # in, the bug reporter is overwritten to match.
577 self.checkUserFields(
578 search_params[2], bug_reporter=self.context,
579 owner=self.context)
580 self.checkUserFields(
581 search_params[3], bug_reporter=self.user,
582 bug_commenter=self.context)
583
584 def test_get_related_bugtasks_search_params_illegal(self):
585 self.assertRaises(
586 IllegalRelatedBugTasksParams,
587 get_related_bugtasks_search_params, self.user, self.context,
588 assignee=self.user, owner=self.user, bug_commenter=self.user,
589 bug_subscriber=self.user)
590
591 def test_get_related_bugtasks_search_params_illegal_context(self):
592 # in case the `context` argument is not of type IPerson an
593 # AssertionError is raised
594 self.assertRaises(
595 AssertionError,
596 get_related_bugtasks_search_params, self.user, "Username",
597 assignee=self.user)
598
599
498def test_suite():600def test_suite():
499 return unittest.TestLoader().loadTestsFromName(__name__)601 return unittest.TestLoader().loadTestsFromName(__name__)