Merge lp:~adiroiban/launchpad/bug-531261 into lp:launchpad

Proposed by Adi Roiban
Status: Merged
Approved by: Guilherme Salgado
Approved revision: no longer in the source branch.
Merged at revision: not available
Proposed branch: lp:~adiroiban/launchpad/bug-531261
Merge into: lp:launchpad
Diff against target: 595 lines (+150/-152)
8 files modified
lib/lp/registry/interfaces/distroseries.py (+11/-39)
lib/lp/registry/interfaces/productseries.py (+6/-41)
lib/lp/registry/interfaces/series.py (+53/-1)
lib/lp/registry/model/distroseries.py (+4/-38)
lib/lp/registry/model/productseries.py (+7/-26)
lib/lp/registry/model/series.py (+53/-0)
lib/lp/registry/stories/webservice/xx-distroseries.txt (+1/-0)
lib/lp/registry/stories/webservice/xx-project-registry.txt (+15/-7)
To merge this branch: bzr merge lp:~adiroiban/launchpad/bug-531261
Reviewer Review Type Date Requested Status
Guilherme Salgado (community) Approve
Review via email: mp+20748@code.launchpad.net

Commit message

Move ISeriesMixin to lp.registry.interfaces.series together with summary, bug_supervisor, security_contact and drivers attributes.

Description of the change

= Bug 531261 =

Right now ISeriesMixin is located in lp.registry.interfaces.distroseries and is only used by DistroSeries.

Since this Mixin should be used for DistroSeries and ProductSeries common attributes, we should move it to lp.registry.interfaces.series, and make ProductSeries use it.

We can also see what other attributes can be moved to ISeriesMixin

== Proposed fix ==

As a start, move summary, bug_supervisor, security_contact and drivers in the common interface and mixin.

The other related bugs can be found here:
https://bugs.edge.launchpad.net/launchpad-registry/+bugs?field.tag=iseries

== Pre-implementation notes ==
Curtis has added his comments in the report for bug 531261.

== Implementation details ==

I don't know how to fix pylint warning about lazr modules. Any hint is much appreciated.

== Tests ==
./bin/test -t series

== Demo and Q/A ==

Since this is only a cleanup, there is no new functionality to test.

= Launchpad lint =

Checking for conflicts. and issues in doctests and templates.
Running jslint, xmllint, pyflakes, and pylint.
Using normal rules.

Linting changed files:
  lib/lp/registry/interfaces/distroseries.py

== Pylint notices ==

lib/lp/registry/interfaces/distroseries.py
    22: [F0401] Unable to import 'lazr.enum' (No module named enum)
    23: [F0401] Unable to import 'lazr.restful.declarations' (No module named restful)
    28: [F0401] Unable to import 'lazr.restful.fields' (No module named restful)
    112: [E1002, DistroSeriesVersionField._validate] Use super on an old style class
    423: [C0322, IDistroSeriesPublic.getPackageUploads] Operator not preceded by a space
    description=_(
    ^
    "Return items that are more recent than this timestamp."),
    required=False),
    status=Choice(

    vocabulary=DBEnumeratedType,
    title=_("Package Upload Status"),
    description=_("Return only items that have this status."),
    required=False),
    archive=Reference(

    schema=Interface,
    title=_("Archive"),
    description=_("Return only items for this archive."),
    required=False),
    pocket=Choice(

    vocabulary=DBEnumeratedType,
    title=_("Pocket"),
    description=_("Return only items targeted to this pocket"),
    required=False),
    custom_type=Choice(

    vocabulary=DBEnumeratedType,
    title=_("Custom Type"),
    description=_("Return only items with custom files of this "
    "type."),
    required=False),
    )

    @operation_returns_collection_of(Interface)
    @export_read_operation()
    def getPackageUploads(created_since_date, status, archive, pocket,
    custom_type):

To post a comment you must log in.
Revision history for this message
Guilherme Salgado (salgado) wrote :
Download full text (3.8 KiB)

Hi Adi,

This is a nice refactoring; thanks for doing it.

There's just one thing I noticed which was not mentioned in your cover
letter: the IProductSeries interface has grown a 'active' attribute,
thanks to ISeriesMixin. Since your branch doesn't add any code that
uses that attribute, I don't see a reason for adding it, so I'd suggest
moving it to IDistroSeries and the actual implementation to
DistroSeries. Unless you do plan to use IProductSeries.active
somewhere?

On Fri, 2010-03-05 at 13:51 +0000, Adi Roiban wrote:
> Adi Roiban has proposed merging lp:~adiroiban/launchpad/bug-531261
> into lp:launchpad/devel.
>
[...]
> == Implementation details ==
>
> I don't know how to fix pylint warning about lazr modules. Any hint is
> much appreciated.

These are spurious, but I don't know how to silence pylint,
unfortunately.

>
> == Tests ==
> ./bin/test -t series
>
> == Demo and Q/A ==
>
> Since this is only a cleanup, there is no new functionality to test.
>
> = Launchpad lint =
>
> Checking for conflicts. and issues in doctests and templates.
> Running jslint, xmllint, pyflakes, and pylint.
> Using normal rules.
>
> Linting changed files:
> lib/lp/registry/interfaces/distroseries.py
>
>
> == Pylint notices ==
>
> lib/lp/registry/interfaces/distroseries.py
> 22: [F0401] Unable to import 'lazr.enum' (No module named enum)
> 23: [F0401] Unable to import 'lazr.restful.declarations' (No
> module named restful)
> 28: [F0401] Unable to import 'lazr.restful.fields' (No module
> named restful)
> 112: [E1002, DistroSeriesVersionField._validate] Use super on an
> old style class

> === modified file 'lib/lp/registry/interfaces/series.py'
> --- lib/lp/registry/interfaces/series.py 2009-12-13 11:55:40 +0000
> +++ lib/lp/registry/interfaces/series.py 2010-03-05 13:51:28 +0000
> @@ -76,3 +88,45 @@
> haven't started working yet.
> """)
>
> +
> +class ISeriesMixin(IHasDrivers):
> + """Methods & properties shared between distro & product series."""
> +
> + active = exported(
> + Bool(

In this case, having the first argument on a line by itself doesn't buy
us much as it allows you to move only one char to the left, so better to
leave it on the same line as Bool.

> + title=_("Active"),
> + description=_(
> + "Whether or not this series is stable and supported, or "
> + "under current development. This excludes series which "
> + "are experimental or obsolete.")))
> +
> + summary = exported(
> + Summary(title=_("Summary"),
> + description=_('A single paragraph that explains the goals of '
> + 'of this series and the intended users. '
> + 'For example: "The 2.0 series of Apache '
> + 'represents the current stable series, '
> + 'and is recommended for all new deployments".'),
> + required=True))
> +
> + drivers = exported(
> + CollectionField(
> + title=_(
> + 'A list of the people or teams who are drivers for this '
> + 'series. This l...

Read more...

Revision history for this message
Adi Roiban (adiroiban) wrote :

> Hi Adi,
>
> This is a nice refactoring; thanks for doing it.
>
> There's just one thing I noticed which was not mentioned in your cover
> letter: the IProductSeries interface has grown a 'active' attribute,
> thanks to ISeriesMixin. Since your branch doesn't add any code that
> uses that attribute, I don't see a reason for adding it, so I'd suggest
> moving it to IDistroSeries and the actual implementation to
> DistroSeries. Unless you do plan to use IProductSeries.active
> somewhere?
>
In the current code from lp/devel 'active' is the only attribute of ISeriesMixin. I was thinking it was put there to be used in the future.

Revision history for this message
Adi Roiban (adiroiban) wrote :
Download full text (3.4 KiB)

Hi, as you suggested, I moved „active” from ISeries to IDistroSeries.

Here is the latest diff:
=== modified file 'lib/lp/registry/interfaces/distroseries.py'
--- lib/lp/registry/interfaces/distroseries.py 2010-03-06 01:24:05 +0000
+++ lib/lp/registry/interfaces/distroseries.py 2010-03-08 09:08:46 +0000
@@ -164,6 +164,12 @@
             description=_(
                 "The title of this series. It should be distinctive "
                 "and designed to look good at the top of a page.")))
+ active = exported(Bool(
+ title=_("Active"),
+ description=_(
+ "Whether or not this series is stable and supported, or "
+ "under current development. This excludes series which "
+ "are experimental or obsolete.")))
     description = exported(
         Description(title=_("Description"), required=True,
             description=_("A detailed description of this series, with "

=== modified file 'lib/lp/registry/interfaces/series.py'
--- lib/lp/registry/interfaces/series.py 2010-03-06 01:24:05 +0000
+++ lib/lp/registry/interfaces/series.py 2010-03-08 09:07:19 +0000
@@ -92,13 +92,6 @@
 class ISeriesMixin(IHasDrivers):
     """Methods & properties shared between distro & product series."""

- active = exported(Bool(
- title=_("Active"),
- description=_(
- "Whether or not this series is stable and supported, or "
- "under current development. This excludes series which "
- "are experimental or obsolete.")))
-
     summary = exported(
         Summary(title=_("Summary"),
              description=_('A single paragraph that explains the goals of '

=== modified file 'lib/lp/registry/model/distroseries.py'
--- lib/lp/registry/model/distroseries.py 2010-03-05 15:50:47 +0000
+++ lib/lp/registry/model/distroseries.py 2010-03-08 09:09:20 +0000
@@ -183,6 +183,15 @@
         intermediateTable='SectionSelection')

     @property
+ def active(self):
+ return self.status in [
+ SeriesStatus.DEVELOPMENT,
+ SeriesStatus.FROZEN,
+ SeriesStatus.CURRENT,
+ SeriesStatus.SUPPORTED,
+ ]
+
+ @property
     def named_version(self):
         return '%s (%s)' % (self.displayname, self.version)

=== modified file 'lib/lp/registry/model/series.py'
--- lib/lp/registry/model/series.py 2010-03-05 13:14:49 +0000
+++ lib/lp/registry/model/series.py 2010-03-08 09:08:56 +0000
@@ -25,15 +25,6 @@
     summary = StringCol(notNull=True)

     @property
- def active(self):
- return self.status in [
- SeriesStatus.DEVELOPMENT,
- SeriesStatus.FROZEN,
- SeriesStatus.CURRENT,
- SeriesStatus.SUPPORTED,
- ]
-
- @property
     def bug_supervisor(self):
         """See `ISeriesMixin`."""
         return self.parent.bug_supervisor

=== modified file 'lib/lp/registry/stories/webservice/xx-project-registry.txt'
--- lib/lp/registry/stories/webservice/xx-project-registry.txt 2010-03-05 13:14:49 +0000
+++ lib/lp/registry/stories/webservice/xx-project-registry.txt 2010-03-08 09:11:47 +0000
@@ -810,7 +810,6 @@

     >>> babadoo_foobadoo = webservice.get('/babadoo/foobadoo'...

Read more...

Revision history for this message
Adi Roiban (adiroiban) wrote :

This is a small diff that fixes a spurious test, as a few line below the test is using both display_name and name:

    >>> for project in project_entries[:5]:
    ... print "%s (%s)" % (project['display_name'], project['name'])
    APTonCD (aptoncd)
    Arch mirrors (arch-mirrors)
    Bazaar (bazaar)
    Bazaar (bzr)
    Derby (derby)

=== modified file 'lib/lp/registry/stories/webservice/xx-project-registry.txt'
--- lib/lp/registry/stories/webservice/xx-project-registry.txt 2010-03-08 09:34:22 +0000
+++ lib/lp/registry/stories/webservice/xx-project-registry.txt 2010-03-08 10:07:31 +0000
@@ -500,7 +500,7 @@
     >>> project_collection = webservice.get("/projects?ws.size=75").jsonBody()

     >>> project_entries = sorted(
- ... project_collection['entries'], key=itemgetter('display_name'))
+ ... project_collection['entries'], key=itemgetter('display_name','name'))
     >>> len(project_entries)
     25

Revision history for this message
Guilherme Salgado (salgado) wrote :

On Mon, 2010-03-08 at 10:13 +0000, Adi Roiban wrote:
> This is a small diff that fixes a spurious test, as a few line below the test is using both display_name and name:
>
> >>> for project in project_entries[:5]:
> ... print "%s (%s)" % (project['display_name'], project['name'])
> APTonCD (aptoncd)
> Arch mirrors (arch-mirrors)
> Bazaar (bazaar)
> Bazaar (bzr)
> Derby (derby)
>
> === modified file 'lib/lp/registry/stories/webservice/xx-project-registry.txt'
> --- lib/lp/registry/stories/webservice/xx-project-registry.txt 2010-03-08 09:34:22 +0000
> +++ lib/lp/registry/stories/webservice/xx-project-registry.txt 2010-03-08 10:07:31 +0000
> @@ -500,7 +500,7 @@
> >>> project_collection = webservice.get("/projects?ws.size=75").jsonBody()
>
> >>> project_entries = sorted(
> - ... project_collection['entries'], key=itemgetter('display_name'))
> + ... project_collection['entries'], key=itemgetter('display_name','name'))

This line has more than 80 chars now, no? Can you break it between the
two args?

 review approve

--
Guilherme Salgado <email address hidden>

review: Approve
Revision history for this message
Adi Roiban (adiroiban) wrote :

I have fixed the long line + some other long lines in this file.

"./bin/test -m registry" has no errors on my system.

Do you have time to sent this branch for landing?

Many thanks!

Revision history for this message
Guilherme Salgado (salgado) wrote :

Sure; I've just submitted it.

Revision history for this message
Guilherme Salgado (salgado) wrote :

The test suite hanged on ec2, but there are a few failures: http://paste.ubuntu.com/391282/

Adi, can you run these tests locally and let me know if the pass or if you need to make any changes to have them passing again?

Revision history for this message
Adi Roiban (adiroiban) wrote :

Hi,

There are no errors running buildd-slavescanner.txt tests with this branch on my computer.

---------------

./bin/test -t test_programming_languages_edit -t test_project_licenses -t test_product_edit_people_owner -t test_product_edit_people_driver -t test_title_inline_edit

These Windmill tests are randomly failing.
They also fail on my computer for the lp/devel branch.

They are AJAX action to modify product properties.
I have watch them using „./bin/test -D -t test_programming_languages_edit”, and even though the test fails, I can see that the action was successfully done in Firefox ... but maybe a bit to late for Windmill.

What should I do to move thinkgs forward?

Many thanks!

Revision history for this message
Guilherme Salgado (salgado) wrote :

If the tests don't fail for you locally, I'll resubmit.

Revision history for this message
Adi Roiban (adiroiban) wrote :

It looks like the buildd-slavescanner error was fixed:
http://bazaar.launchpad.net/~launchpad-pqm/launchpad/devel/revision/10455

I merged with devel and fixed the conflict.

I don't know how to fix those randomly failing Windmill tests.

Revision history for this message
Adi Roiban (adiroiban) wrote :

The active attribute for productseries is used in registry/model/product.py line 1015.

This was causing the failure of test_timeline_graph.

This is really strange, since the product.py code was commited long time ago and test_timeline_graph is not failing on mainline.

Can you please try to run this test on your computer using this branch?
These are the test failing on ec2-test, but they are fine on my computer.

bin/test --layer=WindmillLayer -t programming_languages_edit -t title_inline_edit -t product_edit_people_driver -t product_edit_people_owner -t project_licenses

Revision history for this message
Guilherme Salgado (salgado) wrote :

They fail here too, and the reason why they're failing is that on this
branch I asked you to move the active attribute from SeriesMixin to
DistroSeries because it seemed to be used only there. That was wrong --
ProductSeries inherits from SeriesMixin and, as you noticed, we have
code that rely on ProductSeries.attribute.

I think the correct fix here is to move the 'active' property back to
SeriesMixin. After you do that you'll have to update one of the
webservice tests for ProductSeries because its webservice representation
will now contain an extra 'active' field.

Revision history for this message
Adi Roiban (adiroiban) wrote :
Download full text (3.9 KiB)

Here is the diff for moving "active" back to SeriesMixin.

I have pushed these changes.

After pulling the changes, can you please try to run this test on you computer:
bin/test --layer=WindmillLayer -t programming_languages_edit -t title_inline_edit -t product_edit_people_driver -t product_edit_people_owner -t project_licenses -t test_timeline_graph

Many thanks!
=== modified file 'lib/lp/registry/interfaces/distroseries.py'
--- lib/lp/registry/interfaces/distroseries.py 2010-03-09 12:37:25 +0000
+++ lib/lp/registry/interfaces/distroseries.py 2010-03-12 17:49:47 +0000
@@ -165,12 +165,6 @@
             description=_(
                 "The title of this series. It should be distinctive "
                 "and designed to look good at the top of a page.")))
- active = exported(Bool(
- title=_("Active"),
- description=_(
- "Whether or not this series is stable and supported, or "
- "under current development. This excludes series which "
- "are experimental or obsolete.")))
     description = exported(
         Description(title=_("Description"), required=True,
             description=_("A detailed description of this series, with "

=== modified file 'lib/lp/registry/interfaces/series.py'
--- lib/lp/registry/interfaces/series.py 2010-03-08 09:34:22 +0000
+++ lib/lp/registry/interfaces/series.py 2010-03-12 17:50:01 +0000
@@ -92,6 +92,13 @@
 class ISeriesMixin(IHasDrivers):
     """Methods & properties shared between distro & product series."""

+ active = exported(Bool(
+ title=_("Active"),
+ description=_(
+ "Whether or not this series is stable and supported, or "
+ "under current development. This excludes series which "
+ "are experimental or obsolete.")))
+
     summary = exported(
         Summary(title=_("Summary"),
              description=_('A single paragraph that explains the goals of '
@@ -110,7 +117,6 @@
             readonly=True,
             value_type=Reference(schema=IPerson)))

-
     bug_supervisor = CollectionField(
         title=_('Currently just a reference to the parent bug '
                 'supervisor.'),

=== modified file 'lib/lp/registry/model/distroseries.py'
--- lib/lp/registry/model/distroseries.py 2010-03-09 12:37:25 +0000
+++ lib/lp/registry/model/distroseries.py 2010-03-12 17:50:16 +0000
@@ -182,15 +182,6 @@
         intermediateTable='SectionSelection')

     @property
- def active(self):
- return self.status in [
- SeriesStatus.DEVELOPMENT,
- SeriesStatus.FROZEN,
- SeriesStatus.CURRENT,
- SeriesStatus.SUPPORTED,
- ]
-
- @property
     def named_version(self):
         return '%s (%s)' % (self.displayname, self.version)

=== modified file 'lib/lp/registry/model/series.py'
--- lib/lp/registry/model/series.py 2010-03-08 09:34:22 +0000
+++ lib/lp/registry/model/series.py 2010-03-12 17:50:23 +0000
@@ -25,6 +25,15 @@
     summary = StringCol(notNull=True)

     @property
+ def active(self):
+ return self.status in [
+ SeriesStatus.DEVELOPMENT,
+ SeriesStatus.FROZEN,
+ SeriesStatus.CURRENT,
+ ...

Read more...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/registry/interfaces/distroseries.py'
2--- lib/lp/registry/interfaces/distroseries.py 2010-03-08 10:43:34 +0000
3+++ lib/lp/registry/interfaces/distroseries.py 2010-03-15 15:04:36 +0000
4@@ -12,7 +12,6 @@
5 'IDistroSeriesEditRestricted',
6 'IDistroSeriesPublic',
7 'IDistroSeriesSet',
8- 'ISeriesMixin',
9 'NoSuchDistroSeries',
10 ]
11
12@@ -30,10 +29,9 @@
13
14 from canonical.launchpad import _
15 from canonical.launchpad.fields import (
16- ContentNameField, Description, PublicPersonChoice, Summary, Title,
17+ ContentNameField, Description, PublicPersonChoice, Title,
18 UniqueField)
19-from canonical.launchpad.interfaces.launchpad import (
20- IHasAppointedDriver, IHasDrivers)
21+from canonical.launchpad.interfaces.launchpad import IHasAppointedDriver
22 from canonical.launchpad.validators import LaunchpadValidationError
23 from canonical.launchpad.validators.email import email_validator
24 from canonical.launchpad.validators.name import name_validator
25@@ -46,7 +44,7 @@
26 IBugTarget, IHasBugs, IHasOfficialBugTags)
27 from lp.registry.interfaces.milestone import IHasMilestones, IMilestone
28 from lp.registry.interfaces.role import IHasOwner
29-from lp.registry.interfaces.series import SeriesStatus
30+from lp.registry.interfaces.series import ISeriesMixin, SeriesStatus
31 from lp.registry.interfaces.sourcepackage import ISourcePackage
32 from lp.registry.interfaces.structuralsubscription import (
33 IStructuralSubscriptionTarget)
34@@ -141,22 +139,10 @@
35 """Create a new milestone for this DistroSeries."""
36
37
38-class ISeriesMixin(Interface):
39- """Methods & properties shared between distro & product series."""
40-
41- active = exported(
42- Bool(
43- title=_("Active"),
44- description=_(
45- "Whether or not this series is stable and supported, or "
46- "under current development. This excludes series which "
47- "are experimental or obsolete.")))
48-
49-
50-class IDistroSeriesPublic(IHasAppointedDriver, IHasDrivers, IHasOwner,
51- IBugTarget, ISpecificationGoal, IHasMilestones,
52- IHasBuildRecords, ISeriesMixin,
53- IHasOfficialBugTags):
54+class IDistroSeriesPublic(
55+ ISeriesMixin, IHasAppointedDriver, IHasOwner, IBugTarget,
56+ ISpecificationGoal, IHasMilestones, IHasOfficialBugTags,
57+ IHasBuildRecords):
58 """Public IDistroSeries properties."""
59
60 id = Attribute("The distroseries's unique number.")
61@@ -179,12 +165,6 @@
62 description=_(
63 "The title of this series. It should be distinctive "
64 "and designed to look good at the top of a page.")))
65- summary = exported(
66- Summary(title=_("Summary"), required=True,
67- description=_(
68- "A brief summary of the highlights of this release. "
69- "It should be no longer than a single paragraph, up "
70- "to 200 words.")))
71 description = exported(
72 Description(title=_("Description"), required=True,
73 description=_("A detailed description of this series, with "
74@@ -251,14 +231,6 @@
75 nominatedarchindep = Attribute(
76 "DistroArchSeries designed to build architecture-independent "
77 "packages whithin this distroseries context.")
78- drivers = Attribute(
79- 'A list of the people or teams who are drivers for this series. '
80- 'This list is made up of any drivers or owners from this '
81- 'DistroSeries, and the Distribution to which it belong.')
82- bug_supervisor = Attribute(
83- 'Currently just a reference to the Distribution bug supervisor.')
84- security_contact = Attribute(
85- 'Currently just a reference to the Distribution security contact.')
86 messagecount = Attribute("The total number of translatable items in "
87 "this series.")
88 distroserieslanguages = Attribute("The set of dr-languages in this "
89@@ -445,14 +417,14 @@
90 """Return a list of packagings that are the most recently linked.
91
92 At most five packages are returned of those most recently linked to an
93- upstream.
94- """
95+ upstream.
96+ """
97
98 @operation_parameters(
99 created_since_date=Datetime(
100 title=_("Created Since Timestamp"),
101- description=_("Return items that are more recent than this "
102- "timestamp."),
103+ description=_(
104+ "Return items that are more recent than this timestamp."),
105 required=False),
106 status=Choice(
107 # Really PackageUploadCustomFormat, patched in
108
109=== modified file 'lib/lp/registry/interfaces/productseries.py'
110--- lib/lp/registry/interfaces/productseries.py 2010-02-03 12:09:42 +0000
111+++ lib/lp/registry/interfaces/productseries.py 2010-03-15 15:04:36 +0000
112@@ -19,19 +19,16 @@
113 from zope.interface import Interface, Attribute
114
115 from canonical.launchpad.fields import (
116- ContentNameField, NoneableDescription, ParticipatingPersonChoice,
117- PublicPersonChoice, Title)
118+ ContentNameField, ParticipatingPersonChoice, Title)
119 from lp.registry.interfaces.structuralsubscription import (
120 IStructuralSubscriptionTarget)
121 from lp.code.interfaces.branch import IBranch
122 from lp.bugs.interfaces.bugtarget import IBugTarget, IHasOfficialBugTags
123-from lp.registry.interfaces.series import SeriesStatus
124-from canonical.launchpad.interfaces.launchpad import (
125- IHasAppointedDriver, IHasDrivers)
126+from lp.registry.interfaces.series import ISeriesMixin, SeriesStatus
127+from canonical.launchpad.interfaces.launchpad import IHasAppointedDriver
128 from lp.registry.interfaces.role import IHasOwner
129 from lp.registry.interfaces.milestone import (
130 IHasMilestones, IMilestone)
131-from lp.registry.interfaces.person import IPerson
132 from lp.registry.interfaces.productrelease import IProductRelease
133 from lp.blueprints.interfaces.specificationtarget import (
134 ISpecificationGoal)
135@@ -92,9 +89,9 @@
136 """Create a new milestone for this ProjectSeries."""
137
138
139-class IProductSeriesPublic(IHasAppointedDriver, IHasDrivers, IHasOwner,
140- IBugTarget, ISpecificationGoal, IHasMilestones,
141- IHasOfficialBugTags):
142+class IProductSeriesPublic(
143+ ISeriesMixin, IHasAppointedDriver, IHasOwner, IBugTarget,
144+ ISpecificationGoal, IHasMilestones, IHasOfficialBugTags):
145 """Public IProductSeries properties."""
146 # XXX Mark Shuttleworth 2004-10-14: Would like to get rid of id in
147 # interfaces, as soon as SQLobject allows using the object directly
148@@ -158,15 +155,6 @@
149 'just returns the name.')),
150 exported_as='display_name')
151
152- summary = exported(
153- NoneableDescription(title=_("Summary"),
154- description=_('A single paragraph that explains the goals of '
155- 'of this series and the intended users. '
156- 'For example: "The 2.0 series of Apache '
157- 'represents the current stable series, '
158- 'and is recommended for all new deployments".'),
159- required=True))
160-
161 releases = exported(
162 CollectionField(
163 title=_("An iterator over the releases in this "
164@@ -201,28 +189,6 @@
165 readonly=True,
166 value_type=Reference(schema=IMilestone)))
167
168- drivers = exported(
169- CollectionField(
170- title=_(
171- 'A list of the people or teams who are drivers for this '
172- 'series. This list is made up of any drivers or owners '
173- 'from this project series, the project and if it exists, '
174- 'the relevant project group.'),
175- readonly=True,
176- value_type=Reference(schema=IPerson)))
177-
178- bug_supervisor = CollectionField(
179- title=_('Currently just a reference to the project bug '
180- 'supervisor.'),
181- readonly=True,
182- value_type=Reference(schema=IPerson))
183-
184- security_contact = PublicPersonChoice(
185- title=_('Security Contact'),
186- description=_('Currently just a reference to the project '
187- 'security contact.'),
188- required=False, vocabulary='ValidPersonOrTeam')
189-
190 branch = exported(
191 ReferenceChoice(
192 title=_('Branch'),
193@@ -318,7 +284,6 @@
194 export_as_webservice_entry('project_series')
195
196
197-
198 class IProductSeriesSet(Interface):
199 """Interface representing the set of ProductSeries."""
200
201
202=== modified file 'lib/lp/registry/interfaces/series.py'
203--- lib/lp/registry/interfaces/series.py 2009-12-13 11:55:40 +0000
204+++ lib/lp/registry/interfaces/series.py 2010-03-15 15:04:36 +0000
205@@ -8,10 +8,22 @@
206 __metaclass__ = type
207
208 __all__ = [
209- 'SeriesStatus'
210+ 'SeriesStatus',
211+ 'ISeriesMixin',
212 ]
213
214+from zope.schema import Bool
215+
216 from lazr.enum import DBEnumeratedType, DBItem
217+from lazr.restful.fields import CollectionField, Reference
218+from lazr.restful.declarations import exported
219+
220+from canonical.launchpad import _
221+from canonical.launchpad.fields import (
222+ PublicPersonChoice, Summary)
223+from canonical.launchpad.interfaces.launchpad import IHasDrivers
224+from lp.registry.interfaces.person import IPerson
225+
226
227 class SeriesStatus(DBEnumeratedType):
228 """Distro/Product Series Status
229@@ -76,3 +88,43 @@
230 haven't started working yet.
231 """)
232
233+
234+class ISeriesMixin(IHasDrivers):
235+ """Methods & properties shared between distro & product series."""
236+
237+ active = exported(Bool(
238+ title=_("Active"),
239+ description=_(
240+ "Whether or not this series is stable and supported, or "
241+ "under current development. This excludes series which "
242+ "are experimental or obsolete.")))
243+
244+ summary = exported(
245+ Summary(title=_("Summary"),
246+ description=_('A single paragraph that explains the goals of '
247+ 'of this series and the intended users. '
248+ 'For example: "The 2.0 series of Apache '
249+ 'represents the current stable series, '
250+ 'and is recommended for all new deployments".'),
251+ required=True))
252+
253+ drivers = exported(
254+ CollectionField(
255+ title=_(
256+ 'A list of the people or teams who are drivers for this '
257+ 'series. This list is made up of any drivers or owners '
258+ 'from this series and the parent drivers.'),
259+ readonly=True,
260+ value_type=Reference(schema=IPerson)))
261+
262+ bug_supervisor = CollectionField(
263+ title=_('Currently just a reference to the parent bug '
264+ 'supervisor.'),
265+ readonly=True,
266+ value_type=Reference(schema=IPerson))
267+
268+ security_contact = PublicPersonChoice(
269+ title=_('Security Contact'),
270+ description=_('Currently just a reference to the parent '
271+ 'security contact.'),
272+ required=False, vocabulary='ValidPersonOrTeam')
273
274=== modified file 'lib/lp/registry/model/distroseries.py'
275--- lib/lp/registry/model/distroseries.py 2010-03-08 10:43:34 +0000
276+++ lib/lp/registry/model/distroseries.py 2010-03-15 15:04:36 +0000
277@@ -10,7 +10,6 @@
278 __all__ = [
279 'DistroSeries',
280 'DistroSeriesSet',
281- 'SeriesMixin',
282 ]
283
284 import logging
285@@ -87,8 +86,6 @@
286 HasSpecificationsMixin, Specification)
287 from lp.translations.model.translationimportqueue import (
288 HasTranslationImportsMixin)
289-from lp.registry.model.structuralsubscription import (
290- StructuralSubscriptionTargetMixin)
291 from canonical.launchpad.helpers import shortlist
292 from lp.soyuz.interfaces.archive import (
293 ALLOW_RELEASE_BUILDS, ArchivePurpose, IArchiveSet, MAIN_ARCHIVE_PURPOSES)
294@@ -98,7 +95,10 @@
295 IBinaryPackageName)
296 from lp.registry.interfaces.series import SeriesStatus
297 from lp.registry.interfaces.distroseries import (
298- IDistroSeries, IDistroSeriesSet, ISeriesMixin)
299+ IDistroSeries, IDistroSeriesSet)
300+from lp.registry.model.series import SeriesMixin
301+from lp.registry.model.structuralsubscription import (
302+ StructuralSubscriptionTargetMixin)
303 from lp.translations.interfaces.languagepack import LanguagePackType
304 from canonical.launchpad.interfaces.librarian import ILibraryFileAliasSet
305 from lp.soyuz.interfaces.queue import PackageUploadStatus
306@@ -123,20 +123,6 @@
307 ISourcePackageFormatSelectionSet)
308
309
310-class SeriesMixin:
311- """See `ISeriesMixin`."""
312- implements(ISeriesMixin)
313-
314- @property
315- def active(self):
316- return self.status in [
317- SeriesStatus.DEVELOPMENT,
318- SeriesStatus.FROZEN,
319- SeriesStatus.CURRENT,
320- SeriesStatus.SUPPORTED,
321- ]
322-
323-
324 class DistroSeries(SQLBase, BugTargetBase, HasSpecificationsMixin,
325 HasTranslationImportsMixin, HasTranslationTemplatesMixin,
326 HasMilestonesMixin, SeriesMixin,
327@@ -154,7 +140,6 @@
328 name = StringCol(notNull=True)
329 displayname = StringCol(notNull=True)
330 title = StringCol(notNull=True)
331- summary = StringCol(notNull=True)
332 description = StringCol(notNull=True)
333 version = StringCol(notNull=True)
334 status = EnumCol(
335@@ -283,25 +268,6 @@
336 return self.distribution
337
338 @property
339- def drivers(self):
340- """See `IDistroSeries`."""
341- drivers = set()
342- drivers.add(self.driver)
343- drivers = drivers.union(self.distribution.drivers)
344- drivers.discard(None)
345- return sorted(drivers, key=lambda driver: driver.displayname)
346-
347- @property
348- def bug_supervisor(self):
349- """See `IDistroSeries`."""
350- return self.distribution.bug_supervisor
351-
352- @property
353- def security_contact(self):
354- """See `IDistroSeries`."""
355- return self.distribution.security_contact
356-
357- @property
358 def sortkey(self):
359 """A string to be used for sorting distro seriess.
360
361
362=== modified file 'lib/lp/registry/model/productseries.py'
363--- lib/lp/registry/model/productseries.py 2010-03-05 23:59:18 +0000
364+++ lib/lp/registry/model/productseries.py 2010-03-15 15:04:36 +0000
365@@ -50,8 +50,6 @@
366 from lp.registry.model.structuralsubscription import (
367 StructuralSubscriptionTargetMixin)
368 from canonical.launchpad.helpers import shortlist
369-from lp.registry.interfaces.series import SeriesStatus
370-from lp.registry.model.distroseries import SeriesMixin
371 from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
372 from lp.registry.interfaces.packaging import PackagingType
373 from lp.translations.interfaces.potemplate import IHasTranslationTemplates
374@@ -60,10 +58,12 @@
375 SpecificationGoalStatus, SpecificationImplementationStatus,
376 SpecificationSort)
377 from canonical.launchpad.webapp.interfaces import NotFoundError
378+from lp.registry.interfaces.series import SeriesStatus
379 from lp.registry.interfaces.productseries import (
380 IProductSeries, IProductSeriesSet)
381 from lp.translations.interfaces.translations import (
382 TranslationsBranchImportMode)
383+from lp.registry.model.series import SeriesMixin
384 from canonical.launchpad.webapp.publisher import canonical_url
385 from canonical.launchpad.webapp.sorting import sorted_dotted_numbers
386
387@@ -92,12 +92,12 @@
388 notNull=True, schema=SeriesStatus,
389 default=SeriesStatus.DEVELOPMENT)
390 name = StringCol(notNull=True)
391- summary = StringCol(notNull=True)
392 datecreated = UtcDateTimeCol(notNull=True, default=UTC_NOW)
393 owner = ForeignKey(
394 dbName="owner", foreignKey="Person",
395 storm_validator=validate_person_not_private_membership,
396 notNull=True)
397+
398 driver = ForeignKey(
399 dbName="driver", foreignKey="Person",
400 storm_validator=validate_person_not_private_membership,
401@@ -165,25 +165,6 @@
402 """See `IHasBugs`."""
403 return self.product.max_bug_heat
404
405- @property
406- def drivers(self):
407- """See IProductSeries."""
408- drivers = set()
409- drivers.add(self.driver)
410- drivers = drivers.union(self.product.drivers)
411- drivers.discard(None)
412- return sorted(drivers, key=lambda x: x.displayname)
413-
414- @property
415- def bug_supervisor(self):
416- """See IProductSeries."""
417- return self.product.bug_supervisor
418-
419- @property
420- def security_contact(self):
421- """See IProductSeries."""
422- return self.product.security_contact
423-
424 def getPOTemplate(self, name):
425 """See IProductSeries."""
426 return POTemplate.selectOne(
427@@ -308,7 +289,7 @@
428
429 # filter based on completion. see the implementation of
430 # Specification.is_complete() for more details
431- completeness = Specification.completeness_clause
432+ completeness = Specification.completeness_clause
433
434 if SpecificationFilter.COMPLETE in filter:
435 query += ' AND ( %s ) ' % completeness
436@@ -438,8 +419,8 @@
437
438 def getTranslationTemplates(self):
439 """See `IHasTranslationTemplates`."""
440- result = POTemplate.selectBy(productseries=self,
441- orderBy=['-priority','name'])
442+ result = POTemplate.selectBy(
443+ productseries=self, orderBy=['-priority', 'name'])
444 return shortlist(result, 300)
445
446 def getCurrentTranslationTemplates(self, just_ids=False):
447@@ -472,7 +453,7 @@
448 ProductSeries.product = Product.id AND
449 (iscurrent IS FALSE OR Product.official_rosetta IS FALSE)
450 ''' % sqlvalues(self),
451- orderBy=['-priority','name'],
452+ orderBy=['-priority', 'name'],
453 clauseTables = ['ProductSeries', 'Product'])
454 return shortlist(result, 300)
455
456
457=== added file 'lib/lp/registry/model/series.py'
458--- lib/lp/registry/model/series.py 1970-01-01 00:00:00 +0000
459+++ lib/lp/registry/model/series.py 2010-03-15 15:04:36 +0000
460@@ -0,0 +1,53 @@
461+# Copyright 2009 Canonical Ltd. This software is licensed under the
462+# GNU Affero General Public License version 3 (see the file LICENSE).
463+
464+"""Common implementations for a series."""
465+
466+__metaclass__ = type
467+
468+__all__ = [
469+ 'SeriesMixin',
470+ ]
471+
472+from operator import attrgetter
473+from sqlobject import StringCol
474+
475+from zope.interface import implements
476+
477+from lp.registry.interfaces.series import ISeriesMixin, SeriesStatus
478+
479+
480+class SeriesMixin:
481+ """See `ISeriesMixin`."""
482+
483+ implements(ISeriesMixin)
484+
485+ summary = StringCol(notNull=True)
486+
487+ @property
488+ def active(self):
489+ return self.status in [
490+ SeriesStatus.DEVELOPMENT,
491+ SeriesStatus.FROZEN,
492+ SeriesStatus.CURRENT,
493+ SeriesStatus.SUPPORTED,
494+ ]
495+
496+ @property
497+ def bug_supervisor(self):
498+ """See `ISeriesMixin`."""
499+ return self.parent.bug_supervisor
500+
501+ @property
502+ def security_contact(self):
503+ """See `ISeriesMixin`."""
504+ return self.parent.security_contact
505+
506+ @property
507+ def drivers(self):
508+ """See `ISeriesMixin`."""
509+ drivers = set()
510+ drivers.add(self.driver)
511+ drivers = drivers.union(self.parent.drivers)
512+ drivers.discard(None)
513+ return sorted(drivers, key=attrgetter('displayname'))
514
515=== modified file 'lib/lp/registry/stories/webservice/xx-distroseries.txt'
516--- lib/lp/registry/stories/webservice/xx-distroseries.txt 2010-02-04 21:16:37 +0000
517+++ lib/lp/registry/stories/webservice/xx-distroseries.txt 2010-03-15 15:04:36 +0000
518@@ -68,6 +68,7 @@
519 displayname: u'Hoary'
520 distribution_link: u'http://.../ubuntu'
521 driver_link: None
522+ drivers_collection_link: u'http://.../ubuntu/hoary/drivers'
523 fullseriesname: u'Ubuntu Hoary'
524 main_archive_link: u'http://.../ubuntu/+archive/primary'
525 name: u'hoary'
526
527=== modified file 'lib/lp/registry/stories/webservice/xx-project-registry.txt'
528--- lib/lp/registry/stories/webservice/xx-project-registry.txt 2010-02-04 21:18:35 +0000
529+++ lib/lp/registry/stories/webservice/xx-project-registry.txt 2010-03-15 15:04:36 +0000
530@@ -93,7 +93,8 @@
531 active_milestones_collection_link and the
532 all_milestones_collection_link.
533
534- >>> response = webservice.get(mozilla['active_milestones_collection_link'])
535+ >>> response = webservice.get(
536+ ... mozilla['active_milestones_collection_link'])
537 >>> active_milestones = response.jsonBody()
538 >>> print_self_link_of_entries(active_milestones)
539 http://.../mozilla/+milestone/1.0
540@@ -233,7 +234,8 @@
541 active_milestones_collection_link and the
542 all_milestones_collection_link.
543
544- >>> response = webservice.get(firefox['active_milestones_collection_link'])
545+ >>> response = webservice.get(
546+ ... firefox['active_milestones_collection_link'])
547 >>> active_milestones = response.jsonBody()
548 >>> print_self_link_of_entries(active_milestones)
549 http://.../firefox/+milestone/1.0
550@@ -360,11 +362,14 @@
551 >>> # Create a product with a series and release.
552 >>> login('test@canonical.com')
553 >>> test_project_owner = factory.makePerson(name='test-project-owner')
554- >>> test_project = factory.makeProduct(name='test-project', owner=test_project_owner)
555+ >>> test_project = factory.makeProduct(
556+ ... name='test-project', owner=test_project_owner)
557 >>> test_series = factory.makeProductSeries(
558- ... product=test_project, name='test-series', owner=test_project_owner)
559+ ... product=test_project, name='test-series',
560+ ... owner=test_project_owner)
561 >>> test_milestone = factory.makeMilestone(
562- ... product=test_project, name='test-milestone', productseries=test_series)
563+ ... product=test_project, name='test-milestone',
564+ ... productseries=test_series)
565 >>> test_project_release = factory.makeProductRelease(
566 ... product=test_project, milestone=test_milestone)
567 >>> logout()
568@@ -500,7 +505,8 @@
569 >>> project_collection = webservice.get("/projects?ws.size=75").jsonBody()
570
571 >>> project_entries = sorted(
572- ... project_collection['entries'], key=itemgetter('display_name'))
573+ ... project_collection['entries'],
574+ ... key=itemgetter('display_name','name'))
575 >>> len(project_entries)
576 25
577
578@@ -528,7 +534,8 @@
579 If you don't specify "text" to the search a batched list of all the
580 projects is returned.
581
582- >>> project_collection = webservice.named_get("/projects", "search").jsonBody()
583+ >>> project_collection = webservice.named_get(
584+ ... "/projects", "search").jsonBody()
585 >>> len(project_collection['entries'])
586 5
587
588@@ -810,6 +817,7 @@
589
590 >>> babadoo_foobadoo = webservice.get('/babadoo/foobadoo').jsonBody()
591 >>> pprint_entry(babadoo_foobadoo)
592+ active: True
593 active_milestones_collection_link: u'http://.../babadoo/foobadoo/active_milestones'
594 all_milestones_collection_link: u'http://.../babadoo/foobadoo/all_milestones'
595 branch_link: u'http://.../~babadoo-owner/babadoo/fooey'