Merge lp:~deryck/launchpad/less-restrictive-assign-someone-else-603281 into lp:launchpad
- less-restrictive-assign-someone-else-603281
- Merge into devel
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Brad Crittenden | ||||
Approved revision: | no longer in the source branch. | ||||
Merged at revision: | 11479 | ||||
Proposed branch: | lp:~deryck/launchpad/less-restrictive-assign-someone-else-603281 | ||||
Merge into: | lp:launchpad | ||||
Diff against target: |
621 lines (+185/-144) 6 files modified
lib/lp/bugs/browser/tests/test_bugtask.py (+20/-3) lib/lp/bugs/doc/bugtask.txt (+0/-74) lib/lp/bugs/interfaces/bugtask.py (+4/-3) lib/lp/bugs/model/bugtask.py (+46/-30) lib/lp/bugs/stories/bugtask-management/xx-change-assignee.txt (+13/-1) lib/lp/bugs/tests/test_bugtask.py (+102/-33) |
||||
To merge this branch: | bzr merge lp:~deryck/launchpad/less-restrictive-assign-someone-else-603281 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Brad Crittenden (community) | Approve | ||
Review via email: mp+33824@code.launchpad.net |
Commit message
Allow anyone to set bugtask assignee if there is not a defined bug supervisor.
Description of the change
This branch fixes bug 603281, which notes that when we changed
permissions for who can assign a bug to someone else that we made that a
bit too strict for projects without a bug supervisor.
This branch fixes that by lifting the restrictions if a bug supervisor
is not set for a project/distro. This means that projects that haven't
yet set a bug supervisor will be able to assign bugs without having to
set a bug supervisor. The functionality returns to what it once was.
But for projects that have a bug supervisor, the new requirements will
remain, i.e. that you cannot assign a bug to someone else.
This includes a test update and then the couple line change to
BugTask.
this method, nothing had to be changed with the JS widget.
Deryck Hodge (deryck) wrote : | # |
Hi, bac.
Thanks for the review! I like your suggestion and will do that.
Cheers,
deryck
Robert Collins (lifeless) wrote : | # |
BTW:
or user == celebrities.
Is slow code - it will do, on a bad day, 5 or 6 queries all on its
own. Thats not a huge issue, unless it ends up in a loop - but as it
happens we have loops that do this - the bug importer loops.
Please consider making a tech debt bug or something.
Preview Diff
1 | === modified file 'lib/lp/bugs/browser/tests/test_bugtask.py' |
2 | --- lib/lp/bugs/browser/tests/test_bugtask.py 2010-08-20 20:31:18 +0000 |
3 | +++ lib/lp/bugs/browser/tests/test_bugtask.py 2010-08-31 18:56:47 +0000 |
4 | @@ -312,11 +312,16 @@ |
5 | |
6 | def setUp(self): |
7 | super(TestBugTaskEditViewAssigneeField, self).setUp() |
8 | - self.bugtask = self.factory.makeBug().default_bugtask |
9 | + self.owner = self.factory.makePerson() |
10 | + self.product = self.factory.makeProduct(owner=self.owner) |
11 | + self.bugtask = self.factory.makeBug( |
12 | + product=self.product).default_bugtask |
13 | |
14 | - def test_assignee_field_vocabulary_regular_user(self): |
15 | + def test_assignee_vocabulary_regular_user_with_bug_supervisor(self): |
16 | # For regular users, the assignee vocabulary is |
17 | - # AllUserTeamsParticipation. |
18 | + # AllUserTeamsParticipation if there is a bug supervisor defined. |
19 | + login_person(self.owner) |
20 | + self.product.setBugSupervisor(self.owner, self.owner) |
21 | login('test@canonical.com') |
22 | view = BugTaskEditView(self.bugtask, LaunchpadTestRequest()) |
23 | view.initialize() |
24 | @@ -324,6 +329,18 @@ |
25 | 'AllUserTeamsParticipation', |
26 | view.form_fields['assignee'].field.vocabularyName) |
27 | |
28 | + def test_assignee_vocabulary_regular_user_without_bug_supervisor(self): |
29 | + # For regular users, the assignee vocabulary is |
30 | + # ValidAssignee is there is not a bug supervisor defined. |
31 | + login_person(self.owner) |
32 | + self.product.setBugSupervisor(None, self.owner) |
33 | + login('test@canonical.com') |
34 | + view = BugTaskEditView(self.bugtask, LaunchpadTestRequest()) |
35 | + view.initialize() |
36 | + self.assertEqual( |
37 | + 'ValidAssignee', |
38 | + view.form_fields['assignee'].field.vocabularyName) |
39 | + |
40 | def test_assignee_field_vocabulary_privileged_user(self): |
41 | # Privileged users, like the bug task target owner, can |
42 | # assign anybody. |
43 | |
44 | === modified file 'lib/lp/bugs/doc/bugtask.txt' |
45 | --- lib/lp/bugs/doc/bugtask.txt 2010-07-25 14:39:52 +0000 |
46 | +++ lib/lp/bugs/doc/bugtask.txt 2010-08-31 18:56:47 +0000 |
47 | @@ -564,80 +564,6 @@ |
48 | ... devel_focus_alsa_utils_task.date_assigned) |
49 | True |
50 | |
51 | -Ordinary users can assign themselves and teams they are a member of. |
52 | - |
53 | - >>> login('test@canonical.com') |
54 | - >>> devel_focus_alsa_utils_task.transitionToAssignee(None) |
55 | - Traceback (most recent call last): |
56 | - ... |
57 | - UserCannotEditBugTaskAssignee: Regular users can assign and unassign |
58 | - only themselves and their teams. Only project owners, bug supervisors, |
59 | - drivers and release managers can assign others. |
60 | - |
61 | - >>> devel_focus_alsa_utils_task.transitionToAssignee(sample_person) |
62 | - >>> print generic_alsa_utils_task.assignee.displayname |
63 | - Sample Person |
64 | - >>> login('no-priv@canonical.com') |
65 | - >>> devel_focus_alsa_utils_task.transitionToAssignee(no_priv) |
66 | - >>> print devel_focus_alsa_utils_task.assignee.displayname |
67 | - No Privileges Person |
68 | - >>> devel_focus_alsa_utils_task.transitionToAssignee(None) |
69 | - >>> print generic_alsa_utils_task.assignee |
70 | - None |
71 | - |
72 | - >>> warty_security_team = getUtility(IPersonSet).getByName('name20') |
73 | - >>> no_priv.inTeam(warty_security_team) |
74 | - False |
75 | - >>> devel_focus_alsa_utils_task.transitionToAssignee(warty_security_team) |
76 | - Traceback (most recent call last): |
77 | - ... |
78 | - UserCannotEditBugTaskAssignee: Regular users can assign and unassign |
79 | - only themselves and their teams. Only project owners, bug supervisors, |
80 | - drivers and release managers can assign others. |
81 | - |
82 | - >>> login('test@canonical.com') |
83 | - >>> sample_person.inTeam(warty_security_team) |
84 | - True |
85 | - >>> devel_focus_alsa_utils_task.transitionToAssignee(warty_security_team) |
86 | - >>> print devel_focus_alsa_utils_task.assignee.displayname |
87 | - Warty Security Team |
88 | - |
89 | -These persons can assign anybody: Project owners... |
90 | - |
91 | - >>> from lp.testing import login_person |
92 | - >>> login_person(devel_focus_alsa_utils_task.pillar.owner) |
93 | - >>> devel_focus_alsa_utils_task.transitionToAssignee(no_priv) |
94 | - >>> print generic_alsa_utils_task.assignee.displayname |
95 | - No Privileges Person |
96 | - |
97 | -...drivers... |
98 | - |
99 | - >>> devel_focus_alsa_utils_task.pillar.driver = sample_person |
100 | - >>> login('test@canonical.com') |
101 | - >>> devel_focus_alsa_utils_task.transitionToAssignee(None) |
102 | - >>> print devel_focus_alsa_utils_task.assignee |
103 | - None |
104 | - >>> devel_focus_alsa_utils_task.transitionToAssignee(mark) |
105 | - >>> print devel_focus_alsa_utils_task.assignee.displayname |
106 | - Mark Shuttleworth |
107 | - |
108 | -...bug supervisors... |
109 | - |
110 | - >>> login_person(devel_focus_alsa_utils_task.pillar.owner) |
111 | - >>> devel_focus_alsa_utils_task.pillar.setBugSupervisor( |
112 | - ... sample_person, devel_focus_alsa_utils_task.pillar.owner) |
113 | - >>> devel_focus_alsa_utils_task.pillar.driver = None |
114 | - >>> login('test@canonical.com') |
115 | - >>> devel_focus_alsa_utils_task.transitionToAssignee(no_priv) |
116 | - >>> print devel_focus_alsa_utils_task.assignee.displayname |
117 | - No Privileges Person |
118 | - |
119 | - ...and Launchpad admins. |
120 | - |
121 | - >>> login('foo.bar@canonical.com') |
122 | - >>> devel_focus_alsa_utils_task.transitionToAssignee(sample_person) |
123 | - |
124 | - |
125 | 3. Importance |
126 | |
127 | >>> print generic_netapplet_task.importance.title |
128 | |
129 | === modified file 'lib/lp/bugs/interfaces/bugtask.py' |
130 | --- lib/lp/bugs/interfaces/bugtask.py 2010-08-26 18:57:35 +0000 |
131 | +++ lib/lp/bugs/interfaces/bugtask.py 2010-08-31 18:56:47 +0000 |
132 | @@ -737,8 +737,9 @@ |
133 | def userCanSetAnyAssignee(user): |
134 | """Check if the current user can set anybody sa a bugtask assignee. |
135 | |
136 | - Return True for project owner, project drivers, series drivers, |
137 | - bug supervisors and Launchpad admins; return False for other users. |
138 | + Owners, drivers, bug supervisors and Launchpad admins can always |
139 | + assign to someone else. Other users can assign to someone else if a |
140 | + bug supervisor is not defined. |
141 | """ |
142 | |
143 | def userCanUnassign(user): |
144 | @@ -1401,7 +1402,7 @@ |
145 | def searchBugIds(params): |
146 | """Search bug ids. |
147 | |
148 | - This is a variation on IBugTaskSet.search that returns only the bug ids. |
149 | + This is a variation on IBugTaskSet.search that returns only bug ids. |
150 | |
151 | :param params: the BugTaskSearchParams to search on. |
152 | """ |
153 | |
154 | === modified file 'lib/lp/bugs/model/bugtask.py' |
155 | --- lib/lp/bugs/model/bugtask.py 2010-08-24 15:29:01 +0000 |
156 | +++ lib/lp/bugs/model/bugtask.py 2010-08-31 18:56:47 +0000 |
157 | @@ -21,7 +21,7 @@ |
158 | |
159 | |
160 | import datetime |
161 | -from operator import attrgetter, itemgetter |
162 | +from operator import attrgetter |
163 | |
164 | from lazr.enum import DBItem |
165 | import pytz |
166 | @@ -42,7 +42,6 @@ |
167 | Or, |
168 | SQL, |
169 | ) |
170 | -from storm.sqlobject import SQLObjectResultSet |
171 | from storm.store import EmptyResultSet |
172 | from storm.zope.interfaces import ( |
173 | IResultSet, |
174 | @@ -74,7 +73,9 @@ |
175 | SQLBase, |
176 | sqlvalues, |
177 | ) |
178 | -from canonical.launchpad.components.decoratedresultset import DecoratedResultSet |
179 | +from canonical.launchpad.components.decoratedresultset import ( |
180 | + DecoratedResultSet, |
181 | + ) |
182 | from canonical.launchpad.helpers import shortlist |
183 | from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities |
184 | from canonical.launchpad.interfaces.lpstorm import IStore |
185 | @@ -90,7 +91,6 @@ |
186 | ILaunchBag, |
187 | IStoreSelector, |
188 | MAIN_STORE, |
189 | - SLAVE_FLAVOR, |
190 | ) |
191 | from lp.app.errors import NotFoundError |
192 | from lp.bugs.interfaces.bug import IBugSet |
193 | @@ -157,15 +157,16 @@ |
194 | from lp.soyuz.model.publishing import SourcePackagePublishingHistory |
195 | from lp.soyuz.model.sourcepackagerelease import SourcePackageRelease |
196 | |
197 | - |
198 | -debbugsseveritymap = {None: BugTaskImportance.UNDECIDED, |
199 | - 'wishlist': BugTaskImportance.WISHLIST, |
200 | - 'minor': BugTaskImportance.LOW, |
201 | - 'normal': BugTaskImportance.MEDIUM, |
202 | - 'important': BugTaskImportance.HIGH, |
203 | - 'serious': BugTaskImportance.HIGH, |
204 | - 'grave': BugTaskImportance.HIGH, |
205 | - 'critical': BugTaskImportance.CRITICAL} |
206 | +debbugsseveritymap = { |
207 | + None: BugTaskImportance.UNDECIDED, |
208 | + 'wishlist': BugTaskImportance.WISHLIST, |
209 | + 'minor': BugTaskImportance.LOW, |
210 | + 'normal': BugTaskImportance.MEDIUM, |
211 | + 'important': BugTaskImportance.HIGH, |
212 | + 'serious': BugTaskImportance.HIGH, |
213 | + 'grave': BugTaskImportance.HIGH, |
214 | + 'critical': BugTaskImportance.CRITICAL, |
215 | + } |
216 | |
217 | |
218 | def bugtask_sort_key(bugtask): |
219 | @@ -209,6 +210,7 @@ |
220 | bugtask.bug.id, distribution_name, product_name, productseries_name, |
221 | distroseries_name, sourcepackage_name) |
222 | |
223 | + |
224 | def get_related_bugtasks_search_params(user, context, **kwargs): |
225 | """Returns a list of `BugTaskSearchParams` which can be used to |
226 | search for all tasks related to a user given by `context`. |
227 | @@ -255,7 +257,9 @@ |
228 | |
229 | class BugTaskDelta: |
230 | """See `IBugTaskDelta`.""" |
231 | + |
232 | implements(IBugTaskDelta) |
233 | + |
234 | def __init__(self, bugtask, status=None, importance=None, |
235 | assignee=None, milestone=None, statusexplanation=None, |
236 | bugwatch=None, target=None): |
237 | @@ -461,7 +465,7 @@ |
238 | 'productseriesID': IProductSeriesSet, |
239 | 'sourcepackagenameID': ISourcePackageNameSet, |
240 | 'distributionID': IDistributionSet, |
241 | - 'distroseriesID': IDistroSeriesSet |
242 | + 'distroseriesID': IDistroSeriesSet, |
243 | }[attr] |
244 | if value is None: |
245 | target_params[attr[:-2]] = None |
246 | @@ -477,6 +481,7 @@ |
247 | |
248 | class PassthroughValue: |
249 | """A wrapper to allow setting values on conjoined bug tasks.""" |
250 | + |
251 | def __init__(self, value): |
252 | self.value = value |
253 | |
254 | @@ -543,7 +548,7 @@ |
255 | "date_closed", "date_incomplete", "date_left_new", |
256 | "date_triaged", "date_fix_committed", "date_fix_released", |
257 | "date_left_closed") |
258 | - _NON_CONJOINED_STATUSES = (BugTaskStatus.WONTFIX,) |
259 | + _NON_CONJOINED_STATUSES = (BugTaskStatus.WONTFIX, ) |
260 | |
261 | bug = ForeignKey(dbName='bug', foreignKey='Bug', notNull=True) |
262 | product = ForeignKey( |
263 | @@ -589,7 +594,7 @@ |
264 | notNull=False, default=None) |
265 | date_assigned = UtcDateTimeCol(notNull=False, default=None, |
266 | storm_validator=validate_conjoined_attribute) |
267 | - datecreated = UtcDateTimeCol(notNull=False, default=UTC_NOW) |
268 | + datecreated = UtcDateTimeCol(notNull=False, default=UTC_NOW) |
269 | date_confirmed = UtcDateTimeCol(notNull=False, default=None, |
270 | storm_validator=validate_conjoined_attribute) |
271 | date_inprogress = UtcDateTimeCol(notNull=False, default=None, |
272 | @@ -1009,10 +1014,10 @@ |
273 | if new_status < BugTaskStatus.FIXRELEASED: |
274 | self.date_fix_released = None |
275 | |
276 | - def userCanSetAnyAssignee(self, user): |
277 | - """See `IBugTask`.""" |
278 | + def _userCanSetAssignee(self, user): |
279 | + """Used by methods to check if user can assign or unassign bugtask.""" |
280 | celebrities = getUtility(ILaunchpadCelebrities) |
281 | - return user is not None and ( |
282 | + return ( |
283 | user.inTeam(self.pillar.bug_supervisor) or |
284 | user.inTeam(self.pillar.owner) or |
285 | user.inTeam(self.pillar.driver) or |
286 | @@ -1023,6 +1028,15 @@ |
287 | user.inTeam(celebrities.admin) |
288 | or user == celebrities.bug_importer) |
289 | |
290 | + def userCanSetAnyAssignee(self, user): |
291 | + """See `IBugTask`.""" |
292 | + if user is None: |
293 | + return False |
294 | + elif self.pillar.bug_supervisor is None: |
295 | + return True |
296 | + else: |
297 | + return self._userCanSetAssignee(user) |
298 | + |
299 | def userCanUnassign(self, user): |
300 | """True if user can set the assignee to None. |
301 | |
302 | @@ -1031,7 +1045,7 @@ |
303 | Launchpad admins can always unassign. |
304 | """ |
305 | return user is not None and ( |
306 | - user.inTeam(self.assignee) or self.userCanSetAnyAssignee(user)) |
307 | + user.inTeam(self.assignee) or self._userCanSetAssignee(user)) |
308 | |
309 | def canTransitionToAssignee(self, assignee): |
310 | """See `IBugTask`.""" |
311 | @@ -1162,7 +1176,7 @@ |
312 | component_name = component.name |
313 | |
314 | if IUpstreamBugTask.providedBy(self): |
315 | - header_value = 'product=%s;' % self.target.name |
316 | + header_value = 'product=%s;' % self.target.name |
317 | elif IProductSeriesBugTask.providedBy(self): |
318 | header_value = 'product=%s; productseries=%s;' % ( |
319 | self.productseries.product.name, self.productseries.name) |
320 | @@ -1311,7 +1325,7 @@ |
321 | |
322 | def _nocache_bug_decorator(obj): |
323 | """A pass through decorator for consistency. |
324 | - |
325 | + |
326 | :seealso: get_bug_privacy_filter_with_decorator |
327 | """ |
328 | return obj |
329 | @@ -1723,7 +1737,7 @@ |
330 | sqlvalues(personid=params.subscriber.id)) |
331 | |
332 | if params.structural_subscriber is not None: |
333 | - structural_subscriber_clause = ( """BugTask.id IN ( |
334 | + structural_subscriber_clause = ("""BugTask.id IN ( |
335 | SELECT BugTask.id FROM BugTask, StructuralSubscription |
336 | WHERE BugTask.product = StructuralSubscription.product |
337 | AND StructuralSubscription.subscriber = %(personid)s |
338 | @@ -2161,7 +2175,7 @@ |
339 | |
340 | def search(self, params, *args, **kwargs): |
341 | """See `IBugTaskSet`. |
342 | - |
343 | + |
344 | :param _noprejoins: Private internal parameter to BugTaskSet which |
345 | disables all use of prejoins : consolidated from code paths that |
346 | claim they were inefficient and unwanted. |
347 | @@ -2171,7 +2185,8 @@ |
348 | from lp.bugs.model.bug import Bug |
349 | _noprejoins = kwargs.get('_noprejoins', False) |
350 | store = IStore(BugTask) |
351 | - query, clauseTables, orderby, bugtask_decorator = self.buildQuery(params) |
352 | + query, clauseTables, orderby, bugtask_decorator = self.buildQuery( |
353 | + params) |
354 | if len(args) == 0: |
355 | if _noprejoins: |
356 | resultset = store.find(BugTask, |
357 | @@ -2228,8 +2243,8 @@ |
358 | return bugtask |
359 | |
360 | # Build up the joins. |
361 | - # TODO: implement _noprejoins for this code path: as of 20100818 it has |
362 | - # been silently disabled because clients of the API were setting |
363 | + # TODO: implement _noprejoins for this code path: as of 20100818 it |
364 | + # has been silently disabled because clients of the API were setting |
365 | # prejoins=[] which had no effect; this TODO simply notes the reality |
366 | # already existing when it was added. |
367 | joins = Alias(result._get_select(), "BugTask") |
368 | @@ -2279,9 +2294,10 @@ |
369 | # Import here because of cyclic references. |
370 | from lp.registry.model.milestone import ( |
371 | Milestone, milestone_sort_key) |
372 | - # We need the store that was used, we have no objects to key off of |
373 | - # other than the search result, and Store.of() doesn't currently |
374 | - # work on result sets. Additionally it may be a DecoratedResultSet. |
375 | + # We need the store that was used, we have no objects to key off |
376 | + # of other than the search result, and Store.of() doesn't |
377 | + # currently work on result sets. Additionally it may be a |
378 | + # DecoratedResultSet. |
379 | if zope_isinstance(search_results, DecoratedResultSet): |
380 | store = removeSecurityProxy(search_results).result_set._store |
381 | else: |
382 | |
383 | === modified file 'lib/lp/bugs/stories/bugtask-management/xx-change-assignee.txt' |
384 | --- lib/lp/bugs/stories/bugtask-management/xx-change-assignee.txt 2010-05-25 14:50:42 +0000 |
385 | +++ lib/lp/bugs/stories/bugtask-management/xx-change-assignee.txt 2010-08-31 18:56:47 +0000 |
386 | @@ -90,7 +90,19 @@ |
387 | |
388 | == Bug task assignment by regular users == |
389 | |
390 | -Regular users can only set themselves and their teams as assignees. |
391 | +Regular users can only set themselves and their teams as assignees if |
392 | +there is a bug supervisor established for a project. |
393 | + |
394 | +To demonstrate, let's first set a bug supervisor for the jokosher |
395 | +project used in these tests. |
396 | + |
397 | + >>> login('foo.bar@canonical.com') |
398 | + >>> from lp.registry.interfaces.product import IProductSet |
399 | + >>> jokosher = getUtility(IProductSet).getByName('jokosher') |
400 | + >>> foobar = getUtility(IPersonSet).getByName('name16') |
401 | + >>> jokosher.setBugSupervisor(foobar, foobar) |
402 | + >>> logout() |
403 | + |
404 | To avoid any confusion, the option to assign somebody else is only |
405 | shown if the user has sufficient privileges to assign anybody or if |
406 | the user is a member of at least one team. no-priv is no a member of |
407 | |
408 | === modified file 'lib/lp/bugs/tests/test_bugtask.py' |
409 | --- lib/lp/bugs/tests/test_bugtask.py 2010-08-26 18:57:35 +0000 |
410 | +++ lib/lp/bugs/tests/test_bugtask.py 2010-08-31 18:56:47 +0000 |
411 | @@ -25,7 +25,6 @@ |
412 | BugTaskImportance, |
413 | BugTaskSearchParams, |
414 | BugTaskStatus, |
415 | - IBugTaskSet, |
416 | ) |
417 | from lp.bugs.model.bugtask import build_tag_search_clause |
418 | from lp.hardwaredb.interfaces.hwdb import ( |
419 | @@ -541,15 +540,9 @@ |
420 | self.regular_user = self.factory.makePerson() |
421 | |
422 | login_person(self.target_owner_member) |
423 | + # Target and bug supervisor creation are deferred to sub-classes. |
424 | self.makeTarget() |
425 | - |
426 | - self.supervisor_team = self.factory.makeTeam( |
427 | - owner=self.target_owner_member) |
428 | - self.supervisor_member = self.factory.makePerson() |
429 | - self.supervisor_team.addMember( |
430 | - self.supervisor_member, self.target_owner_member) |
431 | - self.target.setBugSupervisor( |
432 | - self.supervisor_team, self.target_owner_member) |
433 | + self.setBugSupervisor() |
434 | |
435 | self.driver_team = self.factory.makeTeam( |
436 | owner=self.target_owner_member) |
437 | @@ -582,6 +575,29 @@ |
438 | """ |
439 | raise NotImplementedError(self.makeTarget) |
440 | |
441 | + def setBugSupervisor(self): |
442 | + """Set bug supervisor variables. |
443 | + |
444 | + This is the standard interface for sub-classes, but this |
445 | + method should return _setBugSupervisorData or |
446 | + _setBugSupervisorDataNone depending on what is required. |
447 | + """ |
448 | + raise NotImplementedError(self.setBugSupervisor) |
449 | + |
450 | + def _setBugSupervisorData(self): |
451 | + """Helper function used by sub-classes to setup bug supervisors.""" |
452 | + self.supervisor_team = self.factory.makeTeam( |
453 | + owner=self.target_owner_member) |
454 | + self.supervisor_member = self.factory.makePerson() |
455 | + self.supervisor_team.addMember( |
456 | + self.supervisor_member, self.target_owner_member) |
457 | + self.target.setBugSupervisor( |
458 | + self.supervisor_team, self.target_owner_member) |
459 | + |
460 | + def _setBugSupervisorDataNone(self): |
461 | + """Helper for sub-classes to work around setting a bug supervisor.""" |
462 | + self.supervisor_member = None |
463 | + |
464 | def test_userCanSetAnyAssignee_anonymous_user(self): |
465 | # Anonymous users cannot set anybody as an assignee. |
466 | login(ANONYMOUS) |
467 | @@ -595,12 +611,20 @@ |
468 | self.assertFalse(self.series_bugtask.userCanUnassign(None)) |
469 | |
470 | def test_userCanSetAnyAssignee_regular_user(self): |
471 | - # Ordinary users cannot set others as an assignee. |
472 | + # If we have a bug supervisor, check that regular user cannot |
473 | + # assign to someone else. Otherwise, the regular user should |
474 | + # be able to assign to anyone. |
475 | login_person(self.regular_user) |
476 | - self.assertFalse( |
477 | - self.target_bugtask.userCanSetAnyAssignee(self.regular_user)) |
478 | - self.assertFalse( |
479 | - self.series_bugtask.userCanSetAnyAssignee(self.regular_user)) |
480 | + if self.supervisor_member is not None: |
481 | + self.assertFalse( |
482 | + self.target_bugtask.userCanSetAnyAssignee(self.regular_user)) |
483 | + self.assertFalse( |
484 | + self.series_bugtask.userCanSetAnyAssignee(self.regular_user)) |
485 | + else: |
486 | + self.assertTrue( |
487 | + self.target_bugtask.userCanSetAnyAssignee(self.regular_user)) |
488 | + self.assertTrue( |
489 | + self.series_bugtask.userCanSetAnyAssignee(self.regular_user)) |
490 | |
491 | def test_userCanUnassign_regular_user(self): |
492 | # Ordinary users can unassign themselves... |
493 | @@ -640,19 +664,23 @@ |
494 | |
495 | def test_userCanSetAnyAssignee_bug_supervisor(self): |
496 | # A bug supervisor can assign anybody. |
497 | - login_person(self.supervisor_member) |
498 | - self.assertTrue( |
499 | - self.target_bugtask.userCanSetAnyAssignee(self.supervisor_member)) |
500 | - self.assertTrue( |
501 | - self.series_bugtask.userCanSetAnyAssignee(self.supervisor_member)) |
502 | + if self.supervisor_member is not None: |
503 | + login_person(self.supervisor_member) |
504 | + self.assertTrue( |
505 | + self.target_bugtask.userCanSetAnyAssignee( |
506 | + self.supervisor_member)) |
507 | + self.assertTrue( |
508 | + self.series_bugtask.userCanSetAnyAssignee( |
509 | + self.supervisor_member)) |
510 | |
511 | def test_userCanUnassign_bug_supervisor(self): |
512 | # A bug supervisor can unassign anybody. |
513 | - login_person(self.supervisor_member) |
514 | - self.assertTrue( |
515 | - self.target_bugtask.userCanUnassign(self.supervisor_member)) |
516 | - self.assertTrue( |
517 | - self.series_bugtask.userCanUnassign(self.supervisor_member)) |
518 | + if self.supervisor_member is not None: |
519 | + login_person(self.supervisor_member) |
520 | + self.assertTrue( |
521 | + self.target_bugtask.userCanUnassign(self.supervisor_member)) |
522 | + self.assertTrue( |
523 | + self.series_bugtask.userCanUnassign(self.supervisor_member)) |
524 | |
525 | def test_userCanSetAnyAssignee_driver(self): |
526 | # A project driver can assign anybody. |
527 | @@ -676,10 +704,16 @@ |
528 | self.assertTrue( |
529 | self.series_bugtask.userCanSetAnyAssignee( |
530 | self.series_driver_member)) |
531 | - # But he cannot assign anybody to bug tasks of the main target. |
532 | - self.assertFalse( |
533 | - self.target_bugtask.userCanSetAnyAssignee( |
534 | - self.series_driver_member)) |
535 | + if self.supervisor_member is not None: |
536 | + # But he cannot assign anybody to bug tasks of the main target... |
537 | + self.assertFalse( |
538 | + self.target_bugtask.userCanSetAnyAssignee( |
539 | + self.series_driver_member)) |
540 | + else: |
541 | + # ...unless a bug supervisor is not set. |
542 | + self.assertTrue( |
543 | + self.target_bugtask.userCanSetAnyAssignee( |
544 | + self.series_driver_member)) |
545 | |
546 | def test_userCanUnassign_series_driver(self): |
547 | # The target owner can unassign anybody from series bug tasks... |
548 | @@ -733,6 +767,23 @@ |
549 | self.target = self.factory.makeProduct(owner=self.target_owner_team) |
550 | self.series = self.factory.makeProductSeries(self.target) |
551 | |
552 | + def setBugSupervisor(self): |
553 | + """Establish a bug supervisor for this target.""" |
554 | + self._setBugSupervisorData() |
555 | + |
556 | + |
557 | +class TestProductNoBugSupervisorBugTaskPermissionsToSetAssignee( |
558 | + TestBugTaskPermissionsToSetAssigneeMixin, TestCaseWithFactory): |
559 | + |
560 | + def makeTarget(self): |
561 | + """Create a product and a product series without a bug supervisor.""" |
562 | + self.target = self.factory.makeProduct(owner=self.target_owner_team) |
563 | + self.series = self.factory.makeProductSeries(self.target) |
564 | + |
565 | + def setBugSupervisor(self): |
566 | + """Set bug supervisor to None.""" |
567 | + self._setBugSupervisorDataNone() |
568 | + |
569 | |
570 | class TestDistributionBugTaskPermissionsToSetAssignee( |
571 | TestBugTaskPermissionsToSetAssigneeMixin, TestCaseWithFactory): |
572 | @@ -743,6 +794,24 @@ |
573 | owner=self.target_owner_team) |
574 | self.series = self.factory.makeDistroSeries(self.target) |
575 | |
576 | + def setBugSupervisor(self): |
577 | + """Set bug supervisor to None.""" |
578 | + self._setBugSupervisorData() |
579 | + |
580 | + |
581 | +class TestDistributionNoBugSupervisorBugTaskPermissionsToSetAssignee( |
582 | + TestBugTaskPermissionsToSetAssigneeMixin, TestCaseWithFactory): |
583 | + |
584 | + def makeTarget(self): |
585 | + """Create a distribution and a distroseries.""" |
586 | + self.target = self.factory.makeDistribution( |
587 | + owner=self.target_owner_team) |
588 | + self.series = self.factory.makeDistroSeries(self.target) |
589 | + |
590 | + def setBugSupervisor(self): |
591 | + """Establish a bug supervisor for this target.""" |
592 | + self._setBugSupervisorDataNone() |
593 | + |
594 | |
595 | class TestBugTaskSearch(TestCaseWithFactory): |
596 | |
597 | @@ -814,11 +883,11 @@ |
598 | person = self.login() |
599 | self.factory.makeBug(product=target, private=True, owner=person) |
600 | self.factory.makeBug(product=target, private=True, owner=person) |
601 | - # Search style and parameters taken from the milestone index view where |
602 | - # the issue was discovered. |
603 | + # Search style and parameters taken from the milestone index view |
604 | + # where the issue was discovered. |
605 | login_person(person) |
606 | - tasks = target.searchTasks(BugTaskSearchParams(person, omit_dupes=True, |
607 | - orderby=['status', '-importance', 'id'])) |
608 | + tasks = target.searchTasks(BugTaskSearchParams( |
609 | + person, omit_dupes=True, orderby=['status', '-importance', 'id'])) |
610 | # We must be finding the bugs. |
611 | self.assertEqual(2, tasks.count()) |
612 | # Cache in the storm cache the account->person lookup so its not |
613 | @@ -828,7 +897,7 @@ |
614 | # allow access to getConjoinedMaster attribute - an attribute that |
615 | # triggers a permission check (nb: id does not trigger such a check) |
616 | self.assertStatementCount(1, |
617 | - lambda:[task.getConjoinedMaster for task in tasks]) |
618 | + lambda: [task.getConjoinedMaster for task in tasks]) |
619 | |
620 | def test_omit_targeted_default_is_false(self): |
621 | # The default value of omit_targeted is false so bugs targeted |
Hi Deryck,
Thanks for making this fix rolling back some restrictions.
In userCanSetAnyAs signee, I think the test would read better if you factored out the test for 'user is None' and return False immediately. So I'm suggesting (I hope formatting is preserved)
def userCanSetAnyAs signee( self, user): ILaunchpadCeleb rities) bug_supervisor is None:
user. inTeam( self.pillar. bug_supervisor) or
user. inTeam( self.pillar. owner) or
user. inTeam( self.pillar. driver) or
(self. distroseries is not None and
user. inTeam( self.distroseri es.driver) ) or
(self. productseries is not None and
user. inTeam( self.productser ies.driver) ) or
user. inTeam( celebrities. admin) bug_importer)
"""See `IBugTask`."""
celebrities = getUtility(
if user is None:
return False
elif self.pillar.
return True
else:
return (
or user == celebrities.
The technique you provided in setBugSupervisor of having the base class provide two alternatives for the subclasses to choose is very interesting. I've never seen that before.