~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to lib/lp/bugs/interfaces/bugtask.py

  • Committer: Launchpad Patch Queue Manager
  • Date: 2011-06-25 08:55:37 UTC
  • mfrom: (13287.1.8 bug-800652)
  • Revision ID: launchpad@pqm.canonical.com-20110625085537-moikyoo2pe98zs7r
[r=jcsackett, julian-edwards][bug=800634,
        800652] Enable and display overrides on sync package uploads.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright 2009-2011 Canonical Ltd.  This software is licensed under the
 
1
# Copyright 2009-2010 Canonical Ltd.  This software is licensed under the
2
2
# GNU Affero General Public License version 3 (see the file LICENSE).
3
3
 
4
4
# pylint: disable-msg=E0211,E0213,E0602
17
17
    'BugTaskStatus',
18
18
    'BugTaskStatusSearch',
19
19
    'BugTaskStatusSearchDisplay',
20
 
    'CannotDeleteBugtask',
21
 
    'DB_INCOMPLETE_BUGTASK_STATUSES',
22
 
    'DB_UNRESOLVED_BUGTASK_STATUSES',
23
20
    'DEFAULT_SEARCH_BUGTASK_STATUSES_FOR_DISPLAY',
24
 
    'get_bugtask_status',
25
21
    'IAddBugTaskForm',
26
22
    'IAddBugTaskWithProductCreationForm',
27
23
    'IBugTask',
28
 
    'IBugTaskDelete',
29
24
    'IBugTaskDelta',
30
25
    'IBugTaskSearch',
31
26
    'IBugTaskSet',
32
27
    'ICreateQuestionFromBugTaskForm',
 
28
    'IDistroBugTask',
 
29
    'IDistroSeriesBugTask',
33
30
    'IFrontPageBugTaskSearch',
34
 
    'IllegalRelatedBugTasksParams',
35
 
    'IllegalTarget',
36
31
    'INominationsReviewTableBatchNavigator',
37
32
    'IPersonBugTaskSearch',
 
33
    'IProductSeriesBugTask',
38
34
    'IRemoveQuestionFromBugTaskForm',
 
35
    'IUpstreamBugTask',
39
36
    'IUpstreamProductBugTaskSearch',
40
 
    'normalize_bugtask_status',
 
37
    'IllegalRelatedBugTasksParams',
 
38
    'IllegalTarget',
41
39
    'RESOLVED_BUGTASK_STATUSES',
42
40
    'UNRESOLVED_BUGTASK_STATUSES',
43
41
    'UserCannotEditBugTaskAssignee',
60
58
    call_with,
61
59
    error_status,
62
60
    export_as_webservice_entry,
63
 
    export_destructor_operation,
64
61
    export_read_operation,
65
62
    export_write_operation,
66
63
    exported,
99
96
from zope.security.interfaces import Unauthorized
100
97
from zope.security.proxy import isinstance as zope_isinstance
101
98
 
102
 
from lp import _
103
 
from lp.app.interfaces.launchpad import IHasDateCreated
 
99
from canonical.launchpad import _
 
100
from canonical.launchpad.interfaces.launchpad import (
 
101
    IHasBug,
 
102
    IHasDateCreated,
 
103
    )
 
104
from canonical.launchpad.searchbuilder import (
 
105
    all,
 
106
    any,
 
107
    NULL,
 
108
    )
 
109
from canonical.launchpad.webapp.interfaces import ITableBatchNavigator
104
110
from lp.app.validators import LaunchpadValidationError
105
111
from lp.app.validators.name import name_validator
106
112
from lp.bugs.interfaces.bugwatch import (
109
115
    NoBugTrackerFound,
110
116
    UnrecognizedBugTrackerURL,
111
117
    )
112
 
from lp.bugs.interfaces.hasbug import IHasBug
113
118
from lp.services.fields import (
114
119
    BugField,
115
120
    PersonChoice,
118
123
    StrippedTextLine,
119
124
    Summary,
120
125
    )
121
 
from lp.services.searchbuilder import (
122
 
    all,
123
 
    any,
124
 
    NULL,
125
 
    )
126
 
from lp.services.webapp.interfaces import ITableBatchNavigator
127
126
from lp.soyuz.interfaces.component import IComponent
128
127
 
129
128
 
201
200
        this product or source package.
202
201
        """)
203
202
 
204
 
    # INCOMPLETE is never actually stored now: INCOMPLETE_WITH_RESPONSE and
205
 
    # INCOMPLETE_WITHOUT_RESPONSE are mapped to INCOMPLETE on read, and on
206
 
    # write INCOMPLETE is mapped to INCOMPLETE_WITHOUT_RESPONSE. This permits
207
 
    # An index on the INCOMPLETE_WITH*_RESPONSE queries that the webapp
208
 
    # generates.
209
203
    INCOMPLETE = DBItem(15, """
210
204
        Incomplete
211
205
 
279
273
        affected software.
280
274
        """)
281
275
 
 
276
    # DBItem values 35 and 40 are used by
 
277
    # BugTaskStatusSearch.INCOMPLETE_WITH_RESPONSE and
 
278
    # BugTaskStatusSearch.INCOMPLETE_WITHOUT_RESPONSE
 
279
 
282
280
    UNKNOWN = DBItem(999, """
283
281
        Unknown
284
282
 
293
291
    """
294
292
    use_template(BugTaskStatus, exclude=('UNKNOWN'))
295
293
 
296
 
    INCOMPLETE_WITH_RESPONSE = DBItem(13, """
 
294
    sort_order = (
 
295
        'NEW', 'INCOMPLETE_WITH_RESPONSE', 'INCOMPLETE_WITHOUT_RESPONSE',
 
296
        'INCOMPLETE', 'OPINION', 'INVALID', 'WONTFIX', 'EXPIRED',
 
297
        'CONFIRMED', 'TRIAGED', 'INPROGRESS', 'FIXCOMMITTED', 'FIXRELEASED')
 
298
 
 
299
    INCOMPLETE_WITH_RESPONSE = DBItem(35, """
297
300
        Incomplete (with response)
298
301
 
299
302
        This bug has new information since it was last marked
300
303
        as requiring a response.
301
304
        """)
302
305
 
303
 
    INCOMPLETE_WITHOUT_RESPONSE = DBItem(14, """
 
306
    INCOMPLETE_WITHOUT_RESPONSE = DBItem(40, """
304
307
        Incomplete (without response)
305
308
 
306
309
        This bug requires more information, but no additional
308
311
        """)
309
312
 
310
313
 
311
 
def get_bugtask_status(status_id):
312
 
    """Get a member of `BugTaskStatus` or `BugTaskStatusSearch` by value.
313
 
 
314
 
    `BugTaskStatus` and `BugTaskStatusSearch` intersect, but neither is a
315
 
    subset of the other, so this searches first in `BugTaskStatus` then in
316
 
    `BugTaskStatusSearch` for a member with the given ID.
317
 
    """
318
 
    try:
319
 
        return BugTaskStatus.items[status_id]
320
 
    except KeyError:
321
 
        return BugTaskStatusSearch.items[status_id]
322
 
 
323
 
 
324
 
def normalize_bugtask_status(status):
325
 
    """Normalize `status`.
326
 
 
327
 
    It might be a member of any of three related enums: `BugTaskStatus`,
328
 
    `BugTaskStatusSearch`, or `BugTaskStatusSearchDisplay`. This tries to
329
 
    normalize by value back to the first of those three enums in which the
330
 
    status appears.
331
 
    """
332
 
    try:
333
 
        return BugTaskStatus.items[status.value]
334
 
    except KeyError:
335
 
        return BugTaskStatusSearch.items[status.value]
336
 
 
337
 
 
338
314
class BugTagsSearchCombinator(EnumeratedType):
339
315
    """Bug Tags Search Combinator
340
316
 
399
375
    BugTaskStatus.INPROGRESS,
400
376
    BugTaskStatus.FIXCOMMITTED)
401
377
 
402
 
# Actual values stored in the DB:
403
 
DB_INCOMPLETE_BUGTASK_STATUSES = (
404
 
    BugTaskStatusSearch.INCOMPLETE_WITH_RESPONSE,
405
 
    BugTaskStatusSearch.INCOMPLETE_WITHOUT_RESPONSE,
406
 
    )
407
 
 
408
 
DB_UNRESOLVED_BUGTASK_STATUSES = (
409
 
    UNRESOLVED_BUGTASK_STATUSES +
410
 
    DB_INCOMPLETE_BUGTASK_STATUSES
411
 
    )
412
 
 
413
378
RESOLVED_BUGTASK_STATUSES = (
414
379
    BugTaskStatus.FIXRELEASED,
415
380
    BugTaskStatus.OPINION,
436
401
    for item in DEFAULT_SEARCH_BUGTASK_STATUSES]
437
402
 
438
403
 
439
 
@error_status(httplib.BAD_REQUEST)
440
 
class CannotDeleteBugtask(Exception):
441
 
    """The bugtask cannot be deleted.
442
 
 
443
 
    Raised when a user tries to delete a bugtask but the deletion cannot
444
 
    proceed because of a model constraint or other business rule violation.
445
 
    """
446
 
 
447
 
 
448
404
@error_status(httplib.UNAUTHORIZED)
449
405
class UserCannotEditBugTaskStatus(Unauthorized):
450
406
    """User not permitted to change status.
492
448
    in a search for related bug tasks"""
493
449
 
494
450
 
495
 
class IBugTaskDelete(Interface):
496
 
    """An interface for operations allowed with the Delete permission."""
497
 
    @export_destructor_operation()
498
 
    @call_with(who=REQUEST_USER)
499
 
    @operation_for_version('devel')
500
 
    def delete(who):
501
 
        """Delete this bugtask.
502
 
 
503
 
        :param who: the user who is removing the bugtask.
504
 
        :raises: CannotDeleteBugtask if the bugtask cannot be deleted due to a
505
 
            business rule or other model constraint.
506
 
        :raises: Unauthorized if the user does not have permission
507
 
            to delete the bugtask.
508
 
        """
509
 
 
510
 
 
511
 
class IBugTask(IHasDateCreated, IHasBug, IBugTaskDelete):
 
451
class IBugTask(IHasDateCreated, IHasBug):
512
452
    """A bug needing fixing in a particular product or package."""
513
453
    export_as_webservice_entry()
514
454
 
540
480
        schema=Interface))  # IMilestone
541
481
    milestoneID = Attribute('The id of the milestone.')
542
482
 
 
483
    # XXX kiko 2006-03-23:
543
484
    # The status and importance's vocabularies do not
544
485
    # contain an UNKNOWN item in bugtasks that aren't linked to a remote
545
486
    # bugwatch; this would be better described in a separate interface,
546
487
    # but adding a marker interface during initialization is expensive,
547
488
    # and adding it post-initialization is not trivial.
548
 
    # Note that status is a property because the model only exposes INCOMPLETE
549
 
    # but the DB stores INCOMPLETE_WITH_RESPONSE and
550
 
    # INCOMPLETE_WITHOUT_RESPONSE for query efficiency.
551
489
    status = exported(
552
490
        Choice(title=_('Status'), vocabulary=BugTaskStatus,
553
491
               default=BugTaskStatus.NEW, readonly=True))
554
 
    _status = Attribute('The actual status DB column used in queries.')
555
492
    importance = exported(
556
493
        Choice(title=_('Importance'), vocabulary=BugTaskImportance,
557
494
               default=BugTaskImportance.UNDECIDED, readonly=True))
 
495
    statusexplanation = Text(
 
496
        title=_("Status notes (optional)"), required=False)
558
497
    assignee = exported(
559
498
        PersonChoice(
560
499
            title=_('Assigned to'), required=False,
860
799
        value is set to None, date_assigned is also set to None.
861
800
        """
862
801
 
863
 
    def validateTransitionToTarget(target):
864
 
        """Check whether a transition to this target is legal.
865
 
 
866
 
        :raises IllegalTarget: if the new target is not allowed.
867
 
        """
868
 
 
869
802
    @mutator_for(target)
870
803
    @operation_parameters(
871
804
        target=copy_field(target))
899
832
    def getDelta(old_task):
900
833
        """Compute the delta from old_task to this task.
901
834
 
 
835
        old_task and this task are either both IDistroBugTask's or both
 
836
        IUpstreamBugTask's, otherwise a TypeError is raised.
 
837
 
902
838
        Returns an IBugTaskDelta or None if there were no changes between
903
839
        old_task and this task.
904
840
        """
911
847
        not a package task, returns None.
912
848
        """
913
849
 
914
 
    def userHasDriverPrivileges(user):
915
 
        """Does the user have driver privledges on the current bugtask?
916
 
 
917
 
        :return: A boolean.
918
 
        """
919
 
 
920
 
    def userHasBugSupervisorPrivileges(user):
921
 
        """Is the user a privledged one, allowed to changed details on a
922
 
        bug?
923
 
 
924
 
        :return: A boolean.
925
 
        """
 
850
    def userCanEditMilestone(user):
 
851
        """Can the user edit the Milestone field?"""
 
852
 
 
853
    def userCanEditImportance(user):
 
854
        """Can the user edit the Importance field?"""
926
855
 
927
856
 
928
857
# Set schemas that were impossible to specify during the definition of
993
922
    omit_targeted = Bool(
994
923
        title=_('Omit bugs targeted to a series'), required=False,
995
924
        default=True)
 
925
    statusexplanation = TextLine(
 
926
        title=_("Status notes"), required=False)
996
927
    has_patch = Bool(
997
928
        title=_('Show only bugs with patches available.'), required=False,
998
929
        default=False)
1021
952
        required=False)
1022
953
    has_cve = Bool(
1023
954
        title=_('Show only bugs associated with a CVE'), required=False)
1024
 
    structural_subscriber = Choice(
1025
 
        title=_('Structural Subscriber'), vocabulary='ValidPersonOrTeam',
1026
 
        description=_(
1027
 
            'Show only bugs in projects, series, distributions, and packages '
1028
 
            'that this person or team is subscribed to.'),
 
955
    bug_supervisor = Choice(
 
956
        title=_('Bug supervisor'), vocabulary='ValidPersonOrTeam',
 
957
        description=_('Show only bugs in packages this person or team '
 
958
                      'is subscribed to.'),
1029
959
        required=False)
1030
960
    bug_commenter = Choice(
1031
961
        title=_('Bug commenter'), vocabulary='ValidPersonOrTeam',
1102
1032
        required=False)
1103
1033
 
1104
1034
 
1105
 
class IFrontPageBugTaskSearch(IBugTaskSearch):
 
1035
class IFrontPageBugTaskSearch(IBugTaskSearchBase):
1106
1036
    """Additional search options for the front page of bugs."""
1107
1037
    scope = Choice(
1108
1038
        title=u"Search Scope", required=False,
1143
1073
        The value is a dict like {'old' : IPerson, 'new' : IPerson}, or None,
1144
1074
        if no change was made to the assignee.
1145
1075
        """)
 
1076
    statusexplanation = Attribute("The new value of the status notes.")
1146
1077
    bugwatch = Attribute("The bugwatch which governs this task.")
1147
1078
    milestone = Attribute("The milestone for which this task is scheduled.")
1148
1079
 
1149
1080
 
 
1081
class IUpstreamBugTask(IBugTask):
 
1082
    """A bug needing fixing in a product."""
 
1083
    # XXX Brad Bollenbach 2006-08-03 bugs=55089:
 
1084
    # This interface should be renamed.
 
1085
    product = Choice(title=_('Project'), required=True, vocabulary='Product')
 
1086
 
 
1087
 
 
1088
class IDistroBugTask(IBugTask):
 
1089
    """A bug needing fixing in a distribution, possibly a specific package."""
 
1090
    sourcepackagename = Choice(
 
1091
        title=_("Source Package Name"), required=False,
 
1092
        description=_("The source package in which the bug occurs. "
 
1093
        "Leave blank if you are not sure."),
 
1094
        vocabulary='SourcePackageName')
 
1095
    distribution = Choice(
 
1096
        title=_("Distribution"), required=True, vocabulary='Distribution')
 
1097
 
 
1098
 
 
1099
class IDistroSeriesBugTask(IBugTask):
 
1100
    """A bug needing fixing in a distrorelease, or a specific package."""
 
1101
    sourcepackagename = Choice(
 
1102
        title=_("Source Package Name"), required=True,
 
1103
        vocabulary='SourcePackageName')
 
1104
    distroseries = Choice(
 
1105
        title=_("Series"), required=True,
 
1106
        vocabulary='DistroSeries')
 
1107
 
 
1108
 
 
1109
class IProductSeriesBugTask(IBugTask):
 
1110
    """A bug needing fixing a productseries."""
 
1111
    productseries = Choice(
 
1112
        title=_("Series"), required=True,
 
1113
        vocabulary='ProductSeries')
 
1114
 
 
1115
 
1150
1116
class BugTaskSearchParams:
1151
1117
    """Encapsulates search parameters for BugTask.search()
1152
1118
 
1481
1447
class IBugTaskSet(Interface):
1482
1448
    """A utility to retrieving BugTasks."""
1483
1449
    title = Attribute('Title')
1484
 
    orderby_expression = Attribute(
1485
 
        "The SQL expression for a sort key")
1486
1450
 
1487
1451
    def get(task_id):
1488
1452
        """Retrieve a BugTask with the given id.
1556
1520
        :param params: the BugTaskSearchParams to search on.
1557
1521
        """
1558
1522
 
1559
 
    def countBugs(user, contexts, group_on):
1560
 
        """Count open bugs that match params, grouping by group_on.
1561
 
 
1562
 
        This serves results from the bugsummary fact table: it is fast but not
1563
 
        completely precise. See the bug summary documentation for more detail.
1564
 
 
1565
 
        :param user: The user to query on behalf of.
1566
 
        :param contexts: A list of contexts to search. Contexts must support
1567
 
            the IBugSummaryDimension interface.
 
1523
    def countBugs(params, group_on):
 
1524
        """Count bugs that match params, grouping by group_on.
 
1525
 
 
1526
        :param param: A BugTaskSearchParams object.
1568
1527
        :param group_on: The column(s) group on - .e.g (
1569
 
            BugSummary.distroseries_id, BugSummary.milestone_id) will cause
 
1528
            Bugtask.distroseriesID, BugTask.milestoneID) will cause
1570
1529
            grouping by distro series and then milestone.
1571
1530
        :return: A dict {group_instance: count, ...}
1572
1531
        """
1581
1540
        :return: A list of tuples containing (status_id, count).
1582
1541
        """
1583
1542
 
1584
 
    def createTask(bug, owner, target, status=None, importance=None,
1585
 
                   assignee=None, milestone=None):
 
1543
    def createTask(bug, product=None, productseries=None, distribution=None,
 
1544
                   distroseries=None, sourcepackagename=None, status=None,
 
1545
                   importance=None, assignee=None, owner=None,
 
1546
                   milestone=None):
1586
1547
        """Create a bug task on a bug and return it.
1587
1548
 
1588
1549
        If the bug is public, bug supervisors will be automatically
1590
1551
 
1591
1552
        If the bug has any accepted series nominations for a supplied
1592
1553
        distribution, series tasks will be created for them.
 
1554
 
 
1555
        Exactly one of product, distribution or distroseries must be provided.
1593
1556
        """
1594
1557
 
1595
1558
    def findExpirableBugTasks(min_days_old, user, bug=None, target=None,
1639
1602
        <user> is None, no private bugtasks will be returned.
1640
1603
        """
1641
1604
 
 
1605
    def getOrderByColumnDBName(col_name):
 
1606
        """Get the database name for col_name.
 
1607
 
 
1608
        If the col_name is unrecognized, a KeyError is raised.
 
1609
        """
 
1610
 
1642
1611
    def getBugCountsForPackages(user, packages):
1643
1612
        """Return open bug counts for the list of packages.
1644
1613
 
1664
1633
        The assignee and the assignee's validity are precached.
1665
1634
        """
1666
1635
 
1667
 
    def getBugTaskTargetMilestones(bugtasks):
 
1636
    def getBugTaskTargetMilestones(self, bugtasks, eager=False):
1668
1637
        """Get all the milestones for the selected bugtasks' targets."""
1669
1638
 
1670
1639
    open_bugtask_search = Attribute("A search returning open bugTasks.")