~launchpad-pqm/launchpad/devel

7675.916.98 by Henning Eggers
Merged db-stable at r10026 (recife roll-back) but without accepting the changes.
1
# Copyright 2009-2010 Canonical Ltd.  This software is licensed under the
8687.15.14 by Karl Fogel
Add the copyright header block to files under lib/lp/blueprints/.
2
# GNU Affero General Public License version 3 (see the file LICENSE).
3
4983.1.2 by Curtis Hovey
Added pylint exceptions to database classes.
4
# pylint: disable-msg=E0611,W0212
2344 by Canonical.com Patch Queue Manager
[not r=kiko] specification tracker
5
6
__metaclass__ = type
3847.2.25 by Mark Shuttleworth
Test fixes and object interface compliance
7
__all__ = [
8
    'HasSpecificationsMixin',
12579.2.6 by Tim Penhey
Move to module functions.
9
    'recursive_blocked_query',
10
    'recursive_dependent_query',
3847.2.25 by Mark Shuttleworth
Test fixes and object interface compliance
11
    'Specification',
12
    'SpecificationSet',
13
    ]
2344 by Canonical.com Patch Queue Manager
[not r=kiko] specification tracker
14
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
15
from lazr.lifecycle.event import (
16
    ObjectCreatedEvent,
17
    ObjectModifiedEvent,
18
    )
19
from lazr.lifecycle.objectdelta import ObjectDelta
20
from sqlobject import (
21
    BoolCol,
22
    ForeignKey,
23
    IntCol,
24
    SQLMultipleJoin,
25
    SQLRelatedJoin,
26
    StringCol,
27
    )
11474.3.1 by Robert Collins
Precache the is_person_valid status for specification assignments on Product and Distribution.
28
from storm.locals import (
29
    Desc,
30
    SQL,
31
    )
5821.16.5 by Francis J. Lacoste
Use Storm execute instead of cursor().
32
from storm.store import Store
12588.3.4 by Tim Penhey
Define the filter_bugtasks_by_context interface.
33
from zope.component import getUtility
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
34
from zope.event import notify
2344 by Canonical.com Patch Queue Manager
[not r=kiko] specification tracker
35
from zope.interface import implements
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
36
37
from canonical.database.constants import (
38
    DEFAULT,
39
    UTC_NOW,
40
    )
2344 by Canonical.com Patch Queue Manager
[not r=kiko] specification tracker
41
from canonical.database.datetimecol import UtcDateTimeCol
3691.373.5 by Christian Reis
Move DBSchema and Item into webapp.enum, and put EnumCol into canonical.database.enumcol
42
from canonical.database.enumcol import EnumCol
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
43
from canonical.database.sqlbase import (
44
    cursor,
45
    quote,
46
    SQLBase,
47
    sqlvalues,
48
    )
11474.3.1 by Robert Collins
Precache the is_person_valid status for specification assignments on Product and Distribution.
49
from canonical.launchpad.components.decoratedresultset import (
50
    DecoratedResultSet,
51
    )
3691.373.5 by Christian Reis
Move DBSchema and Item into webapp.enum, and put EnumCol into canonical.database.enumcol
52
from canonical.launchpad.helpers import (
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
53
    get_contact_email_addresses,
54
    )
13161.2.2 by Ian Booth
Add canBeUnsubscribedByUser and tests
55
from lp.app.errors import UserCannotUnsubscribePerson
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
56
from lp.blueprints.adapters import SpecificationDelta
11824.1.2 by Tim Penhey
Fix imports for SpecificationDefinitionStatus.
57
from lp.blueprints.enums import (
12498.3.2 by Curtis Hovey
Added NewSpecificationDefinitionStatus to define the set of enums that a new
58
    NewSpecificationDefinitionStatus,
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
59
    SpecificationDefinitionStatus,
60
    SpecificationFilter,
61
    SpecificationGoalStatus,
62
    SpecificationImplementationStatus,
63
    SpecificationLifecycleStatus,
64
    SpecificationPriority,
65
    SpecificationSort,
66
    )
11962.3.1 by Guilherme Salgado
merge james' safe-blueprints-model branch and solve conflicts
67
from lp.blueprints.errors import TargetAlreadyHasSpecification
11824.1.2 by Tim Penhey
Fix imports for SpecificationDefinitionStatus.
68
from lp.blueprints.interfaces.specification import (
69
    ISpecification,
70
    ISpecificationSet,
71
    )
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
72
from lp.blueprints.model.specificationbranch import SpecificationBranch
73
from lp.blueprints.model.specificationbug import SpecificationBug
8376.1.1 by Curtis Hovey
Migrated blueprint code to lp.blueprints. Resolved several circular import issues. Fixed specgraph 06-dependancies and 14-non-ascii-imagemap tests that fail on Jauny, but are fine on Hardy.
74
from lp.blueprints.model.specificationdependency import (
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
75
    SpecificationDependency,
76
    )
77
from lp.blueprints.model.specificationfeedback import SpecificationFeedback
8376.1.1 by Curtis Hovey
Migrated blueprint code to lp.blueprints. Resolved several circular import issues. Fixed specgraph 06-dependancies and 14-non-ascii-imagemap tests that fail on Jauny, but are fine on Hardy.
78
from lp.blueprints.model.specificationsubscription import (
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
79
    SpecificationSubscription,
80
    )
81
from lp.bugs.interfaces.buglink import IBugLinkTarget
12588.3.4 by Tim Penhey
Define the filter_bugtasks_by_context interface.
82
from lp.bugs.interfaces.bugtask import (
83
    BugTaskSearchParams,
84
    IBugTaskSet,
85
    )
12588.3.6 by Tim Penhey
Move the filter function to its own module to avoid circular dependencies.
86
from lp.bugs.interfaces.bugtaskfilter import filter_bugtasks_by_context
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
87
from lp.bugs.model.buglinktarget import BugLinkTargetMixin
11962.3.3 by Guilherme Salgado
Create a new setTarget method on ISpecification, use it in retarget() and protect both with launchpad.Edit. Also started splitting ISpecification into several interfaces to make it easyer to protect them with different permissions
88
from lp.registry.interfaces.distribution import IDistribution
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
89
from lp.registry.interfaces.distroseries import IDistroSeries
90
from lp.registry.interfaces.person import validate_public_person
91
from lp.registry.interfaces.productseries import IProductSeries
11962.3.3 by Guilherme Salgado
Create a new setTarget method on ISpecification, use it in retarget() and protect both with launchpad.Edit. Also started splitting ISpecification into several interfaces to make it easyer to protect them with different permissions
92
from lp.registry.interfaces.product import IProduct
13161.2.2 by Ian Booth
Add canBeUnsubscribedByUser and tests
93
from lp.services.propertycache import (
94
    cachedproperty,
95
    get_property_cache,
96
    )
13147.2.6 by Nigel Babu
There's already abstraction for this.
97
2736.1.22 by Mark Shuttleworth
karma categories in the db, and karma for specs
98
2344 by Canonical.com Patch Queue Manager
[not r=kiko] specification tracker
99
12579.2.6 by Tim Penhey
Move to module functions.
100
def recursive_blocked_query(spec):
101
    return """
102
        RECURSIVE blocked(id) AS (
103
            SELECT %s
104
        UNION
12579.2.8 by Tim Penhey
Tidy up the query, add the id to the repr, and sort the results.
105
            SELECT sd.specification
106
            FROM specificationdependency sd, blocked b
12579.2.6 by Tim Penhey
Move to module functions.
107
            WHERE sd.dependency = b.id
108
        )""" % spec.id
109
110
111
def recursive_dependent_query(spec):
112
    return """
113
        RECURSIVE dependencies(id) AS (
114
            SELECT %s
115
        UNION
12579.2.8 by Tim Penhey
Tidy up the query, add the id to the repr, and sort the results.
116
            SELECT sd.dependency
117
            FROM specificationdependency sd, dependencies d
12579.2.6 by Tim Penhey
Move to module functions.
118
            WHERE sd.specification = d.id
119
        )""" % spec.id
120
121
3691.109.23 by Francis J. Lacoste
* Define a BugLinkTargetMixin for base implementation of linkBug and unlinkBug.
122
class Specification(SQLBase, BugLinkTargetMixin):
2344 by Canonical.com Patch Queue Manager
[not r=kiko] specification tracker
123
    """See ISpecification."""
124
3691.109.19 by Francis J. Lacoste
Make Specification implement IBugLinkTarget
125
    implements(ISpecification, IBugLinkTarget)
2344 by Canonical.com Patch Queue Manager
[not r=kiko] specification tracker
126
4476.5.4 by Tom Berger
implementation and tests complete
127
    _defaultOrder = ['-priority', 'definition_status', 'name', 'id']
2344 by Canonical.com Patch Queue Manager
[not r=kiko] specification tracker
128
129
    # db field names
130
    name = StringCol(unique=True, notNull=True)
131
    title = StringCol(notNull=True)
132
    summary = StringCol(notNull=True)
4476.5.10 by Tom Berger
sql-quote db enum values, don't just rely on them being integers; also other changes after review
133
    definition_status = EnumCol(
134
        schema=SpecificationDefinitionStatus, notNull=True,
4476.5.4 by Tom Berger
implementation and tests complete
135
        default=SpecificationDefinitionStatus.NEW)
2736.1.23 by Mark Shuttleworth
�karma restructuring, and test fixes
136
    priority = EnumCol(schema=SpecificationPriority, notNull=True,
3348.1.40 by Mark Shuttleworth
Rename SpecPriority.PROPOSED to UNDEFINED
137
        default=SpecificationPriority.UNDEFINED)
2344 by Canonical.com Patch Queue Manager
[not r=kiko] specification tracker
138
    assignee = ForeignKey(dbName='assignee', notNull=False,
5485.1.13 by Edwin Grubbs
Sorta working
139
        foreignKey='Person',
5821.2.40 by James Henstridge
* Move all the uses of public_person_validator over to the Storm
140
        storm_validator=validate_public_person, default=None)
2344 by Canonical.com Patch Queue Manager
[not r=kiko] specification tracker
141
    drafter = ForeignKey(dbName='drafter', notNull=False,
5485.1.13 by Edwin Grubbs
Sorta working
142
        foreignKey='Person',
5821.2.40 by James Henstridge
* Move all the uses of public_person_validator over to the Storm
143
        storm_validator=validate_public_person, default=None)
2344 by Canonical.com Patch Queue Manager
[not r=kiko] specification tracker
144
    approver = ForeignKey(dbName='approver', notNull=False,
5485.1.13 by Edwin Grubbs
Sorta working
145
        foreignKey='Person',
5821.2.40 by James Henstridge
* Move all the uses of public_person_validator over to the Storm
146
        storm_validator=validate_public_person, default=None)
5485.1.17 by Edwin Grubbs
Fixed indentation
147
    owner = ForeignKey(
148
        dbName='owner', foreignKey='Person',
5821.2.40 by James Henstridge
* Move all the uses of public_person_validator over to the Storm
149
        storm_validator=validate_public_person, notNull=True)
2344 by Canonical.com Patch Queue Manager
[not r=kiko] specification tracker
150
    datecreated = UtcDateTimeCol(notNull=True, default=DEFAULT)
9550.17.5 by Stuart Bishop
Expose Specification.private
151
    private = BoolCol(notNull=True, default=False)
2344 by Canonical.com Patch Queue Manager
[not r=kiko] specification tracker
152
    product = ForeignKey(dbName='product', foreignKey='Product',
153
        notNull=False, default=None)
154
    productseries = ForeignKey(dbName='productseries',
155
        foreignKey='ProductSeries', notNull=False, default=None)
156
    distribution = ForeignKey(dbName='distribution',
157
        foreignKey='Distribution', notNull=False, default=None)
5121.2.6 by Stuart Bishop
Some required code updates
158
    distroseries = ForeignKey(dbName='distroseries',
4285.2.1 by Mark Shuttleworth
Massive renaming of distrorelease to distroseries
159
        foreignKey='DistroSeries', notNull=False, default=None)
3203.1.8 by Mark Shuttleworth
Terminology cleanup, use "goal" for the distrorelease/productseries of spec
160
    goalstatus = EnumCol(schema=SpecificationGoalStatus, notNull=True,
161
        default=SpecificationGoalStatus.PROPOSED)
3691.73.7 by Mark Shuttleworth
Keep detailed track of spec goal nomination and approval
162
    goal_proposer = ForeignKey(dbName='goal_proposer', notNull=False,
5485.1.13 by Edwin Grubbs
Sorta working
163
        foreignKey='Person',
5821.2.40 by James Henstridge
* Move all the uses of public_person_validator over to the Storm
164
        storm_validator=validate_public_person, default=None)
3691.73.7 by Mark Shuttleworth
Keep detailed track of spec goal nomination and approval
165
    date_goal_proposed = UtcDateTimeCol(notNull=False, default=None)
166
    goal_decider = ForeignKey(dbName='goal_decider', notNull=False,
5485.1.13 by Edwin Grubbs
Sorta working
167
        foreignKey='Person',
5821.2.40 by James Henstridge
* Move all the uses of public_person_validator over to the Storm
168
        storm_validator=validate_public_person, default=None)
3691.73.7 by Mark Shuttleworth
Keep detailed track of spec goal nomination and approval
169
    date_goal_decided = UtcDateTimeCol(notNull=False, default=None)
2344 by Canonical.com Patch Queue Manager
[not r=kiko] specification tracker
170
    milestone = ForeignKey(dbName='milestone',
171
        foreignKey='Milestone', notNull=False, default=None)
5821.2.8 by James Henstridge
Fix a few more issues uncovered by clicking around a bit.
172
    specurl = StringCol(notNull=False, default=None)
2344 by Canonical.com Patch Queue Manager
[not r=kiko] specification tracker
173
    whiteboard = StringCol(notNull=False, default=None)
2736.1.27 by Mark Shuttleworth
Move needs_discussion to spec table, and add a direction_approved flag.
174
    direction_approved = BoolCol(notNull=True, default=False)
2736.1.32 by Mark Shuttleworth
specs: add man days estimate and risk of delivery
175
    man_days = IntCol(notNull=False, default=None)
4476.5.10 by Tom Berger
sql-quote db enum values, don't just rely on them being integers; also other changes after review
176
    implementation_status = EnumCol(
177
        schema=SpecificationImplementationStatus, notNull=True,
4476.5.4 by Tom Berger
implementation and tests complete
178
        default=SpecificationImplementationStatus.UNKNOWN)
2736.1.41 by Mark Shuttleworth
record superseding specification
179
    superseded_by = ForeignKey(dbName='superseded_by',
180
        foreignKey='Specification', notNull=False, default=None)
3691.73.9 by Mark Shuttleworth
Detect spec completion and record it when spec details change.
181
    completer = ForeignKey(dbName='completer', notNull=False,
5485.1.13 by Edwin Grubbs
Sorta working
182
        foreignKey='Person',
5821.2.40 by James Henstridge
* Move all the uses of public_person_validator over to the Storm
183
        storm_validator=validate_public_person, default=None)
3691.73.9 by Mark Shuttleworth
Detect spec completion and record it when spec details change.
184
    date_completed = UtcDateTimeCol(notNull=False, default=None)
3691.74.1 by Mark Shuttleworth
Track spec starting times and people.
185
    starter = ForeignKey(dbName='starter', notNull=False,
5485.1.13 by Edwin Grubbs
Sorta working
186
        foreignKey='Person',
5821.2.40 by James Henstridge
* Move all the uses of public_person_validator over to the Storm
187
        storm_validator=validate_public_person, default=None)
3691.74.1 by Mark Shuttleworth
Track spec starting times and people.
188
    date_started = UtcDateTimeCol(notNull=False, default=None)
2344 by Canonical.com Patch Queue Manager
[not r=kiko] specification tracker
189
190
    # useful joins
13101.1.1 by Nigel Babu
Fix the sort order and the tests that broke with it
191
    _subscriptions = SQLMultipleJoin('SpecificationSubscription',
2344 by Canonical.com Patch Queue Manager
[not r=kiko] specification tracker
192
        joinColumn='specification', orderBy='id')
3504.1.13 by kiko
Implement initial SQLRelatedJoin migration across Launchpad tree. Still needs to reconsider the Snapshot approach which will be a big performance hit.
193
    subscribers = SQLRelatedJoin('Person',
2344 by Canonical.com Patch Queue Manager
[not r=kiko] specification tracker
194
        joinColumn='specification', otherColumn='person',
3691.12.14 by Mark Shuttleworth
Test fixes
195
        intermediateTable='SpecificationSubscription',
196
        orderBy=['displayname', 'name'])
3226.2.1 by Diogo Matsubara
Fix https://launchpad.net/products/launchpad/+bug/33625 (Change MultipleJoin to use the new SQLMultipleJoin.)
197
    feedbackrequests = SQLMultipleJoin('SpecificationFeedback',
2344 by Canonical.com Patch Queue Manager
[not r=kiko] specification tracker
198
        joinColumn='specification', orderBy='id')
3226.2.1 by Diogo Matsubara
Fix https://launchpad.net/products/launchpad/+bug/33625 (Change MultipleJoin to use the new SQLMultipleJoin.)
199
    sprint_links = SQLMultipleJoin('SprintSpecification', orderBy='id',
2445 by Canonical.com Patch Queue Manager
[trivial] db tables for sprints
200
        joinColumn='specification')
3504.1.13 by kiko
Implement initial SQLRelatedJoin migration across Launchpad tree. Still needs to reconsider the Snapshot approach which will be a big performance hit.
201
    sprints = SQLRelatedJoin('Sprint', orderBy='name',
2445 by Canonical.com Patch Queue Manager
[trivial] db tables for sprints
202
        joinColumn='specification', otherColumn='sprint',
203
        intermediateTable='SprintSpecification')
5485.1.18 by Edwin Grubbs
Fixed lots of lint issues
204
    bug_links = SQLMultipleJoin(
205
        'SpecificationBug', joinColumn='specification', orderBy='id')
3504.1.13 by kiko
Implement initial SQLRelatedJoin migration across Launchpad tree. Still needs to reconsider the Snapshot approach which will be a big performance hit.
206
    bugs = SQLRelatedJoin('Bug',
2344 by Canonical.com Patch Queue Manager
[not r=kiko] specification tracker
207
        joinColumn='specification', otherColumn='bug',
2396 by Canonical.com Patch Queue Manager
[r=spiv] launchpad support tracker
208
        intermediateTable='SpecificationBug', orderBy='id')
8698.10.5 by Paul Hummer
Fixed more tests
209
    linked_branches = SQLMultipleJoin('SpecificationBranch',
3691.233.3 by Tim Penhey
interim checkin for specification branches, test fails at the moment
210
        joinColumn='specification',
211
        orderBy='id')
3504.1.13 by kiko
Implement initial SQLRelatedJoin migration across Launchpad tree. Still needs to reconsider the Snapshot approach which will be a big performance hit.
212
    spec_dependency_links = SQLMultipleJoin('SpecificationDependency',
213
        joinColumn='specification', orderBy='id')
214
215
    dependencies = SQLRelatedJoin('Specification', joinColumn='specification',
2344 by Canonical.com Patch Queue Manager
[not r=kiko] specification tracker
216
        otherColumn='dependency', orderBy='title',
217
        intermediateTable='SpecificationDependency')
3504.1.13 by kiko
Implement initial SQLRelatedJoin migration across Launchpad tree. Still needs to reconsider the Snapshot approach which will be a big performance hit.
218
    blocked_specs = SQLRelatedJoin('Specification', joinColumn='dependency',
2344 by Canonical.com Patch Queue Manager
[not r=kiko] specification tracker
219
        otherColumn='specification', orderBy='title',
220
        intermediateTable='SpecificationDependency')
221
13101.1.1 by Nigel Babu
Fix the sort order and the tests that broke with it
222
    @cachedproperty
223
    def subscriptions(self):
224
        """Sort the subscriptions"""
13147.2.6 by Nigel Babu
There's already abstraction for this.
225
        from lp.registry.model.person import person_sort_key
226
        return sorted(
227
            self._subscriptions, key=lambda sub: person_sort_key(sub.person))
13101.1.5 by Nigel Babu
More fixes from sinzui
228
2344 by Canonical.com Patch Queue Manager
[not r=kiko] specification tracker
229
    @property
230
    def target(self):
231
        """See ISpecification."""
232
        if self.product:
233
            return self.product
234
        return self.distribution
235
11962.3.3 by Guilherme Salgado
Create a new setTarget method on ISpecification, use it in retarget() and protect both with launchpad.Edit. Also started splitting ISpecification into several interfaces to make it easyer to protect them with different permissions
236
    def setTarget(self, target):
2736.1.31 by Mark Shuttleworth
fix permissions issue with retargeting
237
        """See ISpecification."""
11962.3.3 by Guilherme Salgado
Create a new setTarget method on ISpecification, use it in retarget() and protect both with launchpad.Edit. Also started splitting ISpecification into several interfaces to make it easyer to protect them with different permissions
238
        if IProduct.providedBy(target):
239
            self.product = target
240
            self.distribution = None
241
        elif IDistribution.providedBy(target):
242
            self.product = None
243
            self.distribution = target
244
        else:
245
            raise AssertionError("Unknown target: %s" % target)
2736.1.31 by Mark Shuttleworth
fix permissions issue with retargeting
246
11962.3.3 by Guilherme Salgado
Create a new setTarget method on ISpecification, use it in retarget() and protect both with launchpad.Edit. Also started splitting ISpecification into several interfaces to make it easyer to protect them with different permissions
247
    def retarget(self, target):
248
        """See ISpecification."""
249
        if self.target == target:
3691.73.15 by Mark Shuttleworth
Ensure that specs on inactive products are filtered from listings.
250
            return
251
11138.2.1 by James Westby
Work towards having a blueprint model that is safe to export.
252
        self.validateMove(target)
253
11962.3.3 by Guilherme Salgado
Create a new setTarget method on ISpecification, use it in retarget() and protect both with launchpad.Edit. Also started splitting ISpecification into several interfaces to make it easyer to protect them with different permissions
254
        # We must lose any goal we have set and approved/declined because we
255
        # are moving to a different target that will have different
256
        # policies and drivers.
2736.1.31 by Mark Shuttleworth
fix permissions issue with retargeting
257
        self.productseries = None
4285.2.1 by Mark Shuttleworth
Massive renaming of distrorelease to distroseries
258
        self.distroseries = None
3691.73.15 by Mark Shuttleworth
Ensure that specs on inactive products are filtered from listings.
259
        self.goalstatus = SpecificationGoalStatus.PROPOSED
260
        self.goal_proposer = None
261
        self.date_goal_proposed = None
2736.1.31 by Mark Shuttleworth
fix permissions issue with retargeting
262
        self.milestone = None
3691.73.15 by Mark Shuttleworth
Ensure that specs on inactive products are filtered from listings.
263
11962.3.3 by Guilherme Salgado
Create a new setTarget method on ISpecification, use it in retarget() and protect both with launchpad.Edit. Also started splitting ISpecification into several interfaces to make it easyer to protect them with different permissions
264
        self.setTarget(target)
3691.12.13 by Mark Shuttleworth
Fix #50814, now spec retargeting resets spec priority not definition status
265
        self.priority = SpecificationPriority.UNDEFINED
266
        self.direction_approved = False
2736.1.31 by Mark Shuttleworth
fix permissions issue with retargeting
267
11138.2.1 by James Westby
Work towards having a blueprint model that is safe to export.
268
    def validateMove(self, target):
11962.3.3 by Guilherme Salgado
Create a new setTarget method on ISpecification, use it in retarget() and protect both with launchpad.Edit. Also started splitting ISpecification into several interfaces to make it easyer to protect them with different permissions
269
        """See ISpecification."""
11138.2.1 by James Westby
Work towards having a blueprint model that is safe to export.
270
        if target.getSpecification(self.name) is not None:
271
            raise TargetAlreadyHasSpecification(target, self.name)
272
3203.1.8 by Mark Shuttleworth
Terminology cleanup, use "goal" for the distrorelease/productseries of spec
273
    @property
274
    def goal(self):
275
        """See ISpecification."""
276
        if self.productseries:
277
            return self.productseries
4285.2.1 by Mark Shuttleworth
Massive renaming of distrorelease to distroseries
278
        return self.distroseries
3203.1.8 by Mark Shuttleworth
Terminology cleanup, use "goal" for the distrorelease/productseries of spec
279
3691.73.7 by Mark Shuttleworth
Keep detailed track of spec goal nomination and approval
280
    def proposeGoal(self, goal, proposer):
281
        """See ISpecification."""
282
        if goal is None:
283
            # we are clearing goals
284
            self.productseries = None
4285.2.1 by Mark Shuttleworth
Massive renaming of distrorelease to distroseries
285
            self.distroseries = None
3691.73.7 by Mark Shuttleworth
Keep detailed track of spec goal nomination and approval
286
        elif IProductSeries.providedBy(goal):
287
            # set the product series as a goal
288
            self.productseries = goal
289
            self.goal_proposer = proposer
290
            self.date_goal_proposed = UTC_NOW
4285.2.1 by Mark Shuttleworth
Massive renaming of distrorelease to distroseries
291
            # and make sure there is no leftover distroseries goal
292
            self.distroseries = None
293
        elif IDistroSeries.providedBy(goal):
294
            # set the distroseries goal
295
            self.distroseries = goal
3691.73.7 by Mark Shuttleworth
Keep detailed track of spec goal nomination and approval
296
            self.goal_proposer = proposer
297
            self.date_goal_proposed = UTC_NOW
4285.2.1 by Mark Shuttleworth
Massive renaming of distrorelease to distroseries
298
            # and make sure there is no leftover distroseries goal
3691.73.7 by Mark Shuttleworth
Keep detailed track of spec goal nomination and approval
299
            self.productseries = None
300
        else:
11235.5.5 by Curtis Hovey
Hushed lint.
301
            raise AssertionError('Inappropriate goal.')
3691.73.7 by Mark Shuttleworth
Keep detailed track of spec goal nomination and approval
302
        # record who made the proposal, and when
303
        self.goal_proposer = proposer
304
        self.date_goal_proposed = UTC_NOW
305
        # and of course set the goal status to PROPOSED
306
        self.goalstatus = SpecificationGoalStatus.PROPOSED
3691.73.21 by Mark Shuttleworth
Additional changes raised during review.
307
        # the goal should now also not have a decider
308
        self.goal_decider = None
309
        self.date_goal_decided = None
11962.3.2 by Guilherme Salgado
Create IHasDrivers.personHasDriverRights(person), to be used on security adapters and ISpecification.proposeGoal().
310
        if goal is not None and goal.personHasDriverRights(proposer):
311
            self.acceptBy(proposer)
3691.73.7 by Mark Shuttleworth
Keep detailed track of spec goal nomination and approval
312
313
    def acceptBy(self, decider):
314
        """See ISpecification."""
315
        self.goalstatus = SpecificationGoalStatus.ACCEPTED
316
        self.goal_decider = decider
317
        self.date_goal_decided = UTC_NOW
318
319
    def declineBy(self, decider):
320
        """See ISpecification."""
321
        self.goalstatus = SpecificationGoalStatus.DECLINED
322
        self.goal_decider = decider
323
        self.date_goal_decided = UTC_NOW
324
2736.1.4 by Mark Shuttleworth
substantial cleanups of meeting system
325
    def getSprintSpecification(self, sprintname):
326
        """See ISpecification."""
327
        for sprintspecification in self.sprint_links:
328
            if sprintspecification.sprint.name == sprintname:
329
                return sprintspecification
330
        return None
331
2736.1.34 by Mark Shuttleworth
start renaming specificationreview to specificationfeedback
332
    def getFeedbackRequests(self, person):
333
        """See ISpecification."""
5622.3.15 by Christian Robottom Reis
Optimize specification feedback calls away; make sure we only use the View template for the actual page, not for portlets and etc., since the feedback stuff is only relevant for the page itself.
334
        fb = SpecificationFeedback.selectBy(
335
            specification=self, reviewer=person)
336
        return fb.prejoin(['requester'])
2736.1.34 by Mark Shuttleworth
start renaming specificationreview to specificationfeedback
337
3287.1.2 by Bjorn Tillenius
add ISpecification.notificationRecipientAddresses()
338
    def notificationRecipientAddresses(self):
339
        """See ISpecification."""
340
        related_people = [
341
            self.owner, self.assignee, self.approver, self.drafter]
342
        related_people = [
343
            person for person in related_people if person is not None]
344
        subscribers = [
345
            subscription.person for subscription in self.subscriptions]
346
        addresses = set()
347
        for person in related_people + subscribers:
7495.2.3 by Curtis Hovey
Renamed contactEmailAddresses => get_contact_email_addresses. Fixed long lines.
348
            addresses.update(get_contact_email_addresses(person))
3287.1.2 by Bjorn Tillenius
add ISpecification.notificationRecipientAddresses()
349
        return sorted(addresses)
350
2736.1.16 by Mark Shuttleworth
ability to add someone else as a subscriber
351
    # emergent properties
352
    @property
353
    def is_incomplete(self):
354
        """See ISpecification."""
2736.1.32 by Mark Shuttleworth
specs: add man days estimate and risk of delivery
355
        return not self.is_complete
356
3348.1.11 by Mark Shuttleworth
Make IHasSpecifications.specifications filtered and use it
357
    # Several other classes need to generate lists of specifications, and
358
    # one thing they often have to filter for is completeness. We maintain
359
    # this single canonical query string here so that it does not have to be
360
    # cargo culted into Product, Distribution, ProductSeries etc
3691.73.9 by Mark Shuttleworth
Detect spec completion and record it when spec details change.
361
362
    # Also note that there is a constraint in the database which ensures
363
    # that date_completed is set if the spec is complete, and that db
364
    # constraint parrots this definition exactly.
365
366
    # NB NB NB if you change this definition PLEASE update the db constraint
3691.74.1 by Mark Shuttleworth
Track spec starting times and people.
367
    # Specification.specification_completion_recorded_chk !!!
11235.5.5 by Curtis Hovey
Hushed lint.
368
    completeness_clause = ("""
4476.5.10 by Tom Berger
sql-quote db enum values, don't just rely on them being integers; also other changes after review
369
        Specification.implementation_status = %s OR
370
        Specification.definition_status IN ( %s, %s ) OR
371
        (Specification.implementation_status = %s AND
372
         Specification.definition_status = %s)
373
        """ % sqlvalues(SpecificationImplementationStatus.IMPLEMENTED.value,
374
                        SpecificationDefinitionStatus.OBSOLETE.value,
375
                        SpecificationDefinitionStatus.SUPERSEDED.value,
376
                        SpecificationImplementationStatus.INFORMATIONAL.value,
377
                        SpecificationDefinitionStatus.APPROVED.value))
3348.1.11 by Mark Shuttleworth
Make IHasSpecifications.specifications filtered and use it
378
2736.1.32 by Mark Shuttleworth
specs: add man days estimate and risk of delivery
379
    @property
380
    def is_complete(self):
4476.5.10 by Tom Berger
sql-quote db enum values, don't just rely on them being integers; also other changes after review
381
        """See `ISpecification`."""
382
        # Implemented blueprints are by definition complete.
383
        if (self.implementation_status ==
384
            SpecificationImplementationStatus.IMPLEMENTED):
385
            return True
386
        # Obsolete and superseded blueprints are considered complete.
387
        if self.definition_status in (
388
            SpecificationDefinitionStatus.OBSOLETE,
389
            SpecificationDefinitionStatus.SUPERSEDED):
390
            return True
391
        # Approved information blueprints are also considered complete.
392
        if ((self.implementation_status ==
393
             SpecificationImplementationStatus.INFORMATIONAL) and
394
            (self.definition_status ==
395
             SpecificationDefinitionStatus.APPROVED)):
396
            return True
397
        else:
398
            return False
2736.1.16 by Mark Shuttleworth
ability to add someone else as a subscriber
399
3691.74.1 by Mark Shuttleworth
Track spec starting times and people.
400
    # NB NB If you change this definition, please update the equivalent
401
    # DB constraint Specification.specification_start_recorded_chk
402
    # We choose to define "started" as the set of delivery states NOT
403
    # in the values we select. Another option would be to say "anything less
404
    # than a threshold" and to comment the dbschema that "anything not
405
    # started should be less than the threshold". We'll see how maintainable
406
    # this is.
11235.5.5 by Curtis Hovey
Hushed lint.
407
    started_clause = """
4476.5.10 by Tom Berger
sql-quote db enum values, don't just rely on them being integers; also other changes after review
408
        Specification.implementation_status NOT IN (%s, %s, %s, %s) OR
409
        (Specification.implementation_status = %s AND
410
         Specification.definition_status = %s)
411
        """ % sqlvalues(SpecificationImplementationStatus.UNKNOWN.value,
4476.5.4 by Tom Berger
implementation and tests complete
412
                        SpecificationImplementationStatus.NOTSTARTED.value,
413
                        SpecificationImplementationStatus.DEFERRED.value,
4476.5.10 by Tom Berger
sql-quote db enum values, don't just rely on them being integers; also other changes after review
414
                        SpecificationImplementationStatus.INFORMATIONAL.value,
415
                        SpecificationImplementationStatus.INFORMATIONAL.value,
416
                        SpecificationDefinitionStatus.APPROVED.value)
3691.109.27 by Francis J. Lacoste
Merge RF
417
3691.74.1 by Mark Shuttleworth
Track spec starting times and people.
418
    @property
419
    def is_started(self):
420
        """See ISpecification. This is a code implementation of the
421
        SQL in self.started_clause
422
        """
4476.5.4 by Tom Berger
implementation and tests complete
423
        return (self.implementation_status not in [
424
                    SpecificationImplementationStatus.UNKNOWN,
425
                    SpecificationImplementationStatus.NOTSTARTED,
426
                    SpecificationImplementationStatus.DEFERRED,
427
                    SpecificationImplementationStatus.INFORMATIONAL,
3691.74.1 by Mark Shuttleworth
Track spec starting times and people.
428
                    ]
4476.5.4 by Tom Berger
implementation and tests complete
429
                or ((self.implementation_status ==
430
                     SpecificationImplementationStatus.INFORMATIONAL) and
431
                    (self.definition_status ==
432
                     SpecificationDefinitionStatus.APPROVED)))
3691.74.1 by Mark Shuttleworth
Track spec starting times and people.
433
11852.1.3 by Tim Penhey
Have the Specification.updateLifecycleStatus method called due to the object modified event in preparation of API exposure.
434
    @property
435
    def lifecycle_status(self):
436
        """Combine the is_complete and is_started emergent properties."""
437
        if self.is_complete:
438
            return SpecificationLifecycleStatus.COMPLETE
439
        elif self.is_started:
440
            return SpecificationLifecycleStatus.STARTED
441
        else:
442
            return SpecificationLifecycleStatus.NOTSTARTED
443
12534.3.1 by Tim Penhey
Some working.
444
    def setDefinitionStatus(self, definition_status, user):
445
        self.definition_status = definition_status
446
        self.updateLifecycleStatus(user)
447
448
    def setImplementationStatus(self, implementation_status, user):
449
        self.implementation_status = implementation_status
450
        self.updateLifecycleStatus(user)
451
3691.74.1 by Mark Shuttleworth
Track spec starting times and people.
452
    def updateLifecycleStatus(self, user):
3691.73.9 by Mark Shuttleworth
Detect spec completion and record it when spec details change.
453
        """See ISpecification."""
3691.74.1 by Mark Shuttleworth
Track spec starting times and people.
454
        newstatus = None
455
        if self.is_started:
5821.2.27 by James Henstridge
* Add block_implicit_flushes decorator that blocks flushes performed
456
            if self.starterID is None:
3691.74.1 by Mark Shuttleworth
Track spec starting times and people.
457
                newstatus = SpecificationLifecycleStatus.STARTED
458
                self.date_started = UTC_NOW
459
                self.starter = user
460
        else:
5821.2.27 by James Henstridge
* Add block_implicit_flushes decorator that blocks flushes performed
461
            if self.starterID is not None:
3691.74.1 by Mark Shuttleworth
Track spec starting times and people.
462
                newstatus = SpecificationLifecycleStatus.NOTSTARTED
463
                self.date_started = None
464
                self.starter = None
3691.73.9 by Mark Shuttleworth
Detect spec completion and record it when spec details change.
465
        if self.is_complete:
5821.2.27 by James Henstridge
* Add block_implicit_flushes decorator that blocks flushes performed
466
            if self.completerID is None:
3691.74.1 by Mark Shuttleworth
Track spec starting times and people.
467
                newstatus = SpecificationLifecycleStatus.COMPLETE
3691.73.9 by Mark Shuttleworth
Detect spec completion and record it when spec details change.
468
                self.date_completed = UTC_NOW
469
                self.completer = user
470
        else:
5821.2.27 by James Henstridge
* Add block_implicit_flushes decorator that blocks flushes performed
471
            if self.completerID is not None:
3691.73.9 by Mark Shuttleworth
Detect spec completion and record it when spec details change.
472
                self.date_completed = None
473
                self.completer = None
3691.74.1 by Mark Shuttleworth
Track spec starting times and people.
474
                if self.is_started:
475
                    newstatus = SpecificationLifecycleStatus.STARTED
476
                else:
477
                    newstatus = SpecificationLifecycleStatus.NOTSTARTED
3691.109.27 by Francis J. Lacoste
Merge RF
478
3691.74.1 by Mark Shuttleworth
Track spec starting times and people.
479
        return newstatus
3691.73.9 by Mark Shuttleworth
Detect spec completion and record it when spec details change.
480
2736.1.16 by Mark Shuttleworth
ability to add someone else as a subscriber
481
    @property
482
    def is_blocked(self):
483
        """See ISpecification."""
484
        for spec in self.dependencies:
485
            if spec.is_incomplete:
486
                return True
487
        return False
488
2736.1.32 by Mark Shuttleworth
specs: add man days estimate and risk of delivery
489
    @property
4285.2.1 by Mark Shuttleworth
Massive renaming of distrorelease to distroseries
490
    def has_accepted_goal(self):
2736.1.32 by Mark Shuttleworth
specs: add man days estimate and risk of delivery
491
        """See ISpecification."""
3279.1.4 by Mark Shuttleworth
Address review comments for r=kiko
492
        if (self.goal is not None and
493
            self.goalstatus == SpecificationGoalStatus.ACCEPTED):
2736.1.32 by Mark Shuttleworth
specs: add man days estimate and risk of delivery
494
            return True
2736.1.40 by Mark Shuttleworth
tweaks for [r=spiv]
495
        return False
2736.1.32 by Mark Shuttleworth
specs: add man days estimate and risk of delivery
496
2736.1.22 by Mark Shuttleworth
karma categories in the db, and karma for specs
497
    def getDelta(self, old_spec, user):
498
        """See ISpecification."""
3691.431.1 by Tim Penhey
First checkin of branch emails. Nothing works yet, but need to switch
499
        delta = ObjectDelta(old_spec, self)
8971.13.1 by Andrea Corbellini
When the whiteboard of a blueprint is edited, show the differences
500
        delta.recordNewValues(("title", "summary",
3691.290.11 by Tim Penhey
Updates following review comments
501
                               "specurl", "productseries",
4285.2.1 by Mark Shuttleworth
Massive renaming of distrorelease to distroseries
502
                               "distroseries", "milestone"))
5543.8.11 by Tim Penhey
Lint line length fixes.
503
        delta.recordNewAndOld(("name", "priority", "definition_status",
8971.13.1 by Andrea Corbellini
When the whiteboard of a blueprint is edited, show the differences
504
                               "target", "approver", "assignee", "drafter",
505
                               "whiteboard"))
3691.290.11 by Tim Penhey
Updates following review comments
506
        delta.recordListAddedAndRemoved("bugs",
507
                                        "bugs_linked",
508
                                        "bugs_unlinked")
4785.3.1 by Jeroen Vermeulen
Removed whitespace before EOLs.
509
3691.431.1 by Tim Penhey
First checkin of branch emails. Nothing works yet, but need to switch
510
        if delta.changes:
511
            changes = delta.changes
2736.1.22 by Mark Shuttleworth
karma categories in the db, and karma for specs
512
            changes["specification"] = self
513
            changes["user"] = user
514
515
            return SpecificationDelta(**changes)
516
        else:
517
            return None
518
4476.5.4 by Tom Berger
implementation and tests complete
519
    @property
520
    def informational(self):
521
        """For backwards compatibility:
522
        implemented as a value in implementation_status.
523
        """
524
        return (self.implementation_status ==
525
                SpecificationImplementationStatus.INFORMATIONAL)
526
2344 by Canonical.com Patch Queue Manager
[not r=kiko] specification tracker
527
    # subscriptions
3691.73.1 by Mark Shuttleworth
Allow subscribers to be classed as essential to a spec's plan.
528
    def subscription(self, person):
529
        """See ISpecification."""
3691.436.68 by Mark Shuttleworth
Offer the mentor the option to subscribe to the work
530
        return SpecificationSubscription.selectOneBy(
531
                specification=self, person=person)
3691.73.1 by Mark Shuttleworth
Allow subscribers to be classed as essential to a spec's plan.
532
533
    def getSubscriptionByName(self, name):
534
        """See ISpecification."""
535
        for sub in self.subscriptions:
536
            if sub.person.name == name:
537
                return sub
538
        return None
539
13161.2.1 by Ian Booth
Export (un)subscribe methods on ISpecification to web service
540
    def subscribe(self, person, subscribed_by=None, essential=False):
541
        """See ISpecification."""
542
        if subscribed_by is None:
543
            subscribed_by = person
544
        # Create or modify a user's subscription to this blueprint.
545
        # First see if a relevant subscription exists, and if so, return it
3691.73.1 by Mark Shuttleworth
Allow subscribers to be classed as essential to a spec's plan.
546
        sub = self.subscription(person)
4506.3.3 by Tom Berger
post review changes
547
        if sub is not None:
4506.3.5 by Tom Berger
reply to review: make all parameters to specification.subscribe positional
548
            if sub.essential != essential:
4506.3.3 by Tom Berger
post review changes
549
                # If a subscription already exists, but the value for
550
                # 'essential' changes, there's no need to create a new
551
                # subscription, but we modify the existing subscription
552
                # and notify the user about the change.
4506.3.1 by Tom Berger
adds a new notification event for changes to a user's blueprint subscription and handles it by spamming the user, also adds tests for that behaviour and reformats the pagetest
553
                sub.essential = essential
4506.3.3 by Tom Berger
post review changes
554
                # The second argument should really be a copy of sub with
555
                # only the essential attribute changed, but we know
556
                # that we can get away with not examining the attribute
557
                # at all - it's a boolean!
7876.3.6 by Francis J. Lacoste
Used ISQLObject from lazr.lifecycle
558
                notify(ObjectModifiedEvent(
13161.2.1 by Ian Booth
Export (un)subscribe methods on ISpecification to web service
559
                        sub, sub, ['essential'], user=subscribed_by))
3691.73.1 by Mark Shuttleworth
Allow subscribers to be classed as essential to a spec's plan.
560
            return sub
3348.1.13 by Mark Shuttleworth
Finish sprint specification filtering
561
        # since no previous subscription existed, create and return a new one
4506.3.1 by Tom Berger
adds a new notification event for changes to a user's blueprint subscription and handles it by spamming the user, also adds tests for that behaviour and reformats the pagetest
562
        sub = SpecificationSubscription(specification=self,
3691.73.1 by Mark Shuttleworth
Allow subscribers to be classed as essential to a spec's plan.
563
            person=person, essential=essential)
13101.1.5 by Nigel Babu
More fixes from sinzui
564
        property_cache = get_property_cache(self)
565
        if 'subscription' in property_cache:
13147.2.6 by Nigel Babu
There's already abstraction for this.
566
            from lp.registry.model.person import person_sort_key
13101.1.5 by Nigel Babu
More fixes from sinzui
567
            property_cache.subscriptions.append(sub)
568
            property_cache.subscriptions.sort(
13147.2.6 by Nigel Babu
There's already abstraction for this.
569
                key=lambda sub: person_sort_key(sub.person))
570
        notify(ObjectCreatedEvent(sub, user=subscribed_by))
4506.3.1 by Tom Berger
adds a new notification event for changes to a user's blueprint subscription and handles it by spamming the user, also adds tests for that behaviour and reformats the pagetest
571
        return sub
2344 by Canonical.com Patch Queue Manager
[not r=kiko] specification tracker
572
13161.2.1 by Ian Booth
Export (un)subscribe methods on ISpecification to web service
573
    def unsubscribe(self, person, unsubscribed_by):
2344 by Canonical.com Patch Queue Manager
[not r=kiko] specification tracker
574
        """See ISpecification."""
575
        # see if a relevant subscription exists, and if so, delete it
13161.2.1 by Ian Booth
Export (un)subscribe methods on ISpecification to web service
576
        if person is None:
577
            person = unsubscribed_by
2344 by Canonical.com Patch Queue Manager
[not r=kiko] specification tracker
578
        for sub in self.subscriptions:
579
            if sub.person.id == person.id:
13161.2.2 by Ian Booth
Add canBeUnsubscribedByUser and tests
580
                if not sub.canBeUnsubscribedByUser(unsubscribed_by):
581
                    raise UserCannotUnsubscribePerson(
582
                        '%s does not have permission to unsubscribe %s.' % (
583
                            unsubscribed_by.displayname,
584
                            person.displayname))
13101.1.2 by Nigel Babu
Update the cache correctly.
585
                get_property_cache(self).subscriptions.remove(sub)
2344 by Canonical.com Patch Queue Manager
[not r=kiko] specification tracker
586
                SpecificationSubscription.delete(sub.id)
587
                return
588
3691.436.68 by Mark Shuttleworth
Offer the mentor the option to subscribe to the work
589
    def isSubscribed(self, person):
10409.5.69 by Curtis Hovey
Removed answers and blueprints from canonical.launchpad.__init__.
590
        """See lp.blueprints.interfaces.specification.ISpecification."""
3691.436.68 by Mark Shuttleworth
Offer the mentor the option to subscribe to the work
591
        if person is None:
592
            return False
593
594
        return bool(self.subscription(person))
595
2344 by Canonical.com Patch Queue Manager
[not r=kiko] specification tracker
596
    # queueing
2736.1.38 by Mark Shuttleworth
rename db table specificationreview to specificationfeedback [r=stub]
597
    def queue(self, reviewer, requester, queuemsg=None):
2344 by Canonical.com Patch Queue Manager
[not r=kiko] specification tracker
598
        """See ISpecification."""
2736.1.34 by Mark Shuttleworth
start renaming specificationreview to specificationfeedback
599
        for fbreq in self.feedbackrequests:
2736.1.35 by Mark Shuttleworth
multiselector interface for clearing spec feedback requests
600
            if (fbreq.reviewer.id == reviewer.id and
2736.1.38 by Mark Shuttleworth
rename db table specificationreview to specificationfeedback [r=stub]
601
                fbreq.requester == requester.id):
2736.1.35 by Mark Shuttleworth
multiselector interface for clearing spec feedback requests
602
                # we have a relevant request already, update it
2736.1.34 by Mark Shuttleworth
start renaming specificationreview to specificationfeedback
603
                fbreq.queuemsg = queuemsg
604
                return fbreq
2736.1.35 by Mark Shuttleworth
multiselector interface for clearing spec feedback requests
605
        # since no previous feedback request existed for this person,
606
        # create a new one
2736.1.34 by Mark Shuttleworth
start renaming specificationreview to specificationfeedback
607
        return SpecificationFeedback(
2344 by Canonical.com Patch Queue Manager
[not r=kiko] specification tracker
608
            specification=self,
609
            reviewer=reviewer,
2736.1.38 by Mark Shuttleworth
rename db table specificationreview to specificationfeedback [r=stub]
610
            requester=requester,
2344 by Canonical.com Patch Queue Manager
[not r=kiko] specification tracker
611
            queuemsg=queuemsg)
612
2736.1.38 by Mark Shuttleworth
rename db table specificationreview to specificationfeedback [r=stub]
613
    def unqueue(self, reviewer, requester):
2344 by Canonical.com Patch Queue Manager
[not r=kiko] specification tracker
614
        """See ISpecification."""
615
        # see if a relevant queue entry exists, and if so, delete it
2736.1.34 by Mark Shuttleworth
start renaming specificationreview to specificationfeedback
616
        for fbreq in self.feedbackrequests:
2736.1.35 by Mark Shuttleworth
multiselector interface for clearing spec feedback requests
617
            if (fbreq.reviewer.id == reviewer.id and
2736.1.38 by Mark Shuttleworth
rename db table specificationreview to specificationfeedback [r=stub]
618
                fbreq.requester.id == requester.id):
2736.1.34 by Mark Shuttleworth
start renaming specificationreview to specificationfeedback
619
                SpecificationFeedback.delete(fbreq.id)
2344 by Canonical.com Patch Queue Manager
[not r=kiko] specification tracker
620
                return
621
3691.109.23 by Francis J. Lacoste
* Define a BugLinkTargetMixin for base implementation of linkBug and unlinkBug.
622
    # Template methods for BugLinkTargetMixin
623
    buglinkClass = SpecificationBug
2344 by Canonical.com Patch Queue Manager
[not r=kiko] specification tracker
624
3691.109.23 by Francis J. Lacoste
* Define a BugLinkTargetMixin for base implementation of linkBug and unlinkBug.
625
    def createBugLink(self, bug):
626
        """See BugLinkTargetMixin."""
627
        return SpecificationBug(specification=self, bug=bug)
2344 by Canonical.com Patch Queue Manager
[not r=kiko] specification tracker
628
2445 by Canonical.com Patch Queue Manager
[trivial] db tables for sprints
629
    # sprint linking
3657.1.1 by Mark Shuttleworth
Keep track of who nominated a spec for a sprint.
630
    def linkSprint(self, sprint, user):
2445 by Canonical.com Patch Queue Manager
[trivial] db tables for sprints
631
        """See ISpecification."""
11962.3.22 by Guilherme Salgado
Make sure Sprint actually implements IHasSpecifications
632
        from lp.blueprints.model.sprintspecification import (
633
            SprintSpecification)
2445 by Canonical.com Patch Queue Manager
[trivial] db tables for sprints
634
        for sprint_link in self.sprint_links:
3691.73.5 by Mark Shuttleworth
Test fixes for sprint linking code and pages
635
            # sprints have unique names
636
            if sprint_link.sprint.name == sprint.name:
2445 by Canonical.com Patch Queue Manager
[trivial] db tables for sprints
637
                return sprint_link
4473.1.2 by Tom Berger
move the auto-accepting code to the content object (on the way to possiblly removing it altogether later)
638
        sprint_link = SprintSpecification(specification=self,
3691.73.4 by Mark Shuttleworth
Record the full lifecycle of sprint agenda nomination and approval.
639
            sprint=sprint, registrant=user)
4473.1.10 by Tom Berger
post review changes
640
        if sprint.isDriver(user):
4473.1.2 by Tom Berger
move the auto-accepting code to the content object (on the way to possiblly removing it altogether later)
641
            sprint_link.acceptBy(user)
642
        return sprint_link
2445 by Canonical.com Patch Queue Manager
[trivial] db tables for sprints
643
644
    def unlinkSprint(self, sprint):
645
        """See ISpecification."""
11962.3.22 by Guilherme Salgado
Make sure Sprint actually implements IHasSpecifications
646
        from lp.blueprints.model.sprintspecification import (
647
            SprintSpecification)
2445 by Canonical.com Patch Queue Manager
[trivial] db tables for sprints
648
        for sprint_link in self.sprint_links:
3691.73.5 by Mark Shuttleworth
Test fixes for sprint linking code and pages
649
            # sprints have unique names
650
            if sprint_link.sprint.name == sprint.name:
2445 by Canonical.com Patch Queue Manager
[trivial] db tables for sprints
651
                SprintSpecification.delete(sprint_link.id)
652
                return sprint_link
653
2344 by Canonical.com Patch Queue Manager
[not r=kiko] specification tracker
654
    # dependencies
655
    def createDependency(self, specification):
656
        """See ISpecification."""
657
        for deplink in self.spec_dependency_links:
658
            if deplink.dependency.id == specification.id:
659
                return deplink
660
        return SpecificationDependency(specification=self,
661
            dependency=specification)
662
663
    def removeDependency(self, specification):
664
        """See ISpecification."""
665
        # see if a relevant dependency link exists, and if so, delete it
666
        for deplink in self.spec_dependency_links:
667
            if deplink.dependency.id == specification.id:
668
                SpecificationDependency.delete(deplink.id)
669
                return deplink
670
3691.12.2 by Mark Shuttleworth
Do not allow adding a spec dependency on a blocked spec, and fix #2428
671
    @property
3348.1.13 by Mark Shuttleworth
Finish sprint specification filtering
672
    def all_deps(self):
12579.3.2 by Tim Penhey
Move the SQL out.
673
        return Store.of(self).with_(
12579.2.6 by Tim Penhey
Move to module functions.
674
            SQL(recursive_dependent_query(self))).find(
12579.2.3 by Tim Penhey
Change the all_blocked and all_deps to use recursive queries, rather than a recursive algorithm and many queries.
675
            Specification,
676
            Specification.id != self.id,
12579.2.9 by Tim Penhey
Tweaks to make the tests pass. Also add explicit ordering.
677
            SQL('Specification.id in (select id from dependencies)')
678
            ).order_by(Specification.name, Specification.id)
12579.2.3 by Tim Penhey
Change the all_blocked and all_deps to use recursive queries, rather than a recursive algorithm and many queries.
679
7141.3.9 by Edwin Grubbs
Uncached spec.all_blocked
680
    @property
7141.3.2 by Edwin Grubbs
Added tests and comments. Re-introduced old Spec.all_blocked and moved all_blocked to cached_all_blocked_ids.
681
    def all_blocked(self):
682
        """See `ISpecification`."""
12579.3.2 by Tim Penhey
Move the SQL out.
683
        return Store.of(self).with_(
12579.2.6 by Tim Penhey
Move to module functions.
684
            SQL(recursive_blocked_query(self))).find(
12579.2.3 by Tim Penhey
Change the all_blocked and all_deps to use recursive queries, rather than a recursive algorithm and many queries.
685
            Specification,
686
            Specification.id != self.id,
12579.2.9 by Tim Penhey
Tweaks to make the tests pass. Also add explicit ordering.
687
            SQL('Specification.id in (select id from blocked)')
688
            ).order_by(Specification.name, Specification.id)
2344 by Canonical.com Patch Queue Manager
[not r=kiko] specification tracker
689
3691.233.3 by Tim Penhey
interim checkin for specification branches, test fails at the moment
690
    # branches
3691.233.6 by James Henstridge
navigation support for spec branch link
691
    def getBranchLink(self, branch):
692
        return SpecificationBranch.selectOneBy(
693
            specificationID=self.id, branchID=branch.id)
4785.3.1 by Jeroen Vermeulen
Removed whitespace before EOLs.
694
8339.2.8 by Paul Hummer
Removed summary from ISpecificationBranch
695
    def linkBranch(self, branch, registrant):
5543.8.6 by Tim Penhey
Adding notify events.
696
        branch_link = self.getBranchLink(branch)
697
        if branch_link is not None:
698
            return branch_link
699
        branch_link = SpecificationBranch(
8339.2.8 by Paul Hummer
Removed summary from ISpecificationBranch
700
            specification=self, branch=branch, registrant=registrant)
7876.3.6 by Francis J. Lacoste
Used ISQLObject from lazr.lifecycle
701
        notify(ObjectCreatedEvent(branch_link))
5543.8.6 by Tim Penhey
Adding notify events.
702
        return branch_link
3691.233.3 by Tim Penhey
interim checkin for specification branches, test fails at the moment
703
8698.10.9 by Paul Hummer
Responded to the review
704
    def unlinkBranch(self, branch, user):
705
        spec_branch = self.getBranchLink(branch)
706
        spec_branch.destroySelf()
8698.10.3 by Paul Hummer
Integrated IHasLinkedBranches into the interfaces
707
12588.3.4 by Tim Penhey
Define the filter_bugtasks_by_context interface.
708
    def getLinkedBugTasks(self, user):
12588.3.16 by Tim Penhey
Merge devel.
709
        """See `ISpecification`."""
12588.3.4 by Tim Penhey
Define the filter_bugtasks_by_context interface.
710
        params = BugTaskSearchParams(user=user, linked_blueprints=self.id)
12588.3.16 by Tim Penhey
Merge devel.
711
        tasks = getUtility(IBugTaskSet).search(params)
12588.3.21 by Tim Penhey
Remove branch specific bug task filtering, and add the possibility of titles to the generic link formatter.
712
        if self.distroseries is not None:
713
            context = self.distroseries
714
        elif self.distribution is not None:
715
            context = self.distribution
716
        elif self.productseries is not None:
717
            context = self.productseries
718
        else:
719
            context = self.product
720
        return filter_bugtasks_by_context(context, tasks)
12588.3.4 by Tim Penhey
Define the filter_bugtasks_by_context interface.
721
12579.3.6 by Tim Penhey
Add lotsa tests.
722
    def __repr__(self):
12579.2.8 by Tim Penhey
Tidy up the query, add the id to the repr, and sort the results.
723
        return '<Specification %s %r for %r>' % (
724
            self.id, self.name, self.target.name)
12579.3.6 by Tim Penhey
Add lotsa tests.
725
5543.8.10 by Tim Penhey
Updates following review.
726
3847.2.2 by Mark Shuttleworth
Clean up blueprint pages
727
class HasSpecificationsMixin:
728
    """A mixin class that implements many of the common shortcut properties
729
    for other classes that have specifications.
730
    """
731
5622.3.18 by Christian Robottom Reis
Implement another optimization for +roadmap by splitting it into its own view, and avoiding prejoining people when unnecessary (by adding a parameter to the specifications API).
732
    def specifications(self, sort=None, quantity=None, filter=None,
733
                       prejoin_people=True):
3847.2.2 by Mark Shuttleworth
Clean up blueprint pages
734
        """See IHasSpecifications."""
735
        # this should be implemented by the actual context class
736
        raise NotImplementedError
737
11474.3.1 by Robert Collins
Precache the is_person_valid status for specification assignments on Product and Distribution.
738
    def _specification_sort(self, sort):
739
        """Return the storm sort order for 'specifications'.
11962.3.2 by Guilherme Salgado
Create IHasDrivers.personHasDriverRights(person), to be used on security adapters and ISpecification.proposeGoal().
740
11474.3.1 by Robert Collins
Precache the is_person_valid status for specification assignments on Product and Distribution.
741
        :param sort: As per HasSpecificationsMixin.specifications.
742
        """
743
        # sort by priority descending, by default
744
        if sort is None or sort == SpecificationSort.PRIORITY:
745
            return (
746
                Desc(Specification.priority), Specification.definition_status,
747
                Specification.name)
748
        elif sort == SpecificationSort.DATE:
749
            return (Desc(Specification.datecreated), Specification.id)
750
751
    def _preload_specifications_people(self, query):
752
        """Perform eager loading of people and their validity for query.
11962.3.2 by Guilherme Salgado
Create IHasDrivers.personHasDriverRights(person), to be used on security adapters and ISpecification.proposeGoal().
753
11474.3.1 by Robert Collins
Precache the is_person_valid status for specification assignments on Product and Distribution.
754
        :param query: a string query generated in the 'specifications'
755
            method.
756
        :return: A DecoratedResultSet with Person precaching setup.
757
        """
758
        # Circular import.
759
        from lp.registry.model.person import Person
12498.3.2 by Curtis Hovey
Added NewSpecificationDefinitionStatus to define the set of enums that a new
760
11515.3.1 by Robert Collins
Change the preloading strategy for specifications to do two queries : we were spending 16 seconds in storm.
761
        def cache_people(rows):
762
            # Find the people we need:
763
            person_ids = set()
764
            for spec in rows:
765
                person_ids.add(spec.assigneeID)
766
                person_ids.add(spec.approverID)
767
                person_ids.add(spec.drafterID)
768
            person_ids.discard(None)
769
            if not person_ids:
770
                return
771
            # Query those people
772
            origin = [Person]
773
            columns = [Person]
774
            validity_info = Person._validity_queries()
11474.3.2 by Robert Collins
Change the contract for Person._validity_queries to make callsites easier to read as suggested in review.
775
            origin.extend(validity_info["joins"])
776
            columns.extend(validity_info["tables"])
777
            decorators = validity_info["decorators"]
11515.3.1 by Robert Collins
Change the preloading strategy for specifications to do two queries : we were spending 16 seconds in storm.
778
            personset = Store.of(self).using(*origin).find(
779
                tuple(columns),
780
                Person.id.is_in(person_ids),
781
                )
782
            for row in personset:
783
                person = row[0]
784
                index = 1
785
                for decorator in decorators:
786
                    column = row[index]
787
                    index += 1
788
                    decorator(person, column)
12498.3.2 by Curtis Hovey
Added NewSpecificationDefinitionStatus to define the set of enums that a new
789
11515.3.1 by Robert Collins
Change the preloading strategy for specifications to do two queries : we were spending 16 seconds in storm.
790
        results = Store.of(self).find(
791
            Specification,
11474.3.1 by Robert Collins
Precache the is_person_valid status for specification assignments on Product and Distribution.
792
            SQL(query),
793
            )
11515.3.1 by Robert Collins
Change the preloading strategy for specifications to do two queries : we were spending 16 seconds in storm.
794
        return DecoratedResultSet(results, pre_iter_hook=cache_people)
11474.3.1 by Robert Collins
Precache the is_person_valid status for specification assignments on Product and Distribution.
795
3847.2.2 by Mark Shuttleworth
Clean up blueprint pages
796
    @property
3847.2.25 by Mark Shuttleworth
Test fixes and object interface compliance
797
    def valid_specifications(self):
798
        """See IHasSpecifications."""
799
        return self.specifications(filter=[SpecificationFilter.VALID])
800
801
    @property
3847.2.2 by Mark Shuttleworth
Clean up blueprint pages
802
    def latest_specifications(self):
803
        """See IHasSpecifications."""
804
        return self.specifications(sort=SpecificationSort.DATE, quantity=5)
805
806
    @property
807
    def latest_completed_specifications(self):
808
        """See IHasSpecifications."""
809
        return self.specifications(sort=SpecificationSort.DATE, quantity=5,
11235.5.5 by Curtis Hovey
Hushed lint.
810
            filter=[SpecificationFilter.COMPLETE, ])
3847.2.2 by Mark Shuttleworth
Clean up blueprint pages
811
812
    @property
813
    def specification_count(self):
814
        """See IHasSpecifications."""
815
        return self.specifications(filter=[SpecificationFilter.ALL]).count()
816
817
818
class SpecificationSet(HasSpecificationsMixin):
2344 by Canonical.com Patch Queue Manager
[not r=kiko] specification tracker
819
    """The set of feature specifications."""
820
821
    implements(ISpecificationSet)
822
823
    def __init__(self):
824
        """See ISpecificationSet."""
2900.2.5 by Matthew Paul Thomas
fix self.titles in database/
825
        self.title = 'Specifications registered in Launchpad'
3691.14.3 by Mark Shuttleworth
Tweak layout of sprint and spec goal approval pagees
826
        self.displayname = 'All Specifications'
2344 by Canonical.com Patch Queue Manager
[not r=kiko] specification tracker
827
8805.7.3 by Edwin Grubbs
Optimized specification_status_counts.
828
    def getStatusCountsForProductSeries(self, product_series):
829
        """See `ISpecificationSet`."""
830
        cur = cursor()
831
        condition = """
832
            (Specification.productseries = %s
833
                 OR Milestone.productseries = %s)
834
            """ % sqlvalues(product_series, product_series)
835
        query = """
836
            SELECT Specification.implementation_status, count(*)
837
            FROM Specification
838
                LEFT JOIN Milestone ON Specification.milestone = Milestone.id
839
            WHERE
840
                %s
841
            GROUP BY Specification.implementation_status
842
            """ % condition
843
        cur.execute(query)
844
        return cur.fetchall()
845
5616.1.1 by Aaron Bentley
Fix SpecificationSpec interface violations
846
    @property
847
    def all_specifications(self):
848
        return Specification.select()
849
2344 by Canonical.com Patch Queue Manager
[not r=kiko] specification tracker
850
    def __iter__(self):
851
        """See ISpecificationSet."""
5616.1.1 by Aaron Bentley
Fix SpecificationSpec interface violations
852
        return iter(self.all_specifications)
853
854
    @property
855
    def has_any_specifications(self):
856
        return self.all_specifications.count() != 0
2344 by Canonical.com Patch Queue Manager
[not r=kiko] specification tracker
857
5622.3.18 by Christian Robottom Reis
Implement another optimization for +roadmap by splitting it into its own view, and avoiding prejoining people when unnecessary (by adding a parameter to the specifications API).
858
    def specifications(self, sort=None, quantity=None, filter=None,
859
                       prejoin_people=True):
3691.12.7 by Mark Shuttleworth
Allow for text-based searching of specs, fixing #3811
860
        """See IHasSpecifications."""
861
3691.12.18 by Mark Shuttleworth
Reviewer feedback
862
        # Make a new list of the filter, so that we do not mutate what we
863
        # were passed as a filter
3691.12.7 by Mark Shuttleworth
Allow for text-based searching of specs, fixing #3811
864
        if not filter:
3691.12.18 by Mark Shuttleworth
Reviewer feedback
865
            # When filter is None or [] then we decide the default
3691.12.7 by Mark Shuttleworth
Allow for text-based searching of specs, fixing #3811
866
            # which for a product is to show incomplete specs
867
            filter = [SpecificationFilter.INCOMPLETE]
868
869
        # now look at the filter and fill in the unsaid bits
870
871
        # defaults for completeness: if nothing is said about completeness
872
        # then we want to show INCOMPLETE
873
        completeness = False
874
        for option in [
875
            SpecificationFilter.COMPLETE,
876
            SpecificationFilter.INCOMPLETE]:
877
            if option in filter:
878
                completeness = True
879
        if completeness is False:
880
            filter.append(SpecificationFilter.INCOMPLETE)
3691.109.19 by Francis J. Lacoste
Make Specification implement IBugLinkTarget
881
3691.12.7 by Mark Shuttleworth
Allow for text-based searching of specs, fixing #3811
882
        # defaults for acceptance: in this case we have nothing to do
883
        # because specs are not accepted/declined against a distro
884
885
        # defaults for informationalness: we don't have to do anything
886
        # because the default if nothing is said is ANY
887
888
        # sort by priority descending, by default
889
        if sort is None or sort == SpecificationSort.PRIORITY:
5543.8.11 by Tim Penhey
Lint line length fixes.
890
            order = ['-priority', 'Specification.definition_status',
891
                     'Specification.name']
3691.12.7 by Mark Shuttleworth
Allow for text-based searching of specs, fixing #3811
892
        elif sort == SpecificationSort.DATE:
3847.2.2 by Mark Shuttleworth
Clean up blueprint pages
893
            if SpecificationFilter.COMPLETE in filter:
894
                # if we are showing completed, we care about date completed
895
                order = ['-Specification.date_completed', 'Specification.id']
896
            else:
897
                # if not specially looking for complete, we care about date
898
                # registered
899
                order = ['-Specification.datecreated', 'Specification.id']
3691.12.7 by Mark Shuttleworth
Allow for text-based searching of specs, fixing #3811
900
901
        # figure out what set of specifications we are interested in. for
902
        # products, we need to be able to filter on the basis of:
903
        #
904
        #  - completeness.
905
        #  - informational.
906
        #
3691.109.27 by Francis J. Lacoste
Merge RF
907
3691.73.15 by Mark Shuttleworth
Ensure that specs on inactive products are filtered from listings.
908
        # filter out specs on inactive products
909
        base = """(Specification.product IS NULL OR
910
                   Specification.product NOT IN
911
                    (SELECT Product.id FROM Product
912
                     WHERE Product.active IS FALSE))
913
                """
3691.12.7 by Mark Shuttleworth
Allow for text-based searching of specs, fixing #3811
914
        query = base
915
        # look for informational specs
916
        if SpecificationFilter.INFORMATIONAL in filter:
4476.5.10 by Tom Berger
sql-quote db enum values, don't just rely on them being integers; also other changes after review
917
            query += (' AND Specification.implementation_status = %s ' %
918
                quote(SpecificationImplementationStatus.INFORMATIONAL.value))
3691.12.7 by Mark Shuttleworth
Allow for text-based searching of specs, fixing #3811
919
920
        # filter based on completion. see the implementation of
921
        # Specification.is_complete() for more details
11235.5.5 by Curtis Hovey
Hushed lint.
922
        completeness = Specification.completeness_clause
3691.12.7 by Mark Shuttleworth
Allow for text-based searching of specs, fixing #3811
923
924
        if SpecificationFilter.COMPLETE in filter:
925
            query += ' AND ( %s ) ' % completeness
926
        elif SpecificationFilter.INCOMPLETE in filter:
927
            query += ' AND NOT ( %s ) ' % completeness
928
929
        # Filter for validity. If we want valid specs only then we should
930
        # exclude all OBSOLETE or SUPERSEDED specs
931
        if SpecificationFilter.VALID in filter:
6912.5.19 by Curtis Hovey
Revised XXX comment metadata to remove anonmalies from the XXX report.
932
            # XXX: kiko 2007-02-07: this is untested and was broken.
5543.8.11 by Tim Penhey
Lint line length fixes.
933
            query += (
934
                ' AND Specification.definition_status NOT IN ( %s, %s ) ' %
4476.5.4 by Tom Berger
implementation and tests complete
935
                sqlvalues(SpecificationDefinitionStatus.OBSOLETE,
4476.5.10 by Tom Berger
sql-quote db enum values, don't just rely on them being integers; also other changes after review
936
                          SpecificationDefinitionStatus.SUPERSEDED))
3691.12.7 by Mark Shuttleworth
Allow for text-based searching of specs, fixing #3811
937
938
        # ALL is the trump card
939
        if SpecificationFilter.ALL in filter:
940
            query = base
941
942
        # Filter for specification text
943
        for constraint in filter:
3691.12.17 by Mark Shuttleworth
Reviewer feedback, and fix assumed globally unique names in name validation.
944
            if isinstance(constraint, basestring):
945
                # a string in the filter is a text search filter
3691.12.7 by Mark Shuttleworth
Allow for text-based searching of specs, fixing #3811
946
                query += ' AND Specification.fti @@ ftq(%s) ' % quote(
947
                    constraint)
948
949
        results = Specification.select(query, orderBy=order, limit=quantity)
5622.3.18 by Christian Robottom Reis
Implement another optimization for +roadmap by splitting it into its own view, and avoiding prejoining people when unnecessary (by adding a parameter to the specifications API).
950
        if prejoin_people:
951
            results = results.prejoin(['assignee', 'approver', 'drafter'])
952
        return results
3691.12.7 by Mark Shuttleworth
Allow for text-based searching of specs, fixing #3811
953
3194.1.6 by Diogo Matsubara
Fix https://launchpad.net/products/launchpad/+bug/3566 (Oops from registering a specification with an existing URL)
954
    def getByURL(self, url):
955
        """See ISpecificationSet."""
956
        specification = Specification.selectOneBy(specurl=url)
957
        if specification is None:
3691.109.19 by Francis J. Lacoste
Make Specification implement IBugLinkTarget
958
            return None
2920.2.2 by Diogo Matsubara
Fixes https://launchpad.net/products/launchpad/+bug/3436 (no sensible error message when adding a spec which has already been added.) and fixes https://launchpad.net/products/launchpad/+bug/4122 (The docstring of ISpecification is incorrect)
959
        return specification
960
2396 by Canonical.com Patch Queue Manager
[r=spiv] launchpad support tracker
961
    @property
3847.2.5 by Mark Shuttleworth
Provide listings of upcoming sprints where relevant
962
    def coming_sprints(self):
2445 by Canonical.com Patch Queue Manager
[trivial] db tables for sprints
963
        """See ISpecificationSet."""
11962.3.22 by Guilherme Salgado
Make sure Sprint actually implements IHasSpecifications
964
        from lp.blueprints.model.sprint import Sprint
3847.2.2 by Mark Shuttleworth
Clean up blueprint pages
965
        return Sprint.select("time_ends > 'NOW'", orderBy='time_starts',
2445 by Canonical.com Patch Queue Manager
[trivial] db tables for sprints
966
            limit=5)
967
4476.5.4 by Tom Berger
implementation and tests complete
968
    def new(self, name, title, specurl, summary, definition_status,
2344 by Canonical.com Patch Queue Manager
[not r=kiko] specification tracker
969
        owner, approver=None, product=None, distribution=None, assignee=None,
2736.1.23 by Mark Shuttleworth
�karma restructuring, and test fixes
970
        drafter=None, whiteboard=None,
11962.3.11 by Guilherme Salgado
Remove getAllSpecifications and getValidSpecifications as these were created
971
        priority=SpecificationPriority.UNDEFINED):
2344 by Canonical.com Patch Queue Manager
[not r=kiko] specification tracker
972
        """See ISpecificationSet."""
12498.3.2 by Curtis Hovey
Added NewSpecificationDefinitionStatus to define the set of enums that a new
973
        # Adapt the NewSpecificationDefinitionStatus item to a
974
        # SpecificationDefinitionStatus item.
12498.3.4 by Curtis Hovey
Make factory honour life cycle rules.
975
        status_name = definition_status.name
976
        status_names = NewSpecificationDefinitionStatus.items.mapping.keys()
977
        if status_name not in status_names:
12498.3.2 by Curtis Hovey
Added NewSpecificationDefinitionStatus to define the set of enums that a new
978
            raise AssertionError(
979
                "definition_status must an item found in "
980
                "NewSpecificationDefinitionStatus.")
12498.3.4 by Curtis Hovey
Make factory honour life cycle rules.
981
        definition_status = SpecificationDefinitionStatus.items[status_name]
3691.238.2 by Mark Shuttleworth
Revert spec change, will make that in my own branch.
982
        return Specification(name=name, title=title, specurl=specurl,
4476.5.4 by Tom Berger
implementation and tests complete
983
            summary=summary, priority=priority,
984
            definition_status=definition_status, owner=owner,
985
            approver=approver, product=product, distribution=distribution,
11962.3.11 by Guilherme Salgado
Remove getAllSpecifications and getValidSpecifications as these were created
986
            assignee=assignee, drafter=drafter, whiteboard=whiteboard)
2344 by Canonical.com Patch Queue Manager
[not r=kiko] specification tracker
987
5729.1.1 by Tom Berger
optimize the roadmap page
988
    def getDependencyDict(self, specifications):
989
        """See `ISpecificationSet`."""
6527.1.4 by Brad Crittenden
Reverted extra change to getDependencyDict that caused problems.
990
        specification_ids = [spec.id for spec in specifications]
991
992
        if len(specification_ids) == 0:
6527.1.2 by Brad Crittenden
Add ordering of results from getDependencyDict to sort by name and id also.
993
            return {}
994
5821.16.5 by Francis J. Lacoste
Use Storm execute instead of cursor().
995
        results = Store.of(specifications[0]).execute("""
5729.1.1 by Tom Berger
optimize the roadmap page
996
            SELECT SpecificationDependency.specification,
997
                   SpecificationDependency.dependency
998
            FROM SpecificationDependency, Specification
999
            WHERE SpecificationDependency.specification IN %s
1000
            AND SpecificationDependency.dependency = Specification.id
11235.5.5 by Curtis Hovey
Hushed lint.
1001
            ORDER BY Specification.priority DESC, Specification.name,
1002
                     Specification.id
5821.14.11 by James Henstridge
Make ISpecificationSet.getDependencyDict() work with result sets as well
1003
        """ % sqlvalues(specification_ids)).get_all()
5729.1.1 by Tom Berger
optimize the roadmap page
1004
1005
        dependencies = {}
1006
        for spec_id, dep_id in results:
1007
            if spec_id not in dependencies:
1008
                dependencies[spec_id] = []
1009
            dependency = Specification.get(dep_id)
1010
            dependencies[spec_id].append(dependency)
1011
1012
        return dependencies
5729.1.2 by Tom Berger
changes after a review by curtis
1013
1014
    def get(self, spec_id):
10409.5.69 by Curtis Hovey
Removed answers and blueprints from canonical.launchpad.__init__.
1015
        """See lp.blueprints.interfaces.specification.ISpecificationSet."""
5729.1.2 by Tom Berger
changes after a review by curtis
1016
        return Specification.get(spec_id)