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

Proposed by Markus Korn on 2010-02-03
Status: Merged
Approved by: Gavin Panella on 2010-02-12
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) 2010-02-03 Approve on 2010-02-12
Markus Korn (community) Resubmit on 2010-02-12
Eleanor Berger (community) code 2010-02-04 Approve on 2010-02-11
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.
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() ...)

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
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 on 2010-02-04

fixed typo

10115. By Markus Korn on 2010-02-04

started to address some comments of the review

10116. By Markus Korn on 2010-02-08

merged devel

10117. By Markus Korn on 2010-02-10

* merged devel
* fixed conflicting base classes of IPersonPublic

10118. By Markus Korn on 2010-02-10

* 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 on 2010-02-10

* 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 on 2010-02-10

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

10121. By Markus Korn on 2010-02-10

* added unittests for getRelatedBugTasksParams

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: Resubmit
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)
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 on 2010-02-11

fixed typo in docstring of IllegalRelatedBugTasksParams

10123. By Markus Korn on 2010-02-11

* 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 on 2010-02-11

* moved IllegalRelatedBugTasksParams to lp.bugs.interfaces.bugtask

10125. By Markus Korn on 2010-02-11

* added webservice API test for IllegalRelatedBugTasksParams

10126. By Markus Korn on 2010-02-11

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

10127. By Markus Korn on 2010-02-12

added more detailed doctests to Person.searchTasks()

Markus Korn (thekorn) wrote :

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

review: Resubmit
10128. By Markus Korn on 2010-02-12

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

10129. By Markus Korn on 2010-02-12

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

10130. By Markus Korn on 2010-02-12

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

10131. By Markus Korn on 2010-02-12

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

10132. By Markus Korn on 2010-02-12

merged with devel

10133. By Markus Korn on 2010-02-12

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

10134. By Markus Korn on 2010-02-12

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

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: Resubmit
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

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/canonical/launchpad/interfaces/_schema_circular_imports.py'
2--- lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2010-02-11 00:54:32 +0000
3+++ lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2010-02-12 10:32:22 +0000
4@@ -24,11 +24,11 @@
5
6 from lp.registry.interfaces.structuralsubscription import (
7 IStructuralSubscription, IStructuralSubscriptionTarget)
8-from lp.bugs.interfaces.bug import IBug
9+from lp.bugs.interfaces.bug import IBug, IFrontPageBugAddForm
10 from lp.bugs.interfaces.bugbranch import IBugBranch
11 from lp.bugs.interfaces.bugnomination import IBugNomination
12 from lp.bugs.interfaces.bugtask import IBugTask
13-from lp.bugs.interfaces.bugtarget import IHasBugs
14+from lp.bugs.interfaces.bugtarget import IHasBugs, IBugTarget
15 from lp.soyuz.interfaces.build import (
16 BuildStatus, IBuild)
17 from lp.soyuz.interfaces.buildrecords import IHasBuildRecords
18@@ -70,6 +70,11 @@
19 from lp.soyuz.interfaces.queue import (
20 IPackageUpload, PackageUploadCustomFormat, PackageUploadStatus)
21 from lp.registry.interfaces.sourcepackage import ISourcePackage
22+from canonical.launchpad.interfaces.message import (
23+ IIndexedMessage, IMessage, IUserToUserEmail)
24+
25+from lp.bugs.interfaces.bugtracker import IBugTracker
26+from lp.bugs.interfaces.bugwatch import IBugWatch
27
28
29 IBranch['bug_branches'].value_type.schema = IBugBranch
30@@ -315,3 +320,51 @@
31 IStructuralSubscriptionTarget)
32
33 IBuildBase['buildstate'].vocabulary = BuildStatus
34+
35+# IHasBugs
36+patch_plain_parameter_type(
37+ IHasBugs, 'searchTasks', 'assignee', IPerson)
38+patch_plain_parameter_type(
39+ IHasBugs, 'searchTasks', 'bug_reporter', IPerson)
40+patch_plain_parameter_type(
41+ IHasBugs, 'searchTasks', 'bug_supervisor', IPerson)
42+patch_plain_parameter_type(
43+ IHasBugs, 'searchTasks', 'bug_commenter', IPerson)
44+patch_plain_parameter_type(
45+ IHasBugs, 'searchTasks', 'bug_subscriber', IPerson)
46+patch_plain_parameter_type(
47+ IHasBugs, 'searchTasks', 'owner', IPerson)
48+patch_plain_parameter_type(
49+ IHasBugs, 'searchTasks', 'affected_user', IPerson)
50+
51+# IBugTask
52+patch_reference_property(IBugTask, 'owner', IPerson)
53+
54+# IBugWatch
55+patch_reference_property(IBugWatch, 'owner', IPerson)
56+
57+# IIndexedMessage
58+patch_reference_property(IIndexedMessage, 'inside', IBugTask)
59+
60+# IMessage
61+patch_reference_property(IMessage, 'owner', IPerson)
62+
63+# IUserToUserEmail
64+patch_reference_property(IUserToUserEmail, 'sender', IPerson)
65+patch_reference_property(IUserToUserEmail, 'recipient', IPerson)
66+
67+# IBug
68+patch_plain_parameter_type(
69+ IBug, 'addNomination', 'target', IBugTarget)
70+patch_plain_parameter_type(
71+ IBug, 'canBeNominatedFor', 'target', IBugTarget)
72+patch_plain_parameter_type(
73+ IBug, 'getNominationFor', 'target', IBugTarget)
74+patch_plain_parameter_type(
75+ IBug, 'getNominations', 'target', IBugTarget)
76+
77+# IFrontPageBugAddForm
78+patch_reference_property(IFrontPageBugAddForm, 'bugtarget', IBugTarget)
79+
80+# IBugTracker
81+patch_reference_property(IBugTracker, 'owner', IPerson)
82
83=== modified file 'lib/canonical/launchpad/interfaces/message.py'
84--- lib/canonical/launchpad/interfaces/message.py 2009-12-01 11:53:59 +0000
85+++ lib/canonical/launchpad/interfaces/message.py 2010-02-12 10:32:22 +0000
86@@ -27,10 +27,8 @@
87
88 from canonical.launchpad import _
89 from canonical.launchpad.interfaces import NotFoundError
90-from lp.bugs.interfaces.bugtask import IBugTask
91 from lp.services.job.interfaces.job import IJob
92 from canonical.launchpad.interfaces.librarian import ILibraryFileAlias
93-from lp.registry.interfaces.person import IPerson
94
95 from lazr.delegates import delegates
96 from lazr.restful.fields import CollectionField, Reference
97@@ -57,7 +55,7 @@
98 # add form used by MessageAddView.
99 content = Text(title=_("Message"), required=True, readonly=True)
100 owner = exported(
101- Reference(title=_('Person'), schema=IPerson,
102+ Reference(title=_('Person'), schema=Interface,
103 required=False, readonly=True))
104
105 # Schema is really IMessage, but this cannot be declared here. It's
106@@ -181,7 +179,7 @@
107
108 class IIndexedMessage(Interface):
109 """An `IMessage` decorated with its index and context."""
110- inside = Reference(title=_('Inside'), schema=IBugTask,
111+ inside = Reference(title=_('Inside'), schema=Interface,
112 description=_("The bug task which is "
113 "the context for this message."),
114 required=True, readonly=True)
115@@ -227,12 +225,12 @@
116 """User to user direct email communications."""
117
118 sender = Object(
119- schema=IPerson,
120+ schema=Interface,
121 title=_("The message sender"),
122 required=True, readonly=True)
123
124 recipient = Object(
125- schema=IPerson,
126+ schema=Interface,
127 title=_("The message recipient"),
128 required=True, readonly=True)
129
130
131=== modified file 'lib/lp/bugs/doc/bugtask-search.txt'
132--- lib/lp/bugs/doc/bugtask-search.txt 2010-02-02 17:12:29 +0000
133+++ lib/lp/bugs/doc/bugtask-search.txt 2010-02-12 10:32:22 +0000
134@@ -62,6 +62,17 @@
135 >>> ubuntu_firefox_bugs.count() > 0
136 True
137
138+== Person bugs ==
139+
140+To get all related tasks to a person call searchTasks() on the person
141+object:
142+
143+ >>> from canonical.launchpad.interfaces import IPersonSet
144+ >>> user = getUtility(IPersonSet).getByName('name16')
145+ >>> user_bugs = user.searchTasks(None, user=None)
146+ >>> user_bugs.count() > 0
147+ True
148+
149 == Dupes and Conjoined tasks ==
150
151 You can set flags to omit duplicates:
152@@ -962,7 +973,7 @@
153 >>> print reduce(
154 ... lambda x, y: x and y,
155 ... [task.bug.isUserAffected(foo_bar)
156- ... for task in firefox.searchTasks(search_params)])
157+ ... for task in firefox.searchTasks(search_params)])
158 True
159
160
161
162=== modified file 'lib/lp/bugs/interfaces/bug.py'
163--- lib/lp/bugs/interfaces/bug.py 2010-01-23 21:42:36 +0000
164+++ lib/lp/bugs/interfaces/bug.py 2010-02-12 10:32:22 +0000
165@@ -33,7 +33,6 @@
166 from canonical.launchpad.fields import (
167 BugField, ContentNameField, DuplicateBug, PublicPersonChoice, Tag, Title)
168 from lp.bugs.interfaces.bugattachment import IBugAttachment
169-from lp.bugs.interfaces.bugtarget import IBugTarget
170 from lp.bugs.interfaces.bugtask import (
171 BugTaskImportance, BugTaskStatus, IBugTask)
172 from lp.bugs.interfaces.bugwatch import IBugWatch
173@@ -587,7 +586,7 @@
174 """Create an INullBugTask and return it for the given parameters."""
175
176 @operation_parameters(
177- target=Reference(schema=IBugTarget, title=_('Target')))
178+ target=Reference(schema=Interface, title=_('Target')))
179 @call_with(owner=REQUEST_USER)
180 @export_factory_operation(Interface, [])
181 def addNomination(owner, target):
182@@ -601,7 +600,7 @@
183 """
184
185 @operation_parameters(
186- target=Reference(schema=IBugTarget, title=_('Target')))
187+ target=Reference(schema=Interface, title=_('Target')))
188 @export_read_operation()
189 def canBeNominatedFor(target):
190 """Can this bug nominated for this target?
191@@ -612,7 +611,7 @@
192 """
193
194 @operation_parameters(
195- target=Reference(schema=IBugTarget, title=_('Target')))
196+ target=Reference(schema=Interface, title=_('Target')))
197 @operation_returns_entry(Interface)
198 @export_read_operation()
199 def getNominationFor(target):
200@@ -625,7 +624,7 @@
201
202 @operation_parameters(
203 target=Reference(
204- schema=IBugTarget, title=_('Target'), required=False),
205+ schema=Interface, title=_('Target'), required=False),
206 nominations=List(
207 title=_("Nominations to search through."),
208 value_type=Reference(schema=Interface), # IBugNomination
209@@ -895,7 +894,7 @@
210 """Create a bug for any bug target."""
211
212 bugtarget = Reference(
213- schema=IBugTarget, title=_("Where did you find the bug?"),
214+ schema=Interface, title=_("Where did you find the bug?"),
215 required=True)
216
217
218
219=== modified file 'lib/lp/bugs/interfaces/bugtarget.py'
220--- lib/lp/bugs/interfaces/bugtarget.py 2009-08-18 11:12:06 +0000
221+++ lib/lp/bugs/interfaces/bugtarget.py 2010-02-12 10:32:22 +0000
222@@ -25,7 +25,6 @@
223 from canonical.launchpad.fields import Tag
224 from lp.bugs.interfaces.bugtask import (
225 BugTagsSearchCombinator, IBugTask, IBugTaskSearch)
226-from lp.registry.interfaces.person import IPerson
227 from lazr.enum import DBEnumeratedType
228 from lazr.restful.fields import Reference
229 from lazr.restful.interface import copy_field
230@@ -56,11 +55,6 @@
231 "A list of unassigned BugTasks for this target.")
232 all_bugtasks = Attribute(
233 "A list of all BugTasks ever reported for this target.")
234- official_bug_tags = exported(List(
235- title=_("Official Bug Tags"),
236- description=_("The list of bug tags defined as official."),
237- value_type=Tag(),
238- readonly=True))
239
240 @call_with(search_params=None, user=REQUEST_USER)
241 @operation_parameters(
242@@ -71,13 +65,13 @@
243 search_text=copy_field(IBugTaskSearch['searchtext']),
244 status=copy_field(IBugTaskSearch['status']),
245 importance=copy_field(IBugTaskSearch['importance']),
246- assignee=Reference(schema=IPerson),
247- bug_reporter=Reference(schema=IPerson),
248- bug_supervisor=Reference(schema=IPerson),
249- bug_commenter=Reference(schema=IPerson),
250- bug_subscriber=Reference(schema=IPerson),
251- owner=Reference(schema=IPerson),
252- affected_user=Reference(schema=IPerson),
253+ assignee=Reference(schema=Interface),
254+ bug_reporter=Reference(schema=Interface),
255+ bug_supervisor=Reference(schema=Interface),
256+ bug_commenter=Reference(schema=Interface),
257+ bug_subscriber=Reference(schema=Interface),
258+ owner=Reference(schema=Interface),
259+ affected_user=Reference(schema=Interface),
260 has_patch=copy_field(IBugTaskSearch['has_patch']),
261 has_cve=copy_field(IBugTaskSearch['has_cve']),
262 tags=copy_field(IBugTaskSearch['tag']),
263@@ -278,13 +272,21 @@
264 self.status = status
265
266
267-class IOfficialBugTagTargetPublic(Interface):
268+class IHasOfficialBugTags(Interface):
269+ """An entity that exposes a set of official bug tags."""
270+
271+ official_bug_tags = exported(List(
272+ title=_("Official Bug Tags"),
273+ description=_("The list of bug tags defined as official."),
274+ value_type=Tag(),
275+ readonly=True))
276+
277+
278+class IOfficialBugTagTargetPublic(IHasOfficialBugTags):
279 """Public attributes for `IOfficialBugTagTarget`."""
280
281- official_bug_tags = exported(List(
282- title=_("Official Bug Tags"),
283- description=_("The list of bug tags defined as official."),
284- value_type=Tag()))
285+ official_bug_tags = copy_field(
286+ IHasOfficialBugTags['official_bug_tags'], readonly=False)
287
288
289 class IOfficialBugTagTargetRestricted(Interface):
290
291=== modified file 'lib/lp/bugs/interfaces/bugtask.py'
292--- lib/lp/bugs/interfaces/bugtask.py 2010-02-05 16:00:51 +0000
293+++ lib/lp/bugs/interfaces/bugtask.py 2010-02-12 10:32:22 +0000
294@@ -26,6 +26,7 @@
295 'IDistroBugTask',
296 'IDistroSeriesBugTask',
297 'IFrontPageBugTaskSearch',
298+ 'IllegalRelatedBugTasksParams',
299 'IllegalTarget',
300 'INominationsReviewTableBatchNavigator',
301 'INullBugTask',
302@@ -60,7 +61,6 @@
303 from lp.soyuz.interfaces.component import IComponent
304 from canonical.launchpad.interfaces.launchpad import IHasDateCreated, IHasBug
305 from lp.registry.interfaces.mentoringoffer import ICanBeMentored
306-from lp.registry.interfaces.person import IPerson
307 from canonical.launchpad.searchbuilder import all, any, NULL
308 from canonical.launchpad.validators import LaunchpadValidationError
309 from canonical.launchpad.validators.name import name_validator
310@@ -340,6 +340,13 @@
311 """Exception raised when trying to set an illegal bug task target."""
312 webservice_error(400) #Bad request.
313
314+
315+class IllegalRelatedBugTasksParams(Exception):
316+ """Exception raised when trying to overwrite all relevant parameters
317+ in a search for related bug tasks"""
318+ webservice_error(400) #Bad request.
319+
320+
321 class IBugTask(IHasDateCreated, IHasBug, ICanBeMentored):
322 """A bug needing fixing in a particular product or package."""
323 export_as_webservice_entry()
324@@ -474,7 +481,7 @@
325 description=_("The age of this task in seconds, a delta between "
326 "now and the date the bug task was created."))
327 owner = exported(
328- Reference(title=_("The owner"), schema=IPerson, readonly=True))
329+ Reference(title=_("The owner"), schema=Interface, readonly=True))
330 target = exported(Reference(
331 title=_('Target'), required=True, schema=Interface, # IBugTarget
332 readonly=True,
333
334=== modified file 'lib/lp/bugs/interfaces/bugtracker.py'
335--- lib/lp/bugs/interfaces/bugtracker.py 2009-12-14 13:51:00 +0000
336+++ lib/lp/bugs/interfaces/bugtracker.py 2010-02-12 10:32:22 +0000
337@@ -27,7 +27,6 @@
338 from canonical.launchpad import _
339 from canonical.launchpad.fields import (
340 ContentNameField, StrippedTextLine, URIField)
341-from lp.registry.interfaces.person import IPerson
342 from canonical.launchpad.validators import LaunchpadValidationError
343 from canonical.launchpad.validators.name import name_validator
344
345@@ -216,7 +215,7 @@
346 required=False),
347 exported_as='base_url_aliases')
348 owner = exported(
349- Reference(title=_('Owner'), schema=IPerson),
350+ Reference(title=_('Owner'), schema=Interface),
351 exported_as='registrant')
352 contactdetails = exported(
353 Text(
354
355=== modified file 'lib/lp/bugs/interfaces/bugwatch.py'
356--- lib/lp/bugs/interfaces/bugwatch.py 2009-12-22 16:32:42 +0000
357+++ lib/lp/bugs/interfaces/bugwatch.py 2010-02-12 10:32:22 +0000
358@@ -22,7 +22,6 @@
359 from canonical.launchpad import _
360 from canonical.launchpad.fields import StrippedTextLine
361 from canonical.launchpad.interfaces.launchpad import IHasBug
362-from lp.registry.interfaces.person import IPerson
363 from lp.bugs.interfaces.bugtracker import IBugTracker
364
365 from lazr.restful.declarations import (
366@@ -134,7 +133,7 @@
367 exported_as='date_created')
368 owner = exported(
369 Reference(title=_('Owner'), required=True,
370- readonly=True, schema=IPerson))
371+ readonly=True, schema=Interface))
372
373 # Useful joins.
374 bugtasks = exported(
375
376=== modified file 'lib/lp/bugs/model/bugtask.py'
377--- lib/lp/bugs/model/bugtask.py 2010-02-02 17:12:29 +0000
378+++ lib/lp/bugs/model/bugtask.py 2010-02-12 10:32:22 +0000
379@@ -16,6 +16,7 @@
380 'NullBugTask',
381 'bugtask_sort_key',
382 'get_bug_privacy_filter',
383+ 'get_related_bugtasks_search_params',
384 'search_value_to_where_condition']
385
386
387@@ -61,7 +62,7 @@
388 INullBugTask, IProductSeriesBugTask, IUpstreamBugTask, IllegalTarget,
389 RESOLVED_BUGTASK_STATUSES, UNRESOLVED_BUGTASK_STATUSES,
390 UserCannotEditBugTaskImportance, UserCannotEditBugTaskMilestone,
391- UserCannotEditBugTaskStatus)
392+ UserCannotEditBugTaskStatus, IllegalRelatedBugTasksParams)
393 from lp.bugs.model.bugsubscription import BugSubscription
394 from lp.registry.interfaces.distribution import (
395 IDistribution, IDistributionSet)
396@@ -82,7 +83,8 @@
397 from canonical.launchpad.searchbuilder import (
398 all, any, greater_than, NULL, not_equals)
399 from lp.registry.interfaces.person import (
400- validate_person_not_private_membership, validate_public_person)
401+ IPerson, validate_person_not_private_membership,
402+ validate_public_person)
403 from canonical.launchpad.webapp.interfaces import (
404 IStoreSelector, DEFAULT_FLAVOR, MAIN_STORE, NotFoundError)
405
406@@ -137,6 +139,48 @@
407 return (
408 bugtask.bug.id, distribution_name, product_name, productseries_name,
409 distroseries_name, sourcepackage_name)
410+
411+def get_related_bugtasks_search_params(user, context, **kwargs):
412+ """Returns a list of `BugTaskSearchParams` which can be used to
413+ search for all tasks related to a user given by `context`.
414+
415+ Which tasks are related to a user?
416+ * the user has to be either assignee or owner of this tasks
417+ OR
418+ * the user has to be subscriber or commenter to the underlying bug
419+ OR
420+ * the user is reporter of the underlying bug, but this condition
421+ is automatically fulfilled by the first one as each new bug
422+ always get one task owned by the bug reporter
423+ """
424+ assert IPerson.providedBy(context), "Context argument needs to be IPerson"
425+ relevant_fields = ('assignee', 'bug_subscriber', 'owner', 'bug_commenter')
426+ search_params = []
427+ for key in relevant_fields:
428+ # all these parameter default to None
429+ user_param = kwargs.get(key)
430+ if user_param is None or user_param == context:
431+ # we are only creating a `BugTaskSearchParams` object if
432+ # the field is None or equal to the context
433+ arguments = kwargs.copy()
434+ arguments[key] = context
435+ if key == 'owner':
436+ # Specify both owner and bug_reporter to try to
437+ # prevent the same bug (but different tasks)
438+ # being displayed.
439+ # see `PersonRelatedBugTaskSearchListingView.searchUnbatched`
440+ arguments['bug_reporter'] = context
441+ search_params.append(
442+ BugTaskSearchParams.fromSearchForm(user, **arguments))
443+ if len(search_params) == 0:
444+ # unable to search for related tasks to user_context because user
445+ # modified the query in an invalid way by overwriting all user
446+ # related parameters
447+ raise IllegalRelatedBugTasksParams(
448+ ('Cannot search for related tasks to \'%s\', at least one '
449+ 'of these parameter has to be empty: %s'
450+ %(context.name, ", ".join(relevant_fields))))
451+ return search_params
452
453
454 class BugTaskDelta:
455
456=== modified file 'lib/lp/bugs/stories/webservice/xx-bug.txt'
457--- lib/lp/bugs/stories/webservice/xx-bug.txt 2010-02-04 21:18:35 +0000
458+++ lib/lp/bugs/stories/webservice/xx-bug.txt 2010-02-12 10:32:22 +0000
459@@ -676,7 +676,7 @@
460 ...
461 Location: http://.../bugs/.../nominations/...
462 ...
463-
464+
465 >>> nominations = webservice.named_get(
466 ... '/bugs/%d' % bug.id, 'getNominations').jsonBody()
467 >>> pprint_collection(nominations)
468@@ -699,7 +699,7 @@
469 John cannot approve or decline the nomination.
470
471 >>> nom_url = nominations['entries'][0]['self_link']
472-
473+
474 >>> print john_webservice.named_get(nom_url, 'canApprove').jsonBody()
475 False
476
477@@ -715,9 +715,9 @@
478 >>> logout()
479
480 Eric, however, can and does decline the nomination.
481-
482+
483 >>> eric_webservice = webservice_for_person(
484- ... eric, permission=OAuthPermission.WRITE_PRIVATE)
485+ ... eric, permission=OAuthPermission.WRITE_PRIVATE)
486 >>> print eric_webservice.named_post(nom_url, 'decline')
487 HTTP/1.1 200 Ok...
488
489@@ -1645,6 +1645,76 @@
490 http://api.launchpad.dev/beta/ubuntu/+source/linux-source-2.6.15/+bug/10
491
492
493+User related bug tasks
494+~~~~~~~~~~~~~~~~~~~~~~
495+
496+Calling searchTasks() on a Person object returns a collection of tasks
497+related to this person.
498+
499+First create some sample data
500+
501+ >>> login('foo.bar@canonical.com')
502+ >>> testuser1 = factory.makePerson(name='testuser1')
503+ >>> testuser2 = factory.makePerson(name='testuser2')
504+ >>> testuser3 = factory.makePerson(name='testuser3')
505+ >>> testbug1 = factory.makeBug(owner=testuser1)
506+ >>> testbug2 = factory.makeBug(owner=testuser1)
507+ >>> subscription = testbug2.subscribe(testuser2, testuser2)
508+ >>> logout()
509+
510+There are two tasks related to `testuser1`, the initial tasks of both
511+bugs:
512+
513+ >>> related = webservice.named_get(
514+ ... '/~testuser1', 'searchTasks'
515+ ... ).jsonBody()
516+ >>> pprint_collection(related)
517+ start: 0
518+ total_size: 2
519+ ---
520+ ...
521+ owner_link: u'http://api.launchpad.dev/beta/~testuser1'
522+ ...
523+ ---
524+ ...
525+ owner_link: u'http://api.launchpad.dev/beta/~testuser1'
526+ ...
527+
528+`testuser2` is subscribed to `testbugs2`, so this bug is related to this
529+user:
530+
531+ >>> related = webservice.named_get(
532+ ... '/~testuser2', 'searchTasks'
533+ ... ).jsonBody()
534+ >>> len(related['entries']) == 1
535+ True
536+ >>> int(related['entries'][0]['bug_link'].split('/')[-1]) == testbug2.id
537+ True
538+
539+`testuser3` is not active, so the collection of related tasks to him is
540+empty:
541+
542+ >>> related = webservice.named_get(
543+ ... '/~testuser3', 'searchTasks'
544+ ... ).jsonBody()
545+ >>> pprint_collection(related)
546+ start: None
547+ total_size: 0
548+ ---
549+
550+You are not allowed to overwrite all user related parameter in the same
551+query, because in this case this bug will no be related to the person
552+anymore. In this case a `400 Bad Request`-Error will be returned
553+
554+ >>> name12 = webservice.get("/~name12").jsonBody()
555+ >>> print webservice.named_get(
556+ ... '/~name16', 'searchTasks', assignee=name12['self_link'],
557+ ... owner=name12['self_link'], bug_subscriber=name12['self_link'],
558+ ... bug_commenter=name12['self_link']
559+ ... )
560+ HTTP/1.1 400 Bad Request...
561+
562+
563 Affected users
564 --------------
565
566
567=== modified file 'lib/lp/registry/configure.zcml'
568--- lib/lp/registry/configure.zcml 2010-02-10 10:37:16 +0000
569+++ lib/lp/registry/configure.zcml 2010-02-12 10:32:22 +0000
570@@ -905,6 +905,8 @@
571 <allow
572 interface="lp.bugs.interfaces.bugtarget.IHasBugs"/>
573 <allow
574+ interface="lp.bugs.interfaces.bugtarget.IHasOfficialBugTags"/>
575+ <allow
576 interface="canonical.launchpad.webapp.interfaces.IAuthorization"/>
577 <require
578 permission="launchpad.Edit"
579
580=== modified file 'lib/lp/registry/interfaces/distribution.py'
581--- lib/lp/registry/interfaces/distribution.py 2010-02-04 21:18:32 +0000
582+++ lib/lp/registry/interfaces/distribution.py 2010-02-12 10:32:22 +0000
583@@ -532,13 +532,6 @@
584 """An operating system distribution."""
585 export_as_webservice_entry()
586
587-# Patch the official_bug_tags field to make sure that it's
588-# writable from the API, and not readonly like its definition
589-# in IHasBugs.
590-writable_obt_field = copy_field(IDistribution['official_bug_tags'])
591-writable_obt_field.readonly = False
592-IDistribution._v_attrs['official_bug_tags'] = writable_obt_field
593-
594
595 class IBaseDistribution(IDistribution):
596 """A Distribution that is the base for other Distributions."""
597
598=== modified file 'lib/lp/registry/interfaces/distributionsourcepackage.py'
599--- lib/lp/registry/interfaces/distributionsourcepackage.py 2009-12-05 18:37:28 +0000
600+++ lib/lp/registry/interfaces/distributionsourcepackage.py 2010-02-12 10:32:22 +0000
601@@ -21,7 +21,7 @@
602 rename_parameters_as)
603
604 from canonical.launchpad import _
605-from lp.bugs.interfaces.bugtarget import IBugTarget
606+from lp.bugs.interfaces.bugtarget import IBugTarget, IHasOfficialBugTags
607 from lp.bugs.interfaces.bugtask import IBugTask
608 from lp.code.interfaces.hasbranches import IHasBranches, IHasMergeProposals
609 from lp.registry.interfaces.distribution import IDistribution
610@@ -31,7 +31,8 @@
611
612
613 class IDistributionSourcePackage(IBugTarget, IHasBranches, IHasMergeProposals,
614- IStructuralSubscriptionTarget):
615+ IStructuralSubscriptionTarget,
616+ IHasOfficialBugTags):
617 """Represents a source package in a distribution.
618
619 Create IDistributionSourcePackages by invoking
620@@ -101,7 +102,7 @@
621 """
622
623 def get_distroseries_packages(active_only=True):
624- """Return a list of DistroSeriesSourcePackage objects, each
625+ """Return a list of DistroSeriesSourcePackage objects, each
626 representing this same source package in the series of this
627 distribution.
628
629
630=== modified file 'lib/lp/registry/interfaces/distroseries.py'
631--- lib/lp/registry/interfaces/distroseries.py 2010-01-21 17:49:38 +0000
632+++ lib/lp/registry/interfaces/distroseries.py 2010-02-12 10:32:22 +0000
633@@ -28,7 +28,8 @@
634 from lp.registry.interfaces.series import SeriesStatus
635 from lp.registry.interfaces.structuralsubscription import (
636 IStructuralSubscriptionTarget)
637-from lp.bugs.interfaces.bugtarget import IBugTarget, IHasBugs
638+from lp.bugs.interfaces.bugtarget import (
639+ IBugTarget, IHasBugs, IHasOfficialBugTags)
640 from lp.soyuz.interfaces.buildrecords import IHasBuildRecords
641 from lp.translations.interfaces.languagepack import ILanguagePack
642 from canonical.launchpad.interfaces.launchpad import (
643@@ -154,7 +155,8 @@
644
645 class IDistroSeriesPublic(IHasAppointedDriver, IHasDrivers, IHasOwner,
646 IBugTarget, ISpecificationGoal, IHasMilestones,
647- IHasBuildRecords, ISeriesMixin):
648+ IHasBuildRecords, ISeriesMixin,
649+ IHasOfficialBugTags):
650 """Public IDistroSeries properties."""
651
652 id = Attribute("The distroseries's unique number.")
653@@ -389,7 +391,7 @@
654 def getDistroArchSeriesByProcessor(processor):
655 """Return the distroarchseries for this distroseries with the
656 given architecturetag from a `IProcessor`.
657-
658+
659 :param processor: An `IProcessor`
660 :return: An `IDistroArchSeries` or None when none was found.
661 """
662
663=== modified file 'lib/lp/registry/interfaces/milestone.py'
664--- lib/lp/registry/interfaces/milestone.py 2010-01-27 19:12:10 +0000
665+++ lib/lp/registry/interfaces/milestone.py 2010-02-12 10:32:22 +0000
666@@ -21,7 +21,7 @@
667 from lp.registry.interfaces.structuralsubscription import (
668 IStructuralSubscriptionTarget)
669 from lp.registry.interfaces.productrelease import IProductRelease
670-from lp.bugs.interfaces.bugtarget import IHasBugs
671+from lp.bugs.interfaces.bugtarget import IHasBugs, IHasOfficialBugTags
672 from lp.bugs.interfaces.bugtask import IBugTask
673 from canonical.launchpad import _
674 from canonical.launchpad.fields import (
675@@ -70,7 +70,8 @@
676 return milestone
677
678
679-class IMilestone(IHasBugs, IStructuralSubscriptionTarget):
680+class IMilestone(IHasBugs, IStructuralSubscriptionTarget,
681+ IHasOfficialBugTags):
682 """A milestone, or a targeting point for bugs and other
683 release-management items that need coordination.
684 """
685
686=== modified file 'lib/lp/registry/interfaces/person.py'
687--- lib/lp/registry/interfaces/person.py 2010-02-08 14:37:50 +0000
688+++ lib/lp/registry/interfaces/person.py 2010-02-12 10:32:22 +0000
689@@ -97,6 +97,7 @@
690 from canonical.launchpad.webapp.interfaces import NameLookupFailed
691 from canonical.launchpad.webapp.authorization import check_permission
692
693+from lp.bugs.interfaces.bugtarget import IHasBugs
694
695 PRIVATE_TEAM_PREFIX = 'private-'
696
697@@ -478,7 +479,7 @@
698 class IPersonPublic(IHasBranches, IHasSpecifications, IHasMentoringOffers,
699 IHasMergeProposals, IHasLogo, IHasMugshot, IHasIcon,
700 IHasLocation, IHasRequestedReviews, IObjectWithLocation,
701- IPrivacy):
702+ IPrivacy, IHasBugs):
703 """Public attributes for a Person."""
704
705 id = Int(title=_('ID'), required=True, readonly=True)
706@@ -1017,18 +1018,6 @@
707 used between TeamMembership and Person objects.
708 """
709
710- def searchTasks(search_params, *args):
711- """Search IBugTasks with the given search parameters.
712-
713- :search_params: a BugTaskSearchParams object
714- :args: any number of BugTaskSearchParams objects
715-
716- If more than one BugTaskSearchParams is given, return the union of
717- IBugTasks which match any of them.
718-
719- Return an iterable of matching results.
720- """
721-
722 def getLatestMaintainedPackages():
723 """Return `SourcePackageRelease`s maintained by this person.
724
725
726=== modified file 'lib/lp/registry/interfaces/product.py'
727--- lib/lp/registry/interfaces/product.py 2010-01-20 13:58:45 +0000
728+++ lib/lp/registry/interfaces/product.py 2010-02-12 10:32:22 +0000
729@@ -724,13 +724,6 @@
730 IProject['products'].value_type = Reference(IProduct)
731 IProductRelease['product'].schema = IProduct
732
733-# Patch the official_bug_tags field to make sure that it's
734-# writable from the API, and not readonly like its definition
735-# in IHasBugs.
736-writable_obt_field = copy_field(IProduct['official_bug_tags'])
737-writable_obt_field.readonly = False
738-IProduct._v_attrs['official_bug_tags'] = writable_obt_field
739-
740
741 class IProductSet(Interface):
742 export_as_webservice_collection(IProduct)
743
744=== modified file 'lib/lp/registry/interfaces/productseries.py'
745--- lib/lp/registry/interfaces/productseries.py 2009-12-13 11:55:40 +0000
746+++ lib/lp/registry/interfaces/productseries.py 2010-02-12 10:32:22 +0000
747@@ -24,7 +24,7 @@
748 from lp.registry.interfaces.structuralsubscription import (
749 IStructuralSubscriptionTarget)
750 from lp.code.interfaces.branch import IBranch
751-from lp.bugs.interfaces.bugtarget import IBugTarget
752+from lp.bugs.interfaces.bugtarget import IBugTarget, IHasOfficialBugTags
753 from lp.registry.interfaces.series import SeriesStatus
754 from canonical.launchpad.interfaces.launchpad import (
755 IHasAppointedDriver, IHasDrivers)
756@@ -93,7 +93,8 @@
757
758
759 class IProductSeriesPublic(IHasAppointedDriver, IHasDrivers, IHasOwner,
760- IBugTarget, ISpecificationGoal, IHasMilestones):
761+ IBugTarget, ISpecificationGoal, IHasMilestones,
762+ IHasOfficialBugTags):
763 """Public IProductSeries properties."""
764 # XXX Mark Shuttleworth 2004-10-14: Would like to get rid of id in
765 # interfaces, as soon as SQLobject allows using the object directly
766
767=== modified file 'lib/lp/registry/interfaces/project.py'
768--- lib/lp/registry/interfaces/project.py 2009-12-05 18:37:28 +0000
769+++ lib/lp/registry/interfaces/project.py 2010-02-12 10:32:22 +0000
770@@ -24,7 +24,7 @@
771 from lp.code.interfaces.branchvisibilitypolicy import (
772 IHasBranchVisibilityPolicy)
773 from lp.code.interfaces.hasbranches import IHasBranches, IHasMergeProposals
774-from lp.bugs.interfaces.bugtarget import IHasBugs
775+from lp.bugs.interfaces.bugtarget import IHasBugs, IHasOfficialBugTags
776 from lp.registry.interfaces.karma import IKarmaContext
777 from canonical.launchpad.interfaces.launchpad import (
778 IHasAppointedDriver, IHasDrivers, IHasIcon, IHasLogo, IHasMugshot)
779@@ -64,7 +64,8 @@
780 IHasDrivers, IHasBranchVisibilityPolicy, IHasIcon, IHasLogo,
781 IHasMentoringOffers, IHasMergeProposals, IHasMilestones, IHasMugshot,
782 IHasOwner, IHasSpecifications, IHasSprints, IHasTranslationGroup,
783- IMakesAnnouncements, IKarmaContext, IPillar, IRootContext):
784+ IMakesAnnouncements, IKarmaContext, IPillar, IRootContext,
785+ IHasOfficialBugTags):
786 """Public IProject properties."""
787
788 id = Int(title=_('ID'), readonly=True)
789
790=== modified file 'lib/lp/registry/interfaces/sourcepackage.py'
791--- lib/lp/registry/interfaces/sourcepackage.py 2010-02-04 21:18:32 +0000
792+++ lib/lp/registry/interfaces/sourcepackage.py 2010-02-12 10:32:22 +0000
793@@ -21,7 +21,7 @@
794 from lazr.enum import DBEnumeratedType, DBItem
795
796 from canonical.launchpad import _
797-from lp.bugs.interfaces.bugtarget import IBugTarget
798+from lp.bugs.interfaces.bugtarget import IBugTarget, IHasOfficialBugTags
799 from lp.code.interfaces.hasbranches import IHasBranches, IHasMergeProposals
800 from lp.soyuz.interfaces.component import IComponent
801 from lazr.restful.fields import Reference, ReferenceChoice
802@@ -31,7 +31,8 @@
803 operation_returns_entry, REQUEST_USER)
804
805
806-class ISourcePackage(IBugTarget, IHasBranches, IHasMergeProposals):
807+class ISourcePackage(IBugTarget, IHasBranches, IHasMergeProposals,
808+ IHasOfficialBugTags):
809 """A SourcePackage. See the MagicSourcePackage specification. This
810 interface preserves as much as possible of the old SourcePackage
811 interface from the SourcePackage table, with the new table-less
812
813=== modified file 'lib/lp/registry/model/person.py'
814--- lib/lp/registry/model/person.py 2010-02-10 23:14:56 +0000
815+++ lib/lp/registry/model/person.py 2010-02-12 10:32:22 +0000
816@@ -84,7 +84,7 @@
817 from lp.code.model.hasbranches import (
818 HasBranchesMixin, HasMergeProposalsMixin, HasRequestedReviewsMixin)
819 from lp.bugs.interfaces.bugtask import (
820- BugTaskSearchParams, IBugTaskSet)
821+ BugTaskSearchParams, IBugTaskSet, IllegalRelatedBugTasksParams)
822 from lp.bugs.interfaces.bugtarget import IBugTarget
823 from lp.registry.interfaces.codeofconduct import (
824 ISignedCodeOfConductSet)
825@@ -128,7 +128,8 @@
826
827 from lp.soyuz.model.archive import Archive
828 from lp.registry.model.codeofconduct import SignedCodeOfConduct
829-from lp.bugs.model.bugtask import BugTask
830+from lp.bugs.model.bugtask import (
831+ BugTask, get_related_bugtasks_search_params)
832 from canonical.launchpad.database.emailaddress import (
833 EmailAddress, HasOwnerMixin)
834 from lp.registry.model.karma import KarmaCache, KarmaTotalCache
835@@ -838,6 +839,21 @@
836
837 def searchTasks(self, search_params, *args, **kwargs):
838 """See `IHasBugs`."""
839+ if search_params is None and len(args) == 0:
840+ # this method is called via webapi directly
841+ # calling this method on a Person object directly via the
842+ # webservice API means searching for user related tasks
843+ user = kwargs.pop('user')
844+ try:
845+ search_params = get_related_bugtasks_search_params(
846+ user, self, **kwargs)
847+ except IllegalRelatedBugTasksParams, e:
848+ # dirty hack, marking an exception with a HTTP error
849+ # only works if the exception is raised in the exported
850+ # method, see docstring of
851+ # `lazr.restful.declarations.webservice_error()`
852+ raise e
853+ return getUtility(IBugTaskSet).search(*search_params)
854 if len(kwargs) > 0:
855 # if keyword arguments are supplied, use the deault
856 # implementation in HasBugsBase.
857
858=== modified file 'lib/lp/registry/tests/test_person.py'
859--- lib/lp/registry/tests/test_person.py 2009-12-08 17:53:37 +0000
860+++ lib/lp/registry/tests/test_person.py 2010-02-12 10:32:22 +0000
861@@ -29,6 +29,8 @@
862 from lp.registry.model.structuralsubscription import (
863 StructuralSubscription)
864 from lp.registry.model.person import Person
865+from lp.bugs.model.bugtask import get_related_bugtasks_search_params
866+from lp.bugs.interfaces.bugtask import IllegalRelatedBugTasksParams
867 from lp.answers.model.answercontact import AnswerContact
868 from lp.blueprints.model.specification import Specification
869 from lp.testing import TestCaseWithFactory
870@@ -495,5 +497,105 @@
871 name='/john')
872
873
874+class TestPersonRelatedBugTaskSearch(TestCaseWithFactory):
875+
876+ layer = LaunchpadFunctionalLayer
877+
878+ def setUp(self):
879+ super(TestPersonRelatedBugTaskSearch, self).setUp()
880+ self.user = self.factory.makePerson(displayname="User")
881+ self.context = self.factory.makePerson(displayname="Context")
882+
883+ def checkUserFields(
884+ self, params, assignee=None, bug_subscriber=None,
885+ owner=None, bug_commenter=None, bug_reporter=None):
886+ self.failUnlessEqual(assignee, params.assignee)
887+ # fromSearchForm() takes a bug_subscriber parameter, but saves
888+ # it as subscriber on the parameter object.
889+ self.failUnlessEqual(bug_subscriber, params.subscriber)
890+ self.failUnlessEqual(owner, params.owner)
891+ self.failUnlessEqual(bug_commenter, params.bug_commenter)
892+ self.failUnlessEqual(bug_reporter, params.bug_reporter)
893+
894+ def test_get_related_bugtasks_search_params(self):
895+ # With no specified options, get_related_bugtasks_search_params()
896+ # returns 4 BugTaskSearchParams objects, each with a different
897+ # user field set.
898+ search_params = get_related_bugtasks_search_params(self.user, self.context)
899+ self.assertEqual(len(search_params), 4)
900+ self.checkUserFields(
901+ search_params[0], assignee=self.context)
902+ self.checkUserFields(
903+ search_params[1], bug_subscriber=self.context)
904+ self.checkUserFields(
905+ search_params[2], owner=self.context, bug_reporter=self.context)
906+ self.checkUserFields(
907+ search_params[3], bug_commenter=self.context)
908+
909+ def test_get_related_bugtasks_search_params_with_assignee(self):
910+ # With assignee specified, get_related_bugtasks_search_params() returns
911+ # 3 BugTaskSearchParams objects.
912+ search_params = get_related_bugtasks_search_params(
913+ self.user, self.context, assignee=self.user)
914+ self.assertEqual(len(search_params), 3)
915+ self.checkUserFields(
916+ search_params[0], assignee=self.user, bug_subscriber=self.context)
917+ self.checkUserFields(
918+ search_params[1], assignee=self.user, owner=self.context,
919+ bug_reporter=self.context)
920+ self.checkUserFields(
921+ search_params[2], assignee=self.user, bug_commenter=self.context)
922+
923+ def test_get_related_bugtasks_search_params_with_owner(self):
924+ # With owner specified, get_related_bugtasks_search_params() returns
925+ # 3 BugTaskSearchParams objects.
926+ search_params = get_related_bugtasks_search_params(
927+ self.user, self.context, owner=self.user)
928+ self.assertEqual(len(search_params), 3)
929+ self.checkUserFields(
930+ search_params[0], owner=self.user, assignee=self.context)
931+ self.checkUserFields(
932+ search_params[1], owner=self.user, bug_subscriber=self.context)
933+ self.checkUserFields(
934+ search_params[2], owner=self.user, bug_commenter=self.context)
935+
936+ def test_get_related_bugtasks_search_params_with_bug_reporter(self):
937+ # With bug reporter specified, get_related_bugtasks_search_params()
938+ # returns 4 BugTaskSearchParams objects, but the bug reporter
939+ # is overwritten in one instance.
940+ search_params = get_related_bugtasks_search_params(
941+ self.user, self.context, bug_reporter=self.user)
942+ self.assertEqual(len(search_params), 4)
943+ self.checkUserFields(
944+ search_params[0], bug_reporter=self.user,
945+ assignee=self.context)
946+ self.checkUserFields(
947+ search_params[1], bug_reporter=self.user,
948+ bug_subscriber=self.context)
949+ # When a BugTaskSearchParams is prepared with the owner filled
950+ # in, the bug reporter is overwritten to match.
951+ self.checkUserFields(
952+ search_params[2], bug_reporter=self.context,
953+ owner=self.context)
954+ self.checkUserFields(
955+ search_params[3], bug_reporter=self.user,
956+ bug_commenter=self.context)
957+
958+ def test_get_related_bugtasks_search_params_illegal(self):
959+ self.assertRaises(
960+ IllegalRelatedBugTasksParams,
961+ get_related_bugtasks_search_params, self.user, self.context,
962+ assignee=self.user, owner=self.user, bug_commenter=self.user,
963+ bug_subscriber=self.user)
964+
965+ def test_get_related_bugtasks_search_params_illegal_context(self):
966+ # in case the `context` argument is not of type IPerson an
967+ # AssertionError is raised
968+ self.assertRaises(
969+ AssertionError,
970+ get_related_bugtasks_search_params, self.user, "Username",
971+ assignee=self.user)
972+
973+
974 def test_suite():
975 return unittest.TestLoader().loadTestsFromName(__name__)