~launchpad-pqm/launchpad/devel

« back to all changes in this revision

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

[r=danilo][bug=878260] macro
        bugtarget-macros-search/simple-search-form: Show a title;
        drop the 'order by' widget; CSS changes.

Show diffs side-by-side

added added

removed removed

Lines of Context:
29
29
import re
30
30
 
31
31
from lazr.enum import BaseItem
32
 
from lazr.lifecycle.event import (
33
 
    ObjectDeletedEvent,
34
 
    ObjectModifiedEvent,
35
 
    )
 
32
from lazr.lifecycle.event import ObjectModifiedEvent
36
33
from lazr.lifecycle.snapshot import Snapshot
37
34
import pytz
38
35
from sqlobject import (
117
114
    BugTaskSearchParams,
118
115
    BugTaskStatus,
119
116
    BugTaskStatusSearch,
120
 
    CannotDeleteBugtask,
121
117
    DB_INCOMPLETE_BUGTASK_STATUSES,
122
118
    DB_UNRESOLVED_BUGTASK_STATUSES,
123
119
    get_bugtask_status,
624
620
        """
625
621
        return self._status in RESOLVED_BUGTASK_STATUSES
626
622
 
627
 
    def canBeDeleted(self):
628
 
        num_bugtasks = Store.of(self).find(
629
 
            BugTask, bug=self.bug).count()
630
 
 
631
 
        return num_bugtasks > 1
632
 
 
633
 
    def delete(self, who=None):
634
 
        """See `IBugTask`."""
635
 
        if who is None:
636
 
            who = getUtility(ILaunchBag).user
637
 
 
638
 
        if not self.canBeDeleted():
639
 
            raise CannotDeleteBugtask(
640
 
                "Cannot delete bugtask: %s" % self.title)
641
 
        bug = self.bug
642
 
        target = self.target
643
 
        notify(ObjectDeletedEvent(self, who))
644
 
        self.destroySelf()
645
 
        del get_property_cache(bug).bugtasks
646
 
 
647
 
        # When a task is deleted the bug's heat needs to be recalculated.
648
 
        target.recalculateBugHeatCache()
649
 
 
650
623
    def findSimilarBugs(self, user, limit=10):
651
624
        """See `IBugTask`."""
652
625
        if self.product is not None:
1753
1726
            summary, Bug, ' AND '.join(constraint_clauses), ['BugTask'])
1754
1727
        return self.search(search_params, _noprejoins=True)
1755
1728
 
1756
 
    @classmethod
1757
 
    def _buildStatusClause(cls, status):
 
1729
    def _buildStatusClause(self, status):
1758
1730
        """Return the SQL query fragment for search by status.
1759
1731
 
1760
1732
        Called from `buildQuery` or recursively."""
1761
1733
        if zope_isinstance(status, any):
1762
 
            values = list(status.query_values)
1763
 
            # Since INCOMPLETE isn't stored as a single value we need to
1764
 
            # expand it before generating the SQL.
1765
 
            if BugTaskStatus.INCOMPLETE in values:
1766
 
                values.remove(BugTaskStatus.INCOMPLETE)
1767
 
                values.extend(DB_INCOMPLETE_BUGTASK_STATUSES)
1768
 
            return '(BugTask.status {0})'.format(
1769
 
                search_value_to_where_condition(any(*values)))
 
1734
            return '(' + ' OR '.join(
 
1735
                self._buildStatusClause(dbitem)
 
1736
                for dbitem
 
1737
                in status.query_values) + ')'
1770
1738
        elif zope_isinstance(status, not_equals):
1771
 
            return '(NOT {0})'.format(cls._buildStatusClause(status.value))
 
1739
            return '(NOT %s)' % self._buildStatusClause(status.value)
1772
1740
        elif zope_isinstance(status, BaseItem):
1773
 
            # INCOMPLETE is not stored in the DB, instead one of
1774
 
            # DB_INCOMPLETE_BUGTASK_STATUSES is stored, so any request to
1775
 
            # search for INCOMPLETE should instead search for those values.
1776
 
            if status == BugTaskStatus.INCOMPLETE:
1777
 
                return '(BugTask.status {0})'.format(
1778
 
                    search_value_to_where_condition(
1779
 
                        any(*DB_INCOMPLETE_BUGTASK_STATUSES)))
 
1741
            incomplete_response = (
 
1742
                status == BugTaskStatus.INCOMPLETE)
 
1743
            with_response = (
 
1744
                status == BugTaskStatusSearch.INCOMPLETE_WITH_RESPONSE)
 
1745
            without_response = (
 
1746
                status == BugTaskStatusSearch.INCOMPLETE_WITHOUT_RESPONSE)
 
1747
            # TODO: bug 759467 tracks the migration of INCOMPLETE in the db to
 
1748
            # INCOMPLETE_WITH_RESPONSE and INCOMPLETE_WITHOUT_RESPONSE. When
 
1749
            # the migration is complete, we can convert status lookups to a
 
1750
            # simple IN clause.
 
1751
            if with_response or without_response:
 
1752
                if with_response:
 
1753
                    return """(
 
1754
                        BugTask.status = %s OR
 
1755
                        (BugTask.status = %s
 
1756
                        AND (Bug.date_last_message IS NOT NULL
 
1757
                             AND BugTask.date_incomplete <=
 
1758
                                 Bug.date_last_message)))
 
1759
                        """ % sqlvalues(
 
1760
                            BugTaskStatusSearch.INCOMPLETE_WITH_RESPONSE,
 
1761
                            BugTaskStatus.INCOMPLETE)
 
1762
                elif without_response:
 
1763
                    return """(
 
1764
                        BugTask.status = %s OR
 
1765
                        (BugTask.status = %s
 
1766
                        AND (Bug.date_last_message IS NULL
 
1767
                             OR BugTask.date_incomplete >
 
1768
                                Bug.date_last_message)))
 
1769
                        """ % sqlvalues(
 
1770
                            BugTaskStatusSearch.INCOMPLETE_WITHOUT_RESPONSE,
 
1771
                            BugTaskStatus.INCOMPLETE)
 
1772
                assert with_response != without_response
 
1773
            elif incomplete_response:
 
1774
                # search for any of INCOMPLETE (being migrated from),
 
1775
                # INCOMPLETE_WITH_RESPONSE or INCOMPLETE_WITHOUT_RESPONSE
 
1776
                return 'BugTask.status %s' % search_value_to_where_condition(
 
1777
                    any(BugTaskStatus.INCOMPLETE,
 
1778
                        BugTaskStatusSearch.INCOMPLETE_WITH_RESPONSE,
 
1779
                        BugTaskStatusSearch.INCOMPLETE_WITHOUT_RESPONSE))
1780
1780
            else:
1781
1781
                return '(BugTask.status = %s)' % sqlvalues(status)
1782
1782
        else:
1783
 
            raise ValueError('Unrecognized status value: %r' % (status,))
 
1783
            raise AssertionError(
 
1784
                'Unrecognized status value: %s' % repr(status))
1784
1785
 
1785
1786
    def _buildExcludeConjoinedClause(self, milestone):
1786
1787
        """Exclude bugtasks with a conjoined master.