Merge lp:~deryck/launchpad/less-restrictive-assign-someone-else-603281 into lp:launchpad

Proposed by Deryck Hodge
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
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.userCanSetAnyAssignee to get this working. Since the web UI uses
this method, nothing had to be changed with the JS widget.

To post a comment you must log in.
Revision history for this message
Brad Crittenden (bac) wrote :

Hi Deryck,

Thanks for making this fix rolling back some restrictions.

In userCanSetAnyAssignee,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 userCanSetAnyAssignee(self, user):
        """See `IBugTask`."""
        celebrities = getUtility(ILaunchpadCelebrities)
        if user is None:
            return False
        elif self.pillar.bug_supervisor is None:
            return True
        else:
            return (
                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.distroseries.driver)) or
                (self.productseries is not None and
                 user.inTeam(self.productseries.driver)) or
                user.inTeam(celebrities.admin)
                or user == celebrities.bug_importer)

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.

review: Approve
Revision history for this message
Deryck Hodge (deryck) wrote :

Hi, bac.

Thanks for the review! I like your suggestion and will do that.

Cheers,
deryck

Revision history for this message
Robert Collins (lifeless) wrote :

BTW:

               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.distroseries.driver)) or
               (self.productseries is not None and
                user.inTeam(self.productseries.driver)) or
               user.inTeam(celebrities.admin)
               or user == celebrities.bug_importer)

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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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