~launchpad-pqm/launchpad/devel

6414.2.1 by Guilherme Salgado
Database/Interface code for ProductSeries.status.
1
= ProductSeries =
2879.1.1 by Diogo Matsubara
Fixes https://launchpad.net/products/launchpad/+bug/1543 Filing duplicate source details raises an integrityerror instead of a nice error
2
3614.1.68 by Brad Bollenbach
reapply MaloneReleaseManagement
3
A Launchpad Product models a single piece of software. However for
4
release management purposes, a Product often has to be split in several
5
discrete entities which must be considered separately for packaging,
6
translations, version control, etc. We call these entities
7
ProductSeries.
2840.1.7 by David Allouche
[merge conflict] rocketfuel
8
6414.2.1 by Guilherme Salgado
Database/Interface code for ProductSeries.status.
9
    >>> from zope.component import getUtility
14600.2.2 by Curtis Hovey
Moved webapp to lp.services.
10
    >>> from lp.services.webapp.testing import verifyObject
11716.1.6 by Curtis Hovey
Converted glob imports in doctests to import for the true module.
11
    >>> from lp.registry.interfaces.person import IPersonSet
11716.1.12 by Curtis Hovey
Sorted imports in doctests.
12
    >>> from lp.registry.interfaces.product import IProductSet
11716.1.6 by Curtis Hovey
Converted glob imports in doctests to import for the true module.
13
    >>> from lp.registry.interfaces.productseries import IProductSeries
11818.3.1 by Jeroen Vermeulen
Split out lp.translations.enums and lp.translations.interfaces.hastranslationimports.
14
    >>> from lp.translations.interfaces.hastranslationimports import (
8751.1.21 by Danilo Šegan
Fix up interface imports for a few external tests.
15
    ...     IHasTranslationImports)
14606.3.1 by William Grant
Merge canonical.database into lp.services.database.
16
    >>> from lp.services.database.sqlbase import flush_database_updates
2840.1.7 by David Allouche
[merge conflict] rocketfuel
17
18
First, get a product that has some ProductSeries in the sample data.
19
6414.2.1 by Guilherme Salgado
Database/Interface code for ProductSeries.status.
20
    >>> productset = getUtility(IProductSet)
21
    >>> firefox = productset['firefox']
2840.1.7 by David Allouche
[merge conflict] rocketfuel
22
3614.1.68 by Brad Bollenbach
reapply MaloneReleaseManagement
23
A ProductSeries can be retrieved using the associated product and the
24
series name.
2840.1.7 by David Allouche
[merge conflict] rocketfuel
25
6414.2.1 by Guilherme Salgado
Database/Interface code for ProductSeries.status.
26
    >>> trunk = firefox.getSeries('trunk')
2840.1.7 by David Allouche
[merge conflict] rocketfuel
27
28
Verify that the resulting object correctly implements the IProductSeries
29
interface.
30
6414.2.1 by Guilherme Salgado
Database/Interface code for ProductSeries.status.
31
    >>> verifyObject(IProductSeries, trunk)
32
    True
33
    >>> IProductSeries.providedBy(trunk)
34
    True
2840.1.7 by David Allouche
[merge conflict] rocketfuel
35
4268.3.20 by Carlos Perello Marin
All objects that implemente IHasTranslationImports check it now
36
and IHasTranslationImports.
37
6414.2.1 by Guilherme Salgado
Database/Interface code for ProductSeries.status.
38
    >>> verifyObject(IHasTranslationImports, trunk)
39
    True
40
    >>> IHasTranslationImports.providedBy(trunk)
41
    True
4268.3.20 by Carlos Perello Marin
All objects that implemente IHasTranslationImports check it now
42
2840.1.7 by David Allouche
[merge conflict] rocketfuel
43
And verify that it looks like the series we think it should be.
44
6414.2.1 by Guilherme Salgado
Database/Interface code for ProductSeries.status.
45
    >>> trunk.product == firefox
46
    True
8942.3.2 by Curtis Hovey
Revised the grammar and clarity of a pair of tests.
47
    >>> print trunk.name
48
    trunk
2840.1.7 by David Allouche
[merge conflict] rocketfuel
49
50
It's also possible to ask a product for all its associated series.
51
6414.2.1 by Guilherme Salgado
Database/Interface code for ProductSeries.status.
52
    >>> onedotzero = firefox.getSeries('1.0')
9760.8.1 by Brad Crittenden
Change the non-English 'serieses' to 'series' throughout our codebase.
53
    >>> list(firefox.series) == [onedotzero, trunk]
6414.2.1 by Guilherme Salgado
Database/Interface code for ProductSeries.status.
54
    True
2840.1.7 by David Allouche
[merge conflict] rocketfuel
55
3614.1.68 by Brad Bollenbach
reapply MaloneReleaseManagement
56
A ProductSeries can also be fetched with the IProductSeriesSet utility.
57
11692.6.2 by Curtis Hovey
Use deglober to fixing simple glob imports in doctests.
58
    >>> from lp.registry.interfaces.productseries import IProductSeriesSet
3614.1.68 by Brad Bollenbach
reapply MaloneReleaseManagement
59
6414.2.1 by Guilherme Salgado
Database/Interface code for ProductSeries.status.
60
    >>> firefox_1_0 = getUtility(IProductSeriesSet).get(2)
61
    >>> print firefox_1_0.product.name
62
    firefox
63
    >>> print firefox_1_0.name
64
    1.0
3614.1.68 by Brad Bollenbach
reapply MaloneReleaseManagement
65
8322.10.1 by Curtis Hovey
Allow product drivers to create series.
66
New ProductSeries are created using Product.newSeries(). Only the product
67
owner or driver can call Product.newSeries().
2840.1.7 by David Allouche
[merge conflict] rocketfuel
68
8322.10.1 by Curtis Hovey
Allow product drivers to create series.
69
    >>> series_driver = factory.makePerson(name="driver")
70
    >>> summary = "Port of Firefox to the Emacs operating system."
71
    >>> emacs = firefox.newSeries(series_driver , 'emacs', summary)
6935.4.1 by Edwin Grubbs
Mostly working
72
    Traceback (most recent call last):
73
    ...
8322.10.1 by Curtis Hovey
Allow product drivers to create series.
74
    Unauthorized: (..., 'newSeries', 'launchpad.Driver')
75
76
    >>> login_person(firefox.owner)
9899.1.1 by Curtis Hovey
Fixed Product.addSeries to support the releasefileglob argument.
77
    >>> emacs_series = firefox.newSeries(
78
    ...     firefox.owner , 'emacs', summary,
79
    ...     releasefileglob='ftp://gnu.org/emacs*.gz')
80
    >>> print emacs_series.name
81
    emacs
82
83
    >>> print emacs_series.summary
84
    Port of Firefox to the Emacs operating system.
85
86
    >>> print emacs_series.releasefileglob
87
    ftp://gnu.org/emacs*.gz
8322.10.1 by Curtis Hovey
Allow product drivers to create series.
88
9017.1.10 by Curtis Hovey
Pushed productseries driver rule into the model.
89
When a driver creates a series, he is also the driver of the new series
90
to make him the release manager.
91
8322.10.1 by Curtis Hovey
Allow product drivers to create series.
92
    >>> firefox.driver = series_driver
93
    >>> login_person(series_driver)
94
    >>> emacs2 = firefox.newSeries(series_driver , 'emacs2', summary)
9017.1.10 by Curtis Hovey
Pushed productseries driver rule into the model.
95
    >>> print emacs2.driver.name
96
    driver
8322.10.1 by Curtis Hovey
Allow product drivers to create series.
97
98
A newly created series is assumed to be in the development state.
99
6935.4.1 by Edwin Grubbs
Mostly working
100
    >>> login(ANONYMOUS)
8942.3.2 by Curtis Hovey
Revised the grammar and clarity of a pair of tests.
101
    >>> print emacs_series.status.title
102
    Active Development
2840.1.7 by David Allouche
[merge conflict] rocketfuel
103
104
Let's check that the new series is properly associated to its product.
105
6414.2.1 by Guilherme Salgado
Database/Interface code for ProductSeries.status.
106
    >>> flush_database_updates()
8942.3.1 by Curtis Hovey
Allow release managers to edit ProductSeries, milestones, and releases.
107
    >>> firefox.getSeries('emacs') == emacs_series
108
    True
109
110
111
= Drivers and release managers =
112
10326.1.1 by Henning Eggers
Mechanically renamed IProject* to IProjectGroup*.
113
A driver for an IProduct or IProjectGroup cannot modify a product series.
8942.3.1 by Curtis Hovey
Allow release managers to edit ProductSeries, milestones, and releases.
114
14600.2.2 by Curtis Hovey
Moved webapp to lp.services.
115
    >>> from lp.services.webapp.authorization import check_permission
8942.3.1 by Curtis Hovey
Allow release managers to edit ProductSeries, milestones, and releases.
116
117
    >>> login_person(series_driver)
118
    >>> print emacs_series.owner.name
119
    name12
120
    >>> print emacs_series.driver
121
    None
122
    >>> check_permission('launchpad.Edit', emacs_series)
123
    False
124
125
A person appointed to the series driver has the release manager role and can
126
edit a product series.
127
128
    >>> login_person(firefox.owner)
129
    >>> emacs_series.driver = series_driver
130
    >>> login_person(series_driver)
131
    >>> check_permission('launchpad.Edit', emacs_series)
132
    True
133
134
    >>> login(ANONYMOUS)
2879.1.1 by Diogo Matsubara
Fixes https://launchpad.net/products/launchpad/+bug/1543 Filing duplicate source details raises an integrityerror instead of a nice error
135
6414.2.1 by Guilherme Salgado
Database/Interface code for ProductSeries.status.
136
8848.3.3 by Curtis Hovey
Ensure a glob (*) is in the productseries.releassefileglob.
137
== ProductSeries releassefileglob ==
138
139
Each ProductSeries may have a releassefileglob that describes the location
140
of where release files are uploaded to. The product release finder process
141
uses the releassefileglob to locate and retrieve files. The files are stored
142
in the librarian. Each fill is associated with a release. If the series
143
does not have a release for version in the file name, the finder will create
144
it. The finder will also create the series milestone too if it does not
145
exist. The success of product release finder to retrieve files, and create
146
milestone and releases, is largely predicated on the quality of the
147
releassefileglob.
148
149
The field is constrained by the validate_release_glob() function. It verifies
150
that the url uses one of the supported schemes (ftp, http, http).
151
152
    >>> from lp.registry.interfaces.productseries import (
153
    ...     validate_release_glob)
154
155
    >>> validate_release_glob('ftp://ftp.gnu.org/gnu/emacs/emacs-21.*.gz')
156
    True
157
    >>> validate_release_glob('http://ftp.gnu.org/gnu/emacs/emacs-21.*.gz')
158
    True
159
    >>> validate_release_glob('https://ftp.gnu.org/gnu/emacs/emacs-21.*.gz')
160
    True
161
162
Invalid URLs and unsupported schemes raise a LaunchpadValidationError.
163
164
    >>> validate_release_glob('ftp.gnu.org/gnu/emacs/emacs-21.*.gz')
165
    Traceback (most recent call last):
166
     ...
167
    LaunchpadValidationError: ...
168
169
    >>> validate_release_glob('wais://ftp.gnu.org/gnu/emacs/emacs-21.*.gz')
170
    Traceback (most recent call last):
171
     ...
172
    LaunchpadValidationError: ...
173
174
The URL must contain a glob (*) or , and may contain more than one.
175
176
    >>> validate_release_glob('http://ftp.gnu.org/gnu/emacs/emacs-21.10.1.gz')
177
    Traceback (most recent call last):
178
     ...
179
    LaunchpadValidationError: ...
180
181
    >>> validate_release_glob('http://ftp.gnu.org/gnu/*/emacs-21.*.gz')
182
    True
183
184
6414.2.1 by Guilherme Salgado
Database/Interface code for ProductSeries.status.
185
== Specification Listings ==
3348.1.33 by Mark Shuttleworth
Additional tests along with better registry pages.
186
187
We should be able to get lists of specifications in different states
188
related to a productseries.
189
3614.1.68 by Brad Bollenbach
reapply MaloneReleaseManagement
190
Basically, we can filter by completeness, and by whether or not the spec
191
is informational.
3348.1.33 by Mark Shuttleworth
Additional tests along with better registry pages.
192
6414.2.1 by Guilherme Salgado
Database/Interface code for ProductSeries.status.
193
    >>> onezero = firefox.getSeries("1.0")
11824.1.3 by Tim Penhey
Fix imports for SpecificationFilter.
194
    >>> from lp.blueprints.enums import SpecificationFilter
3348.1.33 by Mark Shuttleworth
Additional tests along with better registry pages.
195
3348.1.38 by Mark Shuttleworth
More sophisticated default filtering for specification lists
196
We will create two specs for onezero and use them to demonstrate the
197
filtering.
198
14606.3.1 by William Grant
Merge canonical.database into lp.services.database.
199
    >>> from lp.services.database.constants import UTC_NOW
6414.2.1 by Guilherme Salgado
Database/Interface code for ProductSeries.status.
200
    >>> carlos = getUtility(IPersonSet).getByName('carlos')
8971.22.1 by Guilherme Salgado
Remove all 'from lp.registry.model.<module> import *' lines from canonical.launchpad.database.__init__
201
    >>> from lp.blueprints.model.specification import Specification
6414.2.1 by Guilherme Salgado
Database/Interface code for ProductSeries.status.
202
    >>> a = Specification(name='a', title='A', summary='AA', owner=carlos,
203
    ...                   product=firefox, productseries=onezero,
204
    ...                   specurl='http://wbc.com/two', goal_proposer=carlos,
205
    ...                   date_goal_proposed=UTC_NOW)
206
    >>> b = Specification(name='b', title='b', summary='bb', owner=carlos,
207
    ...                   product=firefox, productseries=onezero,
208
    ...                   specurl='http://fds.com/adsf', goal_proposer=carlos,
209
    ...                   date_goal_proposed=UTC_NOW)
3348.1.38 by Mark Shuttleworth
More sophisticated default filtering for specification lists
210
3614.1.68 by Brad Bollenbach
reapply MaloneReleaseManagement
211
Now, we will make one of them accepted, the other declined, and both of
212
them informational.
3348.1.38 by Mark Shuttleworth
More sophisticated default filtering for specification lists
213
11824.1.2 by Tim Penhey
Fix imports for SpecificationDefinitionStatus.
214
    >>> from lp.blueprints.enums import (
11716.1.12 by Curtis Hovey
Sorted imports in doctests.
215
    ...     SpecificationDefinitionStatus,
216
    ...     SpecificationImplementationStatus,
217
    ...     )
6414.2.1 by Guilherme Salgado
Database/Interface code for ProductSeries.status.
218
    >>> a.definition_status = b.definition_status = SpecificationDefinitionStatus.APPROVED
219
    >>> a.implementation_status = SpecificationImplementationStatus.INFORMATIONAL
220
    >>> b.implementation_status = SpecificationImplementationStatus.INFORMATIONAL
221
    >>> a.acceptBy(a.owner)
222
    >>> shim = a.updateLifecycleStatus(a.owner)
223
    >>> b.declineBy(b.owner)
224
    >>> shim = b.updateLifecycleStatus(b.owner)
3348.1.38 by Mark Shuttleworth
More sophisticated default filtering for specification lists
225
14606.3.1 by William Grant
Merge canonical.database into lp.services.database.
226
    >>> from lp.services.database.sqlbase import flush_database_updates
6414.2.1 by Guilherme Salgado
Database/Interface code for ProductSeries.status.
227
    >>> flush_database_updates()
3348.1.38 by Mark Shuttleworth
More sophisticated default filtering for specification lists
228
229
If we ask for ALL specs we should see them both.
230
6414.2.1 by Guilherme Salgado
Database/Interface code for ProductSeries.status.
231
    >>> filter = [SpecificationFilter.ALL]
232
    >>> for s in onezero.specifications(filter=filter):
233
    ...     print s.name
234
    a
235
    b
3348.1.38 by Mark Shuttleworth
More sophisticated default filtering for specification lists
236
3614.1.68 by Brad Bollenbach
reapply MaloneReleaseManagement
237
With a productseries, we can ask for ACCEPTED, PROPOSED and DECLINED
238
specs:
3348.1.38 by Mark Shuttleworth
More sophisticated default filtering for specification lists
239
6414.2.1 by Guilherme Salgado
Database/Interface code for ProductSeries.status.
240
    >>> filter=[SpecificationFilter.ACCEPTED]
241
    >>> for spec in onezero.specifications(filter=filter):
242
    ...     print spec.name, spec.goalstatus.title
243
    a Accepted
244
245
    >>> filter=[SpecificationFilter.PROPOSED]
246
    >>> onezero.specifications(filter=filter).count()
247
    0
248
249
    >>> filter=[SpecificationFilter.DECLINED]
250
    >>> onezero.specifications(filter=filter).count()
251
    1
3614.1.68 by Brad Bollenbach
reapply MaloneReleaseManagement
252
253
We should see one informational spec if we ask just for that, the
254
accepted one.
3348.1.33 by Mark Shuttleworth
Additional tests along with better registry pages.
255
6414.2.1 by Guilherme Salgado
Database/Interface code for ProductSeries.status.
256
    >>> filter = [SpecificationFilter.INFORMATIONAL]
257
    >>> for s in onezero.specifications(filter=filter):
258
    ...     print s.name
259
    a
3348.1.38 by Mark Shuttleworth
More sophisticated default filtering for specification lists
260
261
If we specifically ask for declined informational, we will get that:
262
6414.2.1 by Guilherme Salgado
Database/Interface code for ProductSeries.status.
263
    >>> filter = [
264
    ...    SpecificationFilter.INFORMATIONAL, SpecificationFilter.DECLINED]
265
    >>> for s in onezero.specifications(filter=filter):
266
    ...     print s.name
267
    b
3348.1.38 by Mark Shuttleworth
More sophisticated default filtering for specification lists
268
269
There are is one completed, accepted spec for 1.0:
3348.1.33 by Mark Shuttleworth
Additional tests along with better registry pages.
270
6414.2.1 by Guilherme Salgado
Database/Interface code for ProductSeries.status.
271
    >>> filter = [SpecificationFilter.COMPLETE]
272
    >>> for spec in onezero.specifications(filter=filter):
273
    ...    print spec.name, spec.is_complete, spec.goalstatus.title
274
    a True Accepted
3348.1.38 by Mark Shuttleworth
More sophisticated default filtering for specification lists
275
276
There is one completed, declined spec:
277
6414.2.1 by Guilherme Salgado
Database/Interface code for ProductSeries.status.
278
    >>> filter = [SpecificationFilter.COMPLETE, SpecificationFilter.DECLINED]
279
    >>> for spec in onezero.specifications(filter=filter):
280
    ...    print spec.name, spec.is_complete, spec.goalstatus.title
281
    b True Declined
3348.1.38 by Mark Shuttleworth
More sophisticated default filtering for specification lists
282
283
Now lets make b incomplete, but accepted.
284
6414.2.1 by Guilherme Salgado
Database/Interface code for ProductSeries.status.
285
    >>> b.implementation_status = SpecificationImplementationStatus.BETA
286
    >>> b.definition_status = SpecificationDefinitionStatus.NEW
287
    >>> shim = b.acceptBy(b.owner)
288
    >>> shim = b.updateLifecycleStatus(b.owner)
289
    >>> flush_database_updates()
3348.1.38 by Mark Shuttleworth
More sophisticated default filtering for specification lists
290
3614.1.68 by Brad Bollenbach
reapply MaloneReleaseManagement
291
And if we ask just for specs, we get BOTH the incomplete and the
292
complete ones that have been accepted.
3348.1.38 by Mark Shuttleworth
More sophisticated default filtering for specification lists
293
6414.2.1 by Guilherme Salgado
Database/Interface code for ProductSeries.status.
294
    >>> for spec in onezero.specifications():
295
    ...     print spec.name, spec.is_complete, spec.goalstatus.title
296
    a True Accepted
297
    b False Accepted
3348.1.33 by Mark Shuttleworth
Additional tests along with better registry pages.
298
3691.12.17 by Mark Shuttleworth
Reviewer feedback, and fix assumed globally unique names in name validation.
299
We can search for text in specifications (in this case there are no
300
matches):
301
6414.2.1 by Guilherme Salgado
Database/Interface code for ProductSeries.status.
302
    >>> print len(list(onezero.specifications(filter=['new'])))
303
    0
304
305
306
== Lifecycle Management ==
3691.73.11 by Mark Shuttleworth
Test fixes
307
3691.74.1 by Mark Shuttleworth
Track spec starting times and people.
308
In the example above, we use the acceptBy and updateLifecycleStatus methods on
3691.73.11 by Mark Shuttleworth
Test fixes
309
a specification. These help us keep the full record of who moved the spec
310
through each relevant stage of its existence.
311
6414.2.1 by Guilherme Salgado
Database/Interface code for ProductSeries.status.
312
    >>> b.goal_decider is None
313
    False
314
    >>> b.goal_decider.name
315
    u'carlos'
316
    >>> b.date_completed is None
317
    True
3691.73.11 by Mark Shuttleworth
Test fixes
318
319
There's a method which will tell us if status changes we have just made will
320
change the overall state of the spec to "completed".
321
6414.2.1 by Guilherme Salgado
Database/Interface code for ProductSeries.status.
322
    >>> jdub = getUtility(IPersonSet).getByName('jdub')
323
    >>> b.definition_status = SpecificationDefinitionStatus.APPROVED
324
    >>> b.implementation_status = SpecificationImplementationStatus.INFORMATIONAL
325
    >>> print b.updateLifecycleStatus(jdub).title
326
    Complete
327
    >>> b.completer.name
328
    u'jdub'
329
    >>> b.date_completed is None
330
    False
331
332
333
== Drivers ==
3348.1.33 by Mark Shuttleworth
Additional tests along with better registry pages.
334
3614.1.68 by Brad Bollenbach
reapply MaloneReleaseManagement
335
Products, projects and product series have drivers, who are people that
336
have permission to approve bugs and features for specific releases. The
337
rules are that:
3348.1.33 by Mark Shuttleworth
Additional tests along with better registry pages.
338
10724.1.1 by Henning Eggers
First batch of Project -> ProjectGrpoup renamings.
339
 1. a "driver" can be set on either ProjectGroup, Product or ProductSeries
3614.1.68 by Brad Bollenbach
reapply MaloneReleaseManagement
340
341
 2. drivers are only actually relevant on a ProductSeries, because thats
342
    the granularity at which we track spec/bug targeting
343
3348.1.33 by Mark Shuttleworth
Additional tests along with better registry pages.
344
 3. the important attribute is ".drivers" on a productseries, it is
345
    calculated based on the combination of owners and drivers in the
3614.1.68 by Brad Bollenbach
reapply MaloneReleaseManagement
346
    series, product and project. It is a LIST of drivers, which might be
347
    empty, or have one, two or three people/teams in it.
348
349
 4. the list includes the explicitly set drivers from series, product
350
    and project
351
3348.1.33 by Mark Shuttleworth
Additional tests along with better registry pages.
352
 5. if there are no explicitly set drivers, then:
353
      - if there is a project, then the list is the project.owner
3614.1.68 by Brad Bollenbach
reapply MaloneReleaseManagement
354
      - if there is no project, then the list is the product.owner in
355
    other words, we use the "highest" owner as the fallback, which is
3348.1.33 by Mark Shuttleworth
Additional tests along with better registry pages.
356
    either the product owner or the project owner if there is a project.
357
3614.1.68 by Brad Bollenbach
reapply MaloneReleaseManagement
358
We test these rules below. We will create the project, product and
359
series directly so that we don't have to deal with security permissions
360
checks when setting and resetting the driver attributes.
3348.1.33 by Mark Shuttleworth
Additional tests along with better registry pages.
361
14606.3.1 by William Grant
Merge canonical.database into lp.services.database.
362
    >>> from lp.services.database.sqlbase import flush_database_updates
6786.2.4 by Brad Crittenden
Added registrant to product and project and created ValidPersonVocabulary.
363
    >>> login('foo.bar@canonical.com')
6414.2.1 by Guilherme Salgado
Database/Interface code for ProductSeries.status.
364
    >>> carlos = getUtility(IPersonSet).getByName('carlos')
9105.3.1 by Brad Crittenden
Changed sample data to remove sabdfl.
365
    >>> mark = getUtility(IPersonSet).getByName('mark')
6414.2.1 by Guilherme Salgado
Database/Interface code for ProductSeries.status.
366
    >>> jblack = getUtility(IPersonSet).getByName('jblack')
6786.2.4 by Brad Crittenden
Added registrant to product and project and created ValidPersonVocabulary.
367
368
    >>> project = factory.makeProject(name='testproj',
369
    ...     displayname='Test Project',
6414.2.1 by Guilherme Salgado
Database/Interface code for ProductSeries.status.
370
    ...     title='Test Project Title', homepageurl='http://foo.com/url',
6786.2.4 by Brad Crittenden
Added registrant to product and project and created ValidPersonVocabulary.
371
    ...     summary='summary', description='description', owner=carlos)
9105.3.1 by Brad Crittenden
Changed sample data to remove sabdfl.
372
    >>> product = factory.makeProduct(owner=mark, name='testprod',
6414.2.1 by Guilherme Salgado
Database/Interface code for ProductSeries.status.
373
    ...     displayname='Test Product', title='Test product title',
374
    ...     summary='summary', project=project)
6786.2.4 by Brad Crittenden
Added registrant to product and project and created ValidPersonVocabulary.
375
    >>> series = factory.makeProductSeries(owner=jblack, name='1.0', product=product,
6414.2.1 by Guilherme Salgado
Database/Interface code for ProductSeries.status.
376
    ...     summary='Series summary')
3348.1.33 by Mark Shuttleworth
Additional tests along with better registry pages.
377
6786.2.4 by Brad Crittenden
Added registrant to product and project and created ValidPersonVocabulary.
378
3614.1.68 by Brad Bollenbach
reapply MaloneReleaseManagement
379
First, lets see what we get for the series drivers before we have
6786.2.4 by Brad Crittenden
Added registrant to product and project and created ValidPersonVocabulary.
380
anything actually set.
3348.1.33 by Mark Shuttleworth
Additional tests along with better registry pages.
381
382
If there is a project on the product, we would expect the project owner:
383
6414.2.1 by Guilherme Salgado
Database/Interface code for ProductSeries.status.
384
    >>> print series.product.project.name
385
    testproj
386
    >>> for d in series.drivers:
387
    ...     print d.name
388
    carlos
3348.1.33 by Mark Shuttleworth
Additional tests along with better registry pages.
389
390
If there is NO project on the product, then we expect the product owner:
391
6414.2.1 by Guilherme Salgado
Database/Interface code for ProductSeries.status.
392
    >>> product.project = None
393
    >>> for d in series.drivers:
394
    ...     print d.name
9105.3.1 by Brad Crittenden
Changed sample data to remove sabdfl.
395
    mark
3348.1.33 by Mark Shuttleworth
Additional tests along with better registry pages.
396
397
Now lets put the project back:
398
6414.2.1 by Guilherme Salgado
Database/Interface code for ProductSeries.status.
399
    >>> product.project = project.id
400
    >>> flush_database_updates()
3348.1.33 by Mark Shuttleworth
Additional tests along with better registry pages.
401
10427.23.18 by Henning Eggers
Reviewer comments and suggestions.
402
Edgar and cprov will be the drivers.
3348.1.33 by Mark Shuttleworth
Additional tests along with better registry pages.
403
6414.2.1 by Guilherme Salgado
Database/Interface code for ProductSeries.status.
404
    >>> cprov = getUtility(IPersonSet).getByName('cprov')
405
    >>> edgar = getUtility(IPersonSet).getByName('edgar')
10427.23.14 by Henning Eggers
Added unit test for product series and moved some functional test into it.
406
407
Edgar becomes the driver of the project group and thus also drives the
408
series.
409
410
    >>> project.driver = edgar
6414.2.1 by Guilherme Salgado
Database/Interface code for ProductSeries.status.
411
    >>> for d in series.drivers:
412
    ...     print d.name
413
    edgar
414
10427.23.14 by Henning Eggers
Added unit test for product series and moved some functional test into it.
415
In addition cprov is made driver of the series. Both are drivers now.
416
6414.2.1 by Guilherme Salgado
Database/Interface code for ProductSeries.status.
417
    >>> series.driver = cprov
418
    >>> for d in series.drivers:
419
    ...     print d.name
10427.23.14 by Henning Eggers
Added unit test for product series and moved some functional test into it.
420
    cprov
421
    edgar
422
423
With just a driver on the series, the owner of the project group is reported
424
as driver, too.
425
426
    >>> project.driver = None
427
    >>> for d in series.drivers:
428
    ...     print d.name
6414.2.1 by Guilherme Salgado
Database/Interface code for ProductSeries.status.
429
    carlos
430
    cprov
3348.1.33 by Mark Shuttleworth
Additional tests along with better registry pages.
431
10427.23.18 by Henning Eggers
Reviewer comments and suggestions.
432
Without a project group, the driver role falls back to the product owner.
3348.1.33 by Mark Shuttleworth
Additional tests along with better registry pages.
433
10427.23.14 by Henning Eggers
Added unit test for product series and moved some functional test into it.
434
    >>> product.project = None
6414.2.1 by Guilherme Salgado
Database/Interface code for ProductSeries.status.
435
    >>> for d in series.drivers:
436
    ...     print d.name
437
    cprov
9105.3.1 by Brad Crittenden
Changed sample data to remove sabdfl.
438
    mark