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

Proposed by Adi Roiban
Status: Merged
Merged at revision: 10953
Proposed branch: lp:~adiroiban/launchpad/bug-525371
Merge into: lp:launchpad
Diff against target: 1034 lines (+338/-153)
18 files modified
lib/canonical/launchpad/interfaces/_schema_circular_imports.py (+18/-4)
lib/canonical/launchpad/security.py (+11/-2)
lib/lp/registry/configure.zcml (+0/-6)
lib/lp/registry/interfaces/distroseries.py (+5/-3)
lib/lp/registry/interfaces/productseries.py (+3/-1)
lib/lp/registry/interfaces/sourcepackage.py (+8/-6)
lib/lp/registry/model/distroseries.py (+5/-8)
lib/lp/registry/model/productseries.py (+2/-4)
lib/lp/registry/model/sourcepackage.py (+2/-4)
lib/lp/registry/stories/webservice/xx-distroseries.txt (+2/-1)
lib/lp/registry/stories/webservice/xx-project-registry.txt (+10/-5)
lib/lp/translations/browser/potemplate.py (+11/-12)
lib/lp/translations/interfaces/pofile.py (+12/-5)
lib/lp/translations/interfaces/potemplate.py (+107/-82)
lib/lp/translations/interfaces/webservice.py (+15/-0)
lib/lp/translations/model/distroseries_translations_copy.py (+1/-1)
lib/lp/translations/model/potemplate.py (+5/-9)
lib/lp/translations/stories/webservice/xx-potemplate.txt (+121/-0)
To merge this branch: bzr merge lp:~adiroiban/launchpad/bug-525371
Reviewer Review Type Date Requested Status
Данило Шеган (community) Needs Fixing
Jeroen T. Vermeulen Pending
Review via email: mp+25423@code.launchpad.net

Commit message

Export POTemplate attributes in the API.

Description of the change

= Bug 525371 =

To help creating various translations statistics (ie for Ubuntu translations) it would be nice to have an LP API that could export the following information about a POTemplate:

 * template name in Launchpad
 * priority
 * length
 * date last updated
 * domain name
 * iscurrent (active or not)
 * source package
 * is in lang pack
 * in how many languages it is translates (nice to have)

The API should return a list of all templates for a series, but also the info for a single template

The requirements are tracked on this wiki page:
https://dev.launchpad.net/Translations/Specs/ReportingAPI

== Proposed fix ==

This is the first step in implementing an API for POTemplates.

We will start with exposing the POTemplates attributes.

Here are the exported attributes:

    active: True
    all_pofiles_collection_link: u'http://.../pmount/+pots/pmount/all_pofiles'
    date_last_updated: u'2005-05-06T20:09:23.775993+00:00'
    description: None
    exported_in_languagepacks: True
    format: u'PO format'
    id: 2
    language_count: 8
    message_count: 63
    name: u'pmount'
    owner_link: u'http://.../~rosetta-admins'
    path: u'po/template.pot'
    priority: 0
    resource_type_link: u'http://.../#potemplate'
    self_link: u'http://.../ubuntu/hoary/+source/pmount/+pots/pmount'
    translation_domain: u'pmount'

== Pre-implementation notes ==

During the last UDS I have talked with Henning and we had a pre-implementation talk.
He agreed with the current exported attributes. He said that he can not comment on the technical implementation details for the API.

If you think it is needed you can ask someone experienced with the LP API to review this branch.

== Implementation details ==

POFile API export is just a stub to illustrate how pofiles are linked to the corresponding po templates.

I don't know how to fix the /usr/share lint errors. Any hints are much appreciated.

== Tests ==

  lib/lp/registry/stories/webservice/xx-distroseries.txt
  lib/lp/registry/stories/webservice/xx-project-registry.txt
  lib/lp/registry/stories/webservice/xx-source-package.txt
  lib/lp/translations/stories/webservice/xx-potemplate.txt

== Demo and Q/A ==

To get the potemplates for a sourcepackage
curl -ks https://launchpad.dev/api/devel/ubuntu/hoary/+source/evolution/all_translation_templates

To get the potemplates for a productseries
curl -ks https://launchpad.dev/api/devel/evolution/trunk/all_translation_templates

To get the potemplates for a distroseries
curl -ks https://launchpad.dev/api/devel/ubuntu/hoary/all_translation_templates

"all_translation_templates" are linked from the sourcepackage, distroseries and productseries
curl -ks https://launchpad.dev/api/devel/ubuntu/hoary/+source/evolution
curl -ks https://launchpad.dev/api/devel/ubuntu/hoary
curl -ks https://launchpad.dev/api/devel/evolution/trunk

= 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/translations/interfaces/webservice.py

== Pyflakes notices ==

lib/lp/translations/interfaces/webservice.py
    8: 'IHasTranslationImports' imported but unused
    8: 'ITranslationImportQueue' imported but unused
    8: 'ITranslationImportQueueEntry' imported but unused
    13: 'IPOTemplate' imported but unused
    15: 'IPOFile' imported but unused
warning: Not importing directory '/usr/share/pyshared/lazr': missing __init__.py
warning: Not importing directory '/usr/share/pyshared/lazr': missing __init__.py
warning: Not importing directory '/usr/share/pyshared/lazr': missing __init__.py

To post a comment you must log in.
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

Hi Adi,

It's really great that you're doing this. I'm reading it through and it looks good to me so far. One note though about the testing in xx-distroseries.txt.

You've got the problem here that you have to rely on existing test data, which can be a bit arbitrary. For example, the fact that there are 6 templates in the distroseries is pretty meaningless in the context of the test. But it's a lot of trouble to set up a fresh distroseries with fresh translation templates, especially if the existing tests don't do it either.

So here's an idea: instead of printing the exact number of templates, why not show that int(all_translation_templates['total_size']) is identical to len(list(getUtility(ILaunchpadCelebrities).ubuntu.getSeries('hoary'))).

Jeroen

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

Hi,

I have improved the tests to check for templates count from the db.

Here is the latest diff:

=== modified file 'lib/lp/registry/stories/webservice/xx-distroseries.txt'
--- lib/lp/registry/stories/webservice/xx-distroseries.txt 2010-05-17 09:35:18 +0000
+++ lib/lp/registry/stories/webservice/xx-distroseries.txt 2010-05-18 14:51:40 +0000
@@ -107,9 +107,19 @@
 All templates associated to a distribution series are available at the
 'all_translation_templates' collection link.

+ >>> from zope.component import getUtility
+ >>> from canonical.launchpad.interfaces.launchpad import (
+ ... ILaunchpadCelebrities)
+ >>> from lp.translations.interfaces.potemplate import IPOTemplateSet
+ >>> login('<email address hidden>')
+ >>> hoary = getUtility(ILaunchpadCelebrities).ubuntu.getSeries('hoary')
+ >>> templates = getUtility(IPOTemplateSet).getSubset(distroseries=hoary)
+ >>> db_count = len(list(templates))
+ >>> logout()
     >>> all_translation_templates = anon_webservice.get(
     ... '/ubuntu/hoary/all_translation_templates').jsonBody()
- >>> print all_translation_templates['total_size']
- 6
+ >>> api_count = all_translation_templates['total_size']
+ >>> api_count == db_count
+ True
     >>> print(all_translation_templates['entries'][0]['resource_type_link'])
     http://.../#potemplate

=== modified file 'lib/lp/registry/stories/webservice/xx-project-registry.txt'
--- lib/lp/registry/stories/webservice/xx-project-registry.txt 2010-05-17 09:35:18 +0000
+++ lib/lp/registry/stories/webservice/xx-project-registry.txt 2010-05-18 15:10:45 +0000
@@ -897,11 +897,20 @@
 "all_translation_templates" will list all POTemplates associated with this
 product series.

+
+ >>> from zope.component import getUtility
+ >>> from lp.translations.interfaces.potemplate import IPOTemplateSet
+ >>> login('<email address hidden>')
+ >>> templates = getUtility(
+ ... IPOTemplateSet).getSubset(productseries=foobadoo)
+ >>> db_count = len(list(templates))
+ >>> logout()
     >>> all_translation_templates = anon_webservice.get(
     ... babadoo_foobadoo[
     ... 'all_translation_templates_collection_link']).jsonBody()
- >>> print all_translation_templates['total_size']
- 1
+ >>> api_count = all_translation_templates['total_size']
+ >>> api_count == db_count
+ True
     >>> print(all_translation_templates['entries'][0]['resource_type_link'])
     http://.../#potemplate

=== modified file 'lib/lp/registry/stories/webservice/xx-source-package.txt'
--- lib/lp/registry/stories/webservice/xx-source-package.txt 2010-05-17 09:35:18 +0000
+++ lib/lp/registry/stories/webservice/xx-source-package.txt 2010-05-18 15:23:51 +0000
@@ -130,10 +130,27 @@
 All translation templates for a source package are available at the
 'all_translation_templates' collection link.

+
+ >>> from zope.component import getUtility
+ >>> from canonical.launchpad.interfaces.launchpad import (
+ ... ILaunchpadCelebrities)
+ >>> from lp.registry.interfaces.sourcepackagename import (
+ ... ISourcePackageNameSet)
+ >>> from lp.translations.interfaces.potemplate import IPOTemplateSet
+ >...

Read more...

Revision history for this message
Данило Шеган (danilo) wrote :
Download full text (6.0 KiB)

Hi Adi,

Thanks a lot for the work on this. Overall, it's pretty good.

У пон, 17. 05 2010. у 11:07 +0000, Adi Roiban пише:

> == Tests ==
>
> lib/lp/registry/stories/webservice/xx-distroseries.txt
> lib/lp/registry/stories/webservice/xx-project-registry.txt
> lib/lp/registry/stories/webservice/xx-source-package.txt
> lib/lp/translations/stories/webservice/xx-potemplate.txt

It usually helps if you provide a bin/test command which runs them for
lazy reviewers to copy-paste them :)

> == Demo and Q/A ==
>
> To get the potemplates for a sourcepackage
> curl -ks
https://launchpad.dev/api/devel/ubuntu/hoary/+source/evolution/all_translation_templates
>
> To get the potemplates for a productseries
> curl -ks
https://launchpad.dev/api/devel/evolution/trunk/all_translation_templates
>
> To get the potemplates for a distroseries
> curl -ks
https://launchpad.dev/api/devel/ubuntu/hoary/all_translation_templates
>
> "all_translation_templates" are linked from the sourcepackage,
distroseries and productseries
> curl -ks
https://launchpad.dev/api/devel/ubuntu/hoary/+source/evolution
> curl -ks https://launchpad.dev/api/devel/ubuntu/hoary
> curl -ks https://launchpad.dev/api/devel/evolution/trunk
>
> = 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/translations/interfaces/webservice.py

There are many more files with many more lint issues: you should try to
run it after you commit your final change, not before (if you've got
uncommitted changes, it lints just the uncommitted files). Please fix
all the issues.

> == Pyflakes notices ==
>
> lib/lp/translations/interfaces/webservice.py
> 8: 'IHasTranslationImports' imported but unused
> 8: 'ITranslationImportQueue' imported but unused
> 8: 'ITranslationImportQueueEntry' imported but unused
> 13: 'IPOTemplate' imported but unused
> 15: 'IPOFile' imported but unused

You should add the following to webservice.py if that's the intention:

__all__ = [
    'IHasTranslationImports',
    'IPOFile',
    'IPOTemplate',
    'ITranslationImportQueue',
    'ITranslationImportQueueEntry',
    ]

But, why is this necessary?

> warning: Not importing directory '/usr/share/pyshared/lazr': missing
__init__.py
> warning: Not importing directory '/usr/share/pyshared/lazr': missing
__init__.py
> warning: Not importing directory '/usr/share/pyshared/lazr': missing
__init__.py

This might be a bug in lazr that needs fixing. Can you please check and
file appropriate bug if it is?

> === modified file 'lib/canonical/launchpad/security.py'
> --- lib/canonical/launchpad/security.py 2010-05-17 12:43:53
+0000
> +++ lib/canonical/launchpad/security.py 2010-05-19 12:09:43
+0000
> @@ -61,8 +61,7 @@ from lp.hardwaredb.interfaces.hwdb impor
> from lp.services.worlddata.interfaces.language import ILanguage,
ILanguageSet
> from lp.translations.interfaces.languagepack import ILanguagePack
> from canonical.launchpad.interfaces.launchpad import (
> - IBazaarApplication, IHasBug, IHasDrivers, ILaunchpadCelebrities,
> - IPersonRoles)
> + IHas...

Read more...

Revision history for this message
Данило Шеган (danilo) :
review: Needs Fixing
Revision history for this message
Adi Roiban (adiroiban) wrote :
Download full text (18.4 KiB)

On Wed, 2010-05-19 at 14:43 +0000, Данило Шеган wrote:
> Hi Adi,
>
> Thanks a lot for the work on this. Overall, it's pretty good.
>
> У пон, 17. 05 2010. у 11:07 +0000, Adi Roiban пише:
>
> > == Tests ==
> >
> > lib/lp/registry/stories/webservice/xx-distroseries.txt
> > lib/lp/registry/stories/webservice/xx-project-registry.txt
> > lib/lp/registry/stories/webservice/xx-source-package.txt
> > lib/lp/translations/stories/webservice/xx-potemplate.txt
>
> It usually helps if you provide a bin/test command which runs them for
> lazy reviewers to copy-paste them :)

Sorry. I will add it with the next MP.

./bin/test -t xx-potemplate.txt -t xx-distroseries.txt \
 -t xx-project-registry.txt -t xx-source-package.txt

:)

[snip]
> > = 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/translations/interfaces/webservice.py
>
> There are many more files with many more lint issues: you should try to
> run it after you commit your final change, not before (if you've got
> uncommitted changes, it lints just the uncommitted files). Please fix
> all the issues.

I have fixed some of the other warning.

There are still some warnings for code like:

    @operation_parameters(
        pocket=Choice(
            title=_("Pocket"), required=True,
            vocabulary=DBEnumeratedType))

and it looks fine to me, but pylint complains about:

lib/lp/registry/interfaces/sourcepackage.py
    191: [C0322, ISourcePackage.getBranch] Operator not preceded by a
space
    required=True,
    ^
    vocabulary=DBEnumeratedType))

> > == Pyflakes notices ==
> >
> > lib/lp/translations/interfaces/webservice.py
> > 8: 'IHasTranslationImports' imported but unused
> > 8: 'ITranslationImportQueue' imported but unused
> > 8: 'ITranslationImportQueueEntry' imported but unused
> > 13: 'IPOTemplate' imported but unused
> > 15: 'IPOFile' imported but unused
>
> You should add the following to webservice.py if that's the intention:
>
> __all__ = [
> 'IHasTranslationImports',
> 'IPOFile',
> 'IPOTemplate',
> 'ITranslationImportQueue',
> 'ITranslationImportQueueEntry',
> ]
>
> But, why is this necessary?

Only interfaces listed in webservices.py will be exported by
lazr.restful.

Adding __all__ will still generate Pyflakes notices as they are still
unused in that file.

> > warning: Not importing directory '/usr/share/pyshared/lazr': missing
> __init__.py
> > warning: Not importing directory '/usr/share/pyshared/lazr': missing
> __init__.py
> > warning: Not importing directory '/usr/share/pyshared/lazr': missing
> __init__.py
>
> This might be a bug in lazr that needs fixing. Can you please check and
> file appropriate bug if it is?

Is is not a problem in lazr. I have the same problem with:
'/usr/share/pyshared/google': missing __init__.py

Those file were installed as dependencies for ubuntuone-client-gnome by
python-protobuf and python-lazr-uri, python-lazr-restfulclient and are
maintained by main Ubuntu developers... not in the LP PPA.

> > === modified file 'lib/...

Revision history for this message
Данило Шеган (danilo) wrote :
Download full text (6.3 KiB)

Hi Adi,

Thanks for the updates. There's still just a little bit more to do :)

У сре, 19. 05 2010. у 20:13 +0000, Adi Roiban пише:

> There are still some warnings for code like:
>
> @operation_parameters(
> pocket=Choice(
> title=_("Pocket"), required=True,
> vocabulary=DBEnumeratedType))
>
> and it looks fine to me, but pylint complains about:
>
> lib/lp/registry/interfaces/sourcepackage.py
> 191: [C0322, ISourcePackage.getBranch] Operator not preceded by a
> space
> required=True,
> ^
> vocabulary=DBEnumeratedType))
>
>
> > > == Pyflakes notices ==
> > >
> > > lib/lp/translations/interfaces/webservice.py
> > > 8: 'IHasTranslationImports' imported but unused
> > > 8: 'ITranslationImportQueue' imported but unused
> > > 8: 'ITranslationImportQueueEntry' imported but unused
> > > 13: 'IPOTemplate' imported but unused
> > > 15: 'IPOFile' imported but unused
> >
> > You should add the following to webservice.py if that's the intention:
> >
> > __all__ = [
> > 'IHasTranslationImports',
> > 'IPOFile',
> > 'IPOTemplate',
> > 'ITranslationImportQueue',
> > 'ITranslationImportQueueEntry',
> > ]
> >
> > But, why is this necessary?
>
> Only interfaces listed in webservices.py will be exported by
> lazr.restful.
>
> Adding __all__ will still generate Pyflakes notices as they are still
> unused in that file.

It actually fixed it for me. I'm attaching a patch which fixes all the
lint issues I've seen. It would be nicer if you did that instead :)

Also, "operator not preceded by space" lint issues in this particular
context seem to be opposed to our style guide. It would be best to
raise this on launchpad-dev mailing list to come to an agreement and to
see if we can fix either pylint or style guide.

> > > warning: Not importing directory '/usr/share/pyshared/lazr': missing
> > __init__.py
> > > warning: Not importing directory '/usr/share/pyshared/lazr': missing
> > __init__.py
> > > warning: Not importing directory '/usr/share/pyshared/lazr': missing
> > __init__.py
> >
> > This might be a bug in lazr that needs fixing. Can you please check and
> > file appropriate bug if it is?
>
> Is is not a problem in lazr. I have the same problem with:
> '/usr/share/pyshared/google': missing __init__.py
>
> Those file were installed as dependencies for ubuntuone-client-gnome by
> python-protobuf and python-lazr-uri, python-lazr-restfulclient and are
> maintained by main Ubuntu developers... not in the LP PPA.

Well, LAZR is a library shared between Launchpad and UbuntuOne. It's
still a bug that would need to be filed. I've worked-around it locally
by doing "sudo touch /usr/share/pyshared/lazr/__init__.py", and there is
probably no reason why the package shouldn't do the same for everybody.

> Should POTemplate and POFile API be available only to authenticated
> users?

Not, it's fine if they are anonymously available.

> > === modified file
> > 'lib/lp/registry/stories/webservice/xx-distroseries.txt'
> > === modified file
> > 'lib/lp/registry/stories/webservice/xx-project-registry.txt'
> > === modified file
> > 'lib/lp/registry/stories/webservice/...

Read more...

1=== modified file 'lib/lp/registry/interfaces/distroseries.py'
2--- lib/lp/registry/interfaces/distroseries.py 2010-05-19 17:04:11 +0000
3+++ lib/lp/registry/interfaces/distroseries.py 2010-05-20 16:10:38 +0000
4@@ -114,7 +114,7 @@ class DistroSeriesVersionField(UniqueFie
5
6 def _validate(self, version):
7 """See `UniqueField`."""
8- super(DistroSeriesVersionField, self)._validate(version)
9+ DistroSeriesVersionField._validate(self, version)
10 if not sane_version(version):
11 raise LaunchpadValidationError(
12 "%s is not a valid version" % version)
13@@ -423,38 +423,38 @@ class IDistroSeriesPublic(
14
15 @operation_parameters(
16 created_since_date=Datetime(
17- title=_("Created Since Timestamp"),
18- description=_(
19+ title = _("Created Since Timestamp"),
20+ description = _(
21 "Return items that are more recent than this timestamp."),
22- required=False),
23+ required = False),
24 status=Choice(
25 # Really PackageUploadCustomFormat, patched in
26 # _schema_circular_imports.py
27- vocabulary=DBEnumeratedType,
28- title=_("Package Upload Status"),
29- description=_("Return only items that have this status."),
30- required=False),
31+ vocabulary = DBEnumeratedType,
32+ title = _("Package Upload Status"),
33+ description = _("Return only items that have this status."),
34+ required = False),
35 archive=Reference(
36 # Really IArchive, patched in _schema_circular_imports.py
37- schema=Interface,
38- title=_("Archive"),
39- description=_("Return only items for this archive."),
40- required=False),
41+ schema = Interface,
42+ title = _("Archive"),
43+ description = _("Return only items for this archive."),
44+ required = False),
45 pocket=Choice(
46 # Really PackagePublishingPocket, patched in
47 # _schema_circular_imports.py
48- vocabulary=DBEnumeratedType,
49- title=_("Pocket"),
50- description=_("Return only items targeted to this pocket"),
51- required=False),
52+ vocabulary = DBEnumeratedType,
53+ title = _("Pocket"),
54+ description = _("Return only items targeted to this pocket"),
55+ required = False),
56 custom_type=Choice(
57 # Really PackageUploadCustomFormat, patched in
58 # _schema_circular_imports.py
59- vocabulary=DBEnumeratedType,
60- title=_("Custom Type"),
61- description=_("Return only items with custom files of this "
62+ vocabulary = DBEnumeratedType,
63+ title = _("Custom Type"),
64+ description = _("Return only items with custom files of this "
65 "type."),
66- required=False),
67+ required = False)
68 )
69 # Really IPackageUpload, patched in _schema_circular_imports.py
70 @operation_returns_collection_of(Interface)
71
72=== modified file 'lib/lp/registry/interfaces/sourcepackage.py'
73--- lib/lp/registry/interfaces/sourcepackage.py 2010-05-17 10:16:03 +0000
74+++ lib/lp/registry/interfaces/sourcepackage.py 2010-05-20 16:13:43 +0000
75@@ -187,8 +187,8 @@ class ISourcePackage(IBugTarget, IHasBra
76 # in _schema_circular_imports.
77 @operation_parameters(
78 pocket=Choice(
79- title=_("Pocket"), required=True,
80- vocabulary=DBEnumeratedType))
81+ title = _("Pocket"), required = True,
82+ vocabulary = DBEnumeratedType))
83 # Actually returns an IBranch, but we say Interface here to avoid circular
84 # imports. Correct interface specified in _schema_circular_imports.
85 @operation_returns_entry(Interface)
86@@ -205,9 +205,9 @@ class ISourcePackage(IBugTarget, IHasBra
87 # imports. Correct interface specific in _schema_circular_imports.
88 @operation_parameters(
89 pocket=Choice(
90- title=_("Pocket"), required=True,
91- vocabulary=DBEnumeratedType),
92- branch=Reference(Interface, title=_("Branch"), required=False))
93+ title = _("Pocket"), required = True,
94+ vocabulary = DBEnumeratedType),
95+ branch = Reference(Interface, title = _("Branch"), required = False))
96 @call_with(registrant=REQUEST_USER)
97 @export_write_operation()
98 def setBranch(pocket, branch, registrant):
99
100=== modified file 'lib/lp/translations/interfaces/webservice.py'
101--- lib/lp/translations/interfaces/webservice.py 2010-05-18 15:27:05 +0000
102+++ lib/lp/translations/interfaces/webservice.py 2010-05-20 16:02:50 +0000
103@@ -14,3 +14,11 @@ from lp.translations.interfaces.potempla
104 IPOTemplate)
105 from lp.translations.interfaces.pofile import (
106 IPOFile)
107+
108+__all__ = [
109+ 'IHasTranslationImports',
110+ 'IPOFile',
111+ 'IPOTemplate',
112+ 'ITranslationImportQueue',
113+ 'ITranslationImportQueueEntry',
114+ ]
115
116=== modified file 'lib/lp/translations/model/potemplate.py'
117--- lib/lp/translations/model/potemplate.py 2010-05-17 10:16:03 +0000
118+++ lib/lp/translations/model/potemplate.py 2010-05-20 16:19:09 +0000
119@@ -1297,8 +1297,8 @@ class POTemplateSet:
120 elif sourcepackagename is None:
121 # Multiple matches, and for a product not a package.
122 logging.warn(
123- "Found %d templates with path '%s' for productseries %s" % (
124- len(matches), path, productseries.title))
125+ "Found %d templates with path '%s' for productseries %s",
126+ len(matches), path, productseries.title)
127 return None
128 else:
129 # Multiple matches, for a distribution package. Prefer a
130@@ -1316,9 +1316,9 @@ class POTemplateSet:
131 else:
132 logging.warn(
133 "Found %d templates with path '%s' for package %s "
134- "(%d matched on from_sourcepackagename)." % (
135- len(matches), path, sourcepackagename.name,
136- len(preferred_matches)))
137+ "(%d matched on from_sourcepackagename).",
138+ len(matches), path, sourcepackagename.name,
139+ len(preferred_matches))
140 return None
141
142 @staticmethod
143@@ -1510,10 +1510,6 @@ class POTemplateToTranslationFileDataAda
144 if flag
145 ])
146
147- # Store sequences so we can detect later whether we changed the
148- # message.
149- sequence = row.sequence
150-
151 # Store the message.
152 messages.append(msgset)
153
154
Revision history for this message
Adi Roiban (adiroiban) wrote :
Download full text (10.5 KiB)

On Thu, 2010-05-20 at 16:33 +0000, Данило Шеган wrote:
> Hi Adi,
>
> Thanks for the updates. There's still just a little bit more to do :)
>
> У сре, 19. 05 2010. у 20:13 +0000, Adi Roiban пише:
>
> > There are still some warnings for code like:
> >
> > @operation_parameters(
> > pocket=Choice(
> > title=_("Pocket"), required=True,
> > vocabulary=DBEnumeratedType))
> >
> > and it looks fine to me, but pylint complains about:
> >
> > lib/lp/registry/interfaces/sourcepackage.py
> > 191: [C0322, ISourcePackage.getBranch] Operator not preceded by a
> > space
> > required=True,
> > ^
> > vocabulary=DBEnumeratedType))
> >
> >
> > > > == Pyflakes notices ==
> > > >
> > > > lib/lp/translations/interfaces/webservice.py
> > > > 8: 'IHasTranslationImports' imported but unused
> > > > 8: 'ITranslationImportQueue' imported but unused
> > > > 8: 'ITranslationImportQueueEntry' imported but unused
> > > > 13: 'IPOTemplate' imported but unused
> > > > 15: 'IPOFile' imported but unused
> > >
> > > You should add the following to webservice.py if that's the intention:
> > >
> > > __all__ = [
> > > 'IHasTranslationImports',
> > > 'IPOFile',
> > > 'IPOTemplate',
> > > 'ITranslationImportQueue',
> > > 'ITranslationImportQueueEntry',
> > > ]
> > >
> > > But, why is this necessary?
> >
> > Only interfaces listed in webservices.py will be exported by
> > lazr.restful.
> >
> > Adding __all__ will still generate Pyflakes notices as they are still
> > unused in that file.
>
> It actually fixed it for me. I'm attaching a patch which fixes all the
> lint issues I've seen. It would be nicer if you did that instead :)

I have no idea why this is not fixing for me.

Here is the output for `make lint` after applying your patch:

= 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
  lib/lp/registry/interfaces/sourcepackage.py
  lib/lp/translations/interfaces/webservice.py
  lib/lp/translations/model/potemplate.py

== Pyflakes notices ==

lib/lp/translations/interfaces/webservice.py
    16: 'IHasTranslationImports' imported but unused
    16: 'ITranslationImportQueue' imported but unused
    16: 'ITranslationImportQueueEntry' imported but unused
    21: 'IPOTemplate' imported but unused
    23: 'IPOFile' imported but unused

> Also, "operator not preceded by space" lint issues in this particular
> context seem to be opposed to our style guide. It would be best to
> raise this on launchpad-dev mailing list to come to an agreement and to
> see if we can fix either pylint or style guide.

I have sent and email to lp-dev ML.

> > > > warning: Not importing directory '/usr/share/pyshared/lazr': missing
> > > __init__.py
> > > > warning: Not importing directory '/usr/share/pyshared/lazr': missing
> > > __init__.py
> > > > warning: Not importing directory '/usr/share/pyshared/lazr': missing
> > > __init__.py
> > >
> > > This might be a bug in lazr that needs fixing. Can you please check and
> > > file app...

Revision history for this message
Данило Шеган (danilo) wrote :
Download full text (8.5 KiB)

Hey Adi, we are getting very close. Just a few more small issues to
fix.

У чет, 20. 05 2010. у 18:00 +0000, Adi Roiban пише:

> > It actually fixed it for me. I'm attaching a patch which fixes all the
> > lint issues I've seen. It would be nicer if you did that instead :)
>
> I have no idea why this is not fixing for me.
>
> Here is the output for `make lint` after applying your patch:
>
> = 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
> lib/lp/registry/interfaces/sourcepackage.py
> lib/lp/translations/interfaces/webservice.py
> lib/lp/translations/model/potemplate.py
>
> == Pyflakes notices ==
>
> lib/lp/translations/interfaces/webservice.py
> 16: 'IHasTranslationImports' imported but unused
> 16: 'ITranslationImportQueue' imported but unused
> 16: 'ITranslationImportQueueEntry' imported but unused
> 21: 'IPOTemplate' imported but unused
> 23: 'IPOFile' imported but unused

Hum, very interesting. Perhaps it's a problem with pyflakes
incompatibilities? (FWIW, it seems your branch now specifies __all__
twice)

FWIW, defining __all__ fixes pyflakes warnings for me, but not the
pylint warnings.

> > Also, "operator not preceded by space" lint issues in this particular
> > context seem to be opposed to our style guide. It would be best to
> > raise this on launchpad-dev mailing list to come to an agreement and to
> > see if we can fix either pylint or style guide.
>
> I have sent and email to lp-dev ML.

Cool. It seems everybody agrees that we should not change the
styleguide, but instead not use pylint as is. You can ignore those
warnings and revert that bit of my patch. It'd still be very useful to
resolve the pyflakes warnings that you are getting, though.

> I have filled a bug report for that:
> https://bugs.edge.launchpad.net/ubuntu/+source/lazr.restfulclient/+bug/583426

Excellent, thanks a lot.

> [snip]
> >
> > > > > === modified file 'lib/lp/translations/interfaces/potemplate.py'
> > > > ...
> > > > > source_file = Object(
> > > > > title=_('Source file for this translation template'),
> > > > > readonly=True, schema=ILibraryFileAlias)
> > > >
> > > > I don't remember why we decided not to export this one? It could be
> > > > useful for things like xpipo conversion (with XPI-based translations,
> > > > it should contain a reference to imported en-US.xpi).
> > > >
> > > > Though, we should probably just export it as the public URL, so not a
> > > > big deal (let's not block this branch on this).
> > >
> > > I will leave it for another branch, but for me, it would make sense if
> > > this attribute would be used for all translation templates, not only
> > > XPI.
> >
> > Well, we can reconstruct the gettext POT file completely from what we
> > have in the DB. Thus, it's not needed.
>
> My note was about creating a more consistent API so that in the API
> source_file (or whatever name we choose for it) would return the
> template file for all formats, not only XPI.

Sure...

> Instead of...

Read more...

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

On Thu, 2010-05-20 at 18:48 +0000, Данило Шеган wrote:
> Hey Adi, we are getting very close. Just a few more small issues to
> fix.
>
> У чет, 20. 05 2010. у 18:00 +0000, Adi Roiban пише:
>
> > > It actually fixed it for me. I'm attaching a patch which fixes all the
> > > lint issues I've seen. It would be nicer if you did that instead :)
> >
> > I have no idea why this is not fixing for me.
> >
> > Here is the output for `make lint` after applying your patch:
> >
> > = 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
> > lib/lp/registry/interfaces/sourcepackage.py
> > lib/lp/translations/interfaces/webservice.py
> > lib/lp/translations/model/potemplate.py
> >
> > == Pyflakes notices ==
> >
> > lib/lp/translations/interfaces/webservice.py
> > 16: 'IHasTranslationImports' imported but unused
> > 16: 'ITranslationImportQueue' imported but unused
> > 16: 'ITranslationImportQueueEntry' imported but unused
> > 21: 'IPOTemplate' imported but unused
> > 23: 'IPOFile' imported but unused
>
> Hum, very interesting. Perhaps it's a problem with pyflakes
> incompatibilities? (FWIW, it seems your branch now specifies __all__
> twice)
>
> FWIW, defining __all__ fixes pyflakes warnings for me, but not the
> pylint warnings.

I have removed the duplicate __all__ and only leave it before the
imports.

[snip]
> > > Add __all__: have you tried it at all? It totally works for me, and I'd
> > > be very surprised if it doesn't work for you. Also, __all__ is required
> > > in all our modules as well (importfascist used to check that before,
> > > maybe it doesn't anymore)
>
> I'd really like to get this resolved. It seems the line length is also
> slightly differently defined for you:
>
> === modified file 'lib/lp/translations/interfaces/pofile.py'
> --- lib/lp/translations/interfaces/pofile.py 2010-05-19 17:04:11 +0000
> +++ lib/lp/translations/interfaces/pofile.py 2010-05-20 17:58:16 +0000
> @@ -127,9 +127,9 @@
> '''),
> required=False)
>
> - translation_messages = Attribute(_('''
> - All `ITranslationMessage` objects related to this translation file.
> - '''))
> + translation_messages = Attribute(_(
> + "All `ITranslationMessage` objects related to this "
> + "translation file."))
>
> plural_forms = Int(
> title=_('Number of plural forms for the language of this PO file.'),
>
>
> This is not needed for me, because just putting it as
>
> translation_messages = Attribute(_(
> 'All `ITranslationMessage` objects related to this translation file.'
> ))
>
> works fine. Not that there's anything wrong with your approach, I am just noting this.

This will break the styleguide as there is a space before the ')'

> > > > === modified file 'lib/lp/translations/interfaces/pofile.py'
> > > > --- lib/lp/translations/interfaces/pofile.py 2010-03-08 21:06:34 +0000
> > > > +++ lib/lp/translations/interfaces/pofile.py 2010-05-19 17:04:11 +0...

Read more...

Revision history for this message
Данило Шеган (danilo) wrote :

Hi Adi,

У чет, 20. 05 2010. у 19:06 +0000, Adi Roiban пише:
> > This is not needed for me, because just putting it as
> >
> > translation_messages = Attribute(_(
> > 'All `ITranslationMessage` objects related to this translation file.'
> > ))
> >
> > works fine. Not that there's anything wrong with your approach, I am just noting this.
>
> This will break the styleguide as there is a space before the ')'

Heh, this is not really a space: it's a multiline wrapping of braces,
and we do exactly the same thing with tuples (we'd do it with imports as
well if we didn't have too many of those), as mentioned on [1]:

  something = (
      FirstVeryLongName,
      SecondVeryLongName,
  )

[1] https://dev.launchpad.net/PythonStyleGuide#Multiline%20braces

Anyway, as I said, your style is just as fine as well.

 review approve
 merge approve

I'll get this into ec2 land soon.

Cheers,
Danilo

review: Approve
Revision history for this message
Данило Шеган (danilo) wrote :
Download full text (3.5 KiB)

Hi Adi,

Ok, I realized there are more things to fix first. We've discussed them
over on IRC, but I will repeat myself a bit for reference.

 review needs-fixing

Attribute names should include as little gettextisms as possible:
"all_pofiles" and "all_potemplates" should be "translation_files" and
"translation_templates" (this one is already almost alright in the
branch: I am not sure about the "all").

And further on that topic, we should only show most relevant translation
templates: these are usually only the current, non-obsolete ones.
Obsolete templates are useful only for management purposes, and if we
want, we can export them either separately, or export all of

  getCurrentTranslationTemplates
  getObsoleteTranslationTemplates
  getTranslationTemplates

There is no need to wrap either of the methods in a property in order to
export them. What's else, these methods expose some implementation
details (like "just_ids" parameter, needed for performance reasons
elsewhere) and we should not get them exported as such.

Since I want to see this branch move in soon, I don't want to dwell on
cleaning up these right now.

Now, performance reasons are very important to consider here. We may
have to investigate whether `doNotSnapshot` is needed, and how batching
works over Collection results ([1] suggests it splits results into
batches of 75, but we'd have to check that). If API framework splits
the results into batches and only serializes a single batch (vs
serializing a whole result set), it'd be ok even if we don't use
doNotSnapshot (unless it turns out to be too slow once it hits
edge/staging, when we'll need to fix it).

Also, at the moment, we should export only the most useful list of
templates (because most of this code needs slight refactoring, we should
not export all methods and then stop exporting them soon after). At the
moment, for the "reporting" use case, I believe only "current" templates
make sense to be exported. Now, exporting it with just_ids parameter is
fine as well, and we can fix it with the refactoring in the future.

Next, we did mention how it should already allow read-write access if
you've got sufficient privileges. In my tests that has turned out to be
false. This is definitely not a blocker (it's ok if we start with
read-only API), but we at least need to understand why this is so and
how is write access created (so we don't do it by accident).

Example script I tried out (after doing 'make run' in this branch, run
this in a python console and log in with sufficient privileges, eg.
<email address hidden>:test):

  cachedir = "/home/danilo/.launchpadlib/cache/"
  from launchpadlib.launchpad import Launchpad
  launchpad = Launchpad.login_with('dev testing', 'dev', cachedir)
  ubuntu = launchpad.distributions['ubuntu']
  hoary = ubuntu.series[2]
  pots = hoary.all_translation_templates
  pot = pots[0]
  pot.translation_domain = 'something_else'
  pot.lp_save()

So, suggested course of action to get this landed:

 0. fix "all_pofile" attribute name
 1. Check if we get batching of results on API method calls/attributes
for free (without needing to do anything else)
 2. If 1 is true, check if batching snapshots the en...

Read more...

review: Needs Fixing
Revision history for this message
Adi Roiban (adiroiban) wrote :
Download full text (16.2 KiB)

On Wed, 2010-05-26 at 16:12 +0000, Данило Шеган wrote:
> Review: Needs Fixing
> Hi Adi,
>
> Ok, I realized there are more things to fix first. We've discussed them
> over on IRC, but I will repeat myself a bit for reference.
>
> review needs-fixing
>
> Attribute names should include as little gettextisms as possible:
> "all_pofiles" and "all_potemplates" should be "translation_files" and
> "translation_templates" (this one is already almost alright in the
> branch: I am not sure about the "all").

renamed all_pofiles to translation_files
removed all_translation_templates since its content will be provided by
a method.

I have renamed the name used for exporting pofile and potemplate entries
and collections.

> And further on that topic, we should only show most relevant translation
> templates: these are usually only the current, non-obsolete ones.
> Obsolete templates are useful only for management purposes, and if we
> want, we can export them either separately, or export all of
>
> getCurrentTranslationTemplates
> getObsoleteTranslationTemplates
> getTranslationTemplates
>
> There is no need to wrap either of the methods in a property in order to
> export them. What's else, these methods expose some implementation
> details (like "just_ids" parameter, needed for performance reasons
> elsewhere) and we should not get them exported as such.
>
> Since I want to see this branch move in soon, I don't want to dwell on
> cleaning up these right now.

We can hide the "just_ids" in the exported API call.
We can export getCurrentTranslationTemplates() method, with just_ids
hidden and always set to False.

I still think that exporting getTranslationTemplates() will make the API
simpler (rather than exporting all 3 methods) and the client side check
for active/inactive templates is not that complicated.

> Now, performance reasons are very important to consider here. We may
> have to investigate whether `doNotSnapshot` is needed, and how batching
> works over Collection results ([1] suggests it splits results into
> batches of 75, but we'd have to check that). If API framework splits
> the results into batches and only serializes a single batch (vs
> serializing a whole result set), it'd be ok even if we don't use
> doNotSnapshot (unless it turns out to be too slow once it hits
> edge/staging, when we'll need to fix it).

I will raise this discussion over the lp-dev ML. I still have to read
the code and see exactly why, where and how those snosphots are
needed/used in order to ask a valid question.

I think that we should land this branch and the start of next cycle.
The first attempt should be without doNotSnapshot. If something goes
wrong we can reconsider our options.

> Also, at the moment, we should export only the most useful list of
> templates (because most of this code needs slight refactoring, we should
> not export all methods and then stop exporting them soon after). At the
> moment, for the "reporting" use case, I believe only "current" templates
> make sense to be exported. Now, exporting it with just_ids parameter is
> fine as well, and we can fix it with the refactoring in the future.

See above comments.

> Next, we...

Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

We've been discussing this in IRC. It turns out that IDistroSeries.getTranslationTemplates wasn't batching properly because it used shortlist() to limit its results. As a side effect, that listifies the query result—causing all its results to be fetched and marshaled. The batching logic then slices the resulting list instead of the query result, which is somewhat pointless.

The docstring deliberately makes no promise that getTranslationTemplates returns a list, so it's perfectly valid to return the query result directly instead of a shortlisted version of it. Unfortunately the distroseries translations copying script does assert that len(child.getTranslationTemplates()) == 0. This assumes a list-like return value; with a query it should assert that child.getTranslationTemplates.is_empty().

Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

Revision 10369 looks good to me (except the part where you update the getTranslationTemplates docstring to say it returns a list) but brings another problem to my attention: the purpose of the privileges check in POTemplateSubsetNavigation.traverse is a bit unclear. The comment also contains a typo ("is a is a").

You can also make the check easier to read by eliminating individual, simple cases:

if official_rosetta and potemplate.is_current:
    # This template is available for translation.
    return potemplate
elif check_permission('launchpad.Edit', potemplate):
    # User has special privileges for this template.
    return potemplate
else:
    raise NotFoundError(name)

Also, a bit higher up, I don't think the assertion for "unknown context" carries its weight. We have database constraints enforcing it. So might as well do something simpler such as:

if potemplate.distribution is None:
    product_or_distro = potemplate.productseries.product
else:
    product_or_distro = potemplate.distroseries.distribution
official_rosetta = product_or_distro.official_rosetta

Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

For the record I should add that we first discussed the bigger issues (performance, security, various batching hazards) as per Danilo's proposed course of action above. All those points have been satisfied, so the nitpicking about small bits of code means that the branch is in good shape. There is no problematic snapshotting, performance is good, and with the removal of that shortlist we no longer fetch all templates in a distroseries at once.

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

Hi,

Here is the diff with the changes discussed in the latest comment.

If everything is ok, can you please sent this branch to ec2-test?

=== modified file 'lib/lp/registry/model/distroseries.py'
--- lib/lp/registry/model/distroseries.py 2010-05-19 17:24:21 +0000
+++ lib/lp/registry/model/distroseries.py 2010-06-03 15:06:35 +0000
@@ -1886,7 +1886,7 @@
         """See `IHasTranslationTemplates`."""
         result = POTemplate.selectBy(distroseries=self,
                                      orderBy=['-priority', 'name'])
- return shortlist(result, 2000)
+ return result

     def getCurrentTranslationTemplates(self, just_ids=False):
         """See `IHasTranslationTemplates`."""

=== modified file 'lib/lp/registry/model/productseries.py'
--- lib/lp/registry/model/productseries.py 2010-05-17 09:35:18 +0000
+++ lib/lp/registry/model/productseries.py 2010-06-03 15:06:14 +0000
@@ -427,7 +427,7 @@
         """See `IHasTranslationTemplates`."""
         result = POTemplate.selectBy(
             productseries=self, orderBy=['-priority', 'name'])
- return shortlist(result, 300)
+ return result

     def getCurrentTranslationTemplates(self, just_ids=False):
         """See `IHasTranslationTemplates`."""

=== modified file 'lib/lp/registry/model/sourcepackage.py'
--- lib/lp/registry/model/sourcepackage.py 2010-05-17 09:35:18 +0000
+++ lib/lp/registry/model/sourcepackage.py 2010-06-03 15:19:06 +0000
@@ -602,7 +602,7 @@
         result = POTemplate.selectBy(
             distroseries=self.distroseries,
             sourcepackagename=self.sourcepackagename)
- return shortlist(result.orderBy(['-priority', 'name']), 300)
+ return result.orderBy(['-priority', 'name'])

     def getCurrentTranslationTemplates(self, just_ids=False):
         """See `IHasTranslationTemplates`."""

=== modified file 'lib/lp/translations/browser/potemplate.py'
--- lib/lp/translations/browser/potemplate.py 2010-05-27 14:38:56 +0000
+++ lib/lp/translations/browser/potemplate.py 2010-06-03 16:02:24 +0000
@@ -774,18 +774,17 @@

         # Get whether the target for the requested template is officially
         # using Launchpad Translations.
- if potemplate.distribution is not None:
- official_rosetta = potemplate.distribution.official_rosetta
- elif potemplate.product is not None:
- official_rosetta = potemplate.product.official_rosetta
+ if potemplate.distribution is None:
+ product_or_distro = potemplate.productseries.product
         else:
- raise AssertionError('Unknown context for %s' % potemplate.title)
+ product_or_distro = potemplate.distroseries.distribution
+ official_rosetta = product_or_distro.official_rosetta

- if ((official_rosetta and potemplate.iscurrent) or
- check_permission('launchpad.Edit', potemplate)):
- # The target is using officially Launchpad Translations and the
- # template is available to be translated, or the user is a is a
- # Launchpad administrator in which case we show everything.
+ if official_rosetta and potemplate.iscurrent:
+ # This template is av...

Read more...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/canonical/launchpad/interfaces/_schema_circular_imports.py'
2--- lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2010-05-14 08:05:23 +0000
3+++ lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2010-06-03 16:14:30 +0000
4@@ -24,6 +24,8 @@
5 patch_plain_parameter_type, patch_choice_parameter_type,
6 patch_reference_property)
7
8+from canonical.launchpad.interfaces.message import (
9+ IIndexedMessage, IMessage, IUserToUserEmail)
10 from lp.registry.interfaces.structuralsubscription import (
11 IStructuralSubscription, IStructuralSubscriptionTarget)
12 from lp.bugs.interfaces.bug import IBug, IFrontPageBugAddForm
13@@ -52,16 +54,17 @@
14 ISourcePackageRecipe)
15 from lp.code.interfaces.sourcepackagerecipebuild import (
16 ISourcePackageRecipeBuild)
17+from lp.hardwaredb.interfaces.hwdb import HWBus, IHWSubmission
18 from lp.registry.interfaces.distribution import IDistribution
19 from lp.registry.interfaces.distributionmirror import IDistributionMirror
20 from lp.registry.interfaces.distributionsourcepackage import (
21 IDistributionSourcePackage)
22 from lp.registry.interfaces.distroseries import IDistroSeries
23 from lp.registry.interfaces.person import IPerson, IPersonPublic
24-from lp.hardwaredb.interfaces.hwdb import HWBus, IHWSubmission
25 from lp.registry.interfaces.pocket import PackagePublishingPocket
26 from lp.registry.interfaces.product import IProduct
27 from lp.registry.interfaces.productseries import IProductSeries
28+from lp.registry.interfaces.sourcepackage import ISourcePackage
29 from lp.soyuz.interfaces.archive import IArchive
30 from lp.soyuz.interfaces.archivepermission import (
31 IArchivePermission)
32@@ -77,9 +80,9 @@
33 from lp.soyuz.interfaces.queue import (
34 IPackageUpload, PackageUploadCustomFormat, PackageUploadStatus)
35 from lp.soyuz.interfaces.sourcepackagerelease import ISourcePackageRelease
36-from lp.registry.interfaces.sourcepackage import ISourcePackage
37-from canonical.launchpad.interfaces.message import (
38- IIndexedMessage, IMessage, IUserToUserEmail)
39+from lp.translations.interfaces.pofile import IPOFile
40+from lp.translations.interfaces.potemplate import (
41+ IPOTemplate, IPOTemplateSharingSubset, IPOTemplateSubset)
42
43
44 IBranch['bug_branches'].value_type.schema = IBugBranch
45@@ -394,5 +397,16 @@
46 # IBugTracker
47 patch_reference_property(IBugTracker, 'owner', IPerson)
48
49+# IPOTemplate
50+patch_collection_property(IPOTemplate, 'pofiles', IPOFile)
51+patch_reference_property(IPOTemplate, 'product', IProduct)
52+
53+# IPOTemplateSubset
54+patch_reference_property(IPOTemplateSubset, 'distroseries', IDistroSeries)
55+patch_reference_property(IPOTemplateSubset, 'productseries', IProductSeries)
56+
57+# IPOTemplateSharingSubset
58+patch_reference_property(IPOTemplateSharingSubset, 'product', IProduct)
59+
60 # IProductSeries
61 patch_reference_property(IProductSeries, 'product', IProduct)
62
63=== modified file 'lib/canonical/launchpad/security.py'
64--- lib/canonical/launchpad/security.py 2010-05-24 04:54:44 +0000
65+++ lib/canonical/launchpad/security.py 2010-06-03 16:14:30 +0000
66@@ -61,8 +61,7 @@
67 from lp.services.worlddata.interfaces.language import ILanguage, ILanguageSet
68 from lp.translations.interfaces.languagepack import ILanguagePack
69 from canonical.launchpad.interfaces.launchpad import (
70- IBazaarApplication, IHasBug, IHasDrivers, ILaunchpadCelebrities,
71- IPersonRoles)
72+ IHasBug, IHasDrivers, ILaunchpadCelebrities, IPersonRoles)
73 from lp.registry.interfaces.role import IHasOwner
74 from lp.registry.interfaces.location import IPersonLocation
75 from lp.registry.interfaces.mailinglist import IMailingListSet
76@@ -1159,6 +1158,11 @@
77 self.obj).checkAuthenticated(user))
78
79
80+class ViewPOTemplates(AnonymousAuthorization):
81+ """Anyone can view an IPOTemplate."""
82+ usedfor = IPOTemplate
83+
84+
85 class AdminPOTemplateDetails(OnlyRosettaExpertsAndAdmins):
86 """Controls administration of an `IPOTemplate`.
87
88@@ -1211,6 +1215,11 @@
89 usedfor = IProductSeries
90
91
92+class ViewPOFile(AnonymousAuthorization):
93+ """Anyone can view an IPOFile."""
94+ usedfor = IPOFile
95+
96+
97 class EditPOFileDetails(EditByOwnersOrAdmins):
98 usedfor = IPOFile
99
100
101=== modified file 'lib/lp/registry/configure.zcml'
102--- lib/lp/registry/configure.zcml 2010-04-27 13:57:18 +0000
103+++ lib/lp/registry/configure.zcml 2010-06-03 16:14:30 +0000
104@@ -113,8 +113,6 @@
105 <allow
106 interface="lp.translations.interfaces.translationimportqueue.IHasTranslationImports"/>
107 <allow
108- interface="lp.translations.interfaces.potemplate.IHasTranslationTemplates"/>
109- <allow
110 interface="canonical.launchpad.interfaces.ICanPublishPackages"/>
111 <require
112 permission="launchpad.Edit"
113@@ -1325,8 +1323,6 @@
114 interface="lp.bugs.interfaces.bugtarget.IHasBugHeat"/>
115 <allow
116 interface="lp.translations.interfaces.translationimportqueue.IHasTranslationImports"/>
117- <allow
118- interface="lp.translations.interfaces.potemplate.IHasTranslationTemplates"/>
119 <require
120 permission="launchpad.Edit"
121 set_attributes="product name owner driver summary branch
122@@ -1529,8 +1525,6 @@
123 interface="canonical.launchpad.interfaces.IHasBuildRecords"/>
124 <allow
125 interface="lp.translations.interfaces.translationimportqueue.IHasTranslationImports"/>
126- <allow
127- interface="lp.translations.interfaces.potemplate.IHasTranslationTemplates"/>
128
129 <!-- IQuestionTarget -->
130
131
132=== modified file 'lib/lp/registry/interfaces/distroseries.py'
133--- lib/lp/registry/interfaces/distroseries.py 2010-04-27 22:22:20 +0000
134+++ lib/lp/registry/interfaces/distroseries.py 2010-06-03 16:14:30 +0000
135@@ -51,6 +51,7 @@
136 IStructuralSubscriptionTarget)
137 from lp.soyuz.interfaces.buildrecords import IHasBuildRecords
138 from lp.translations.interfaces.languagepack import ILanguagePack
139+from lp.translations.interfaces.potemplate import IHasTranslationTemplates
140
141
142 class DistroSeriesNameField(ContentNameField):
143@@ -113,7 +114,7 @@
144
145 def _validate(self, version):
146 """See `UniqueField`."""
147- super(DistroSeriesVersionField, self)._validate(version)
148+ DistroSeriesVersionField._validate(self, version)
149 if not sane_version(version):
150 raise LaunchpadValidationError(
151 "%s is not a valid version" % version)
152@@ -142,7 +143,7 @@
153 class IDistroSeriesPublic(
154 ISeriesMixin, IHasAppointedDriver, IHasOwner, IBugTarget,
155 ISpecificationGoal, IHasMilestones, IHasOfficialBugTags,
156- IHasBuildRecords):
157+ IHasBuildRecords, IHasTranslationTemplates):
158 """Public IDistroSeries properties."""
159
160 id = Attribute("The distroseries's unique number.")
161@@ -593,7 +594,8 @@
162 :param dsc: string, original content of the dsc file
163 :param copyright: string, the original debian/copyright content
164 :param changelog: LFA ID of the debian/changelog file in librarian
165- :param changelog_entry: string, changelog extracted from the changesfile
166+ :param changelog_entry: string, changelog extracted from the
167+ changesfile
168 :param architecturehintlist: string, DSC architectures
169 :param builddepends: string, DSC build dependencies
170 :param builddependsindep: string, DSC architecture independent build
171
172=== modified file 'lib/lp/registry/interfaces/productseries.py'
173--- lib/lp/registry/interfaces/productseries.py 2010-03-25 20:36:46 +0000
174+++ lib/lp/registry/interfaces/productseries.py 2010-06-03 16:14:30 +0000
175@@ -32,6 +32,7 @@
176 from lp.registry.interfaces.productrelease import IProductRelease
177 from lp.blueprints.interfaces.specificationtarget import (
178 ISpecificationGoal)
179+from lp.translations.interfaces.potemplate import IHasTranslationTemplates
180 from lp.translations.interfaces.translations import (
181 TranslationsBranchImportMode)
182 from canonical.launchpad.interfaces.validation import validate_url
183@@ -91,7 +92,8 @@
184
185 class IProductSeriesPublic(
186 ISeriesMixin, IHasAppointedDriver, IHasOwner, IBugTarget,
187- ISpecificationGoal, IHasMilestones, IHasOfficialBugTags):
188+ ISpecificationGoal, IHasMilestones, IHasOfficialBugTags,
189+ IHasTranslationTemplates):
190 """Public IProductSeries properties."""
191 # XXX Mark Shuttleworth 2004-10-14: Would like to get rid of id in
192 # interfaces, as soon as SQLobject allows using the object directly
193
194=== modified file 'lib/lp/registry/interfaces/sourcepackage.py'
195--- lib/lp/registry/interfaces/sourcepackage.py 2010-04-01 20:26:16 +0000
196+++ lib/lp/registry/interfaces/sourcepackage.py 2010-06-03 16:14:30 +0000
197@@ -19,21 +19,23 @@
198 from zope.interface import Attribute, Interface
199 from zope.schema import Choice, Object, TextLine
200 from lazr.enum import DBEnumeratedType, DBItem
201+from lazr.restful.fields import Reference, ReferenceChoice
202+from lazr.restful.declarations import (
203+ call_with, export_as_webservice_entry, export_read_operation,
204+ export_write_operation, exported, operation_parameters,
205+ operation_returns_entry, REQUEST_USER)
206
207 from canonical.launchpad import _
208 from lp.bugs.interfaces.bugtarget import IBugTarget, IHasOfficialBugTags
209 from lp.code.interfaces.hasbranches import (
210 IHasBranches, IHasCodeImports, IHasMergeProposals)
211 from lp.soyuz.interfaces.component import IComponent
212-from lazr.restful.fields import Reference, ReferenceChoice
213-from lazr.restful.declarations import (
214- call_with, export_as_webservice_entry, export_read_operation,
215- export_write_operation, exported, operation_parameters,
216- operation_returns_entry, REQUEST_USER)
217+from lp.translations.interfaces.potemplate import IHasTranslationTemplates
218
219
220 class ISourcePackage(IBugTarget, IHasBranches, IHasMergeProposals,
221- IHasOfficialBugTags, IHasCodeImports):
222+ IHasOfficialBugTags, IHasCodeImports,
223+ IHasTranslationTemplates):
224 """A SourcePackage. See the MagicSourcePackage specification. This
225 interface preserves as much as possible of the old SourcePackage
226 interface from the SourcePackage table, with the new table-less
227
228=== modified file 'lib/lp/registry/model/distroseries.py'
229--- lib/lp/registry/model/distroseries.py 2010-05-12 23:23:19 +0000
230+++ lib/lp/registry/model/distroseries.py 2010-06-03 16:14:30 +0000
231@@ -103,7 +103,6 @@
232 from lp.translations.interfaces.languagepack import LanguagePackType
233 from canonical.launchpad.interfaces.librarian import ILibraryFileAliasSet
234 from lp.soyuz.interfaces.queue import PackageUploadStatus
235-from lp.translations.interfaces.potemplate import IHasTranslationTemplates
236 from lp.soyuz.interfaces.publishedpackage import (
237 IPublishedPackageSet)
238 from lp.soyuz.interfaces.publishing import (
239@@ -131,7 +130,7 @@
240 """A particular series of a distribution."""
241 implements(
242 ICanPublishPackages, IDistroSeries, IHasBugHeat, IHasBuildRecords,
243- IHasQueueItems, IHasTranslationTemplates)
244+ IHasQueueItems)
245
246 _table = 'DistroSeries'
247 _defaultOrder = ['distribution', 'version']
248@@ -1303,15 +1302,13 @@
249 find_spec = (
250 DistroSeriesPackageCache,
251 BinaryPackageName,
252- SQL('rank(fti, ftq(%s)) AS rank' % sqlvalues(text))
253- )
254+ SQL('rank(fti, ftq(%s)) AS rank' % sqlvalues(text)))
255 origin = [
256 DistroSeriesPackageCache,
257 Join(
258 BinaryPackageName,
259 DistroSeriesPackageCache.binarypackagename ==
260- BinaryPackageName.id
261- )
262+ BinaryPackageName.id),
263 ]
264
265 # Note: When attempting to convert the query below into straight
266@@ -1325,7 +1322,7 @@
267 DistroSeriesPackageCache.name ILIKE '%%' || %s || '%%')
268 """ % (quote(self),
269 quote(self.distribution.all_distro_archive_ids),
270- quote(text), quote_like(text))
271+ quote(text), quote_like(text)),
272 ).config(distinct=True)
273
274 # Create a function that will decorate the results, converting
275@@ -1889,7 +1886,7 @@
276 """See `IHasTranslationTemplates`."""
277 result = POTemplate.selectBy(distroseries=self,
278 orderBy=['-priority', 'name'])
279- return shortlist(result, 2000)
280+ return result
281
282 def getCurrentTranslationTemplates(self, just_ids=False):
283 """See `IHasTranslationTemplates`."""
284
285=== modified file 'lib/lp/registry/model/productseries.py'
286--- lib/lp/registry/model/productseries.py 2010-03-24 02:53:42 +0000
287+++ lib/lp/registry/model/productseries.py 2010-06-03 16:14:30 +0000
288@@ -53,7 +53,6 @@
289 from canonical.launchpad.helpers import shortlist
290 from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
291 from lp.registry.interfaces.packaging import PackagingType
292-from lp.translations.interfaces.potemplate import IHasTranslationTemplates
293 from lp.blueprints.interfaces.specification import (
294 SpecificationDefinitionStatus, SpecificationFilter,
295 SpecificationGoalStatus, SpecificationImplementationStatus,
296@@ -84,7 +83,7 @@
297 HasTranslationImportsMixin, HasTranslationTemplatesMixin,
298 StructuralSubscriptionTargetMixin, SeriesMixin):
299 """A series of product releases."""
300- implements(IHasBugHeat, IProductSeries, IHasTranslationTemplates)
301+ implements(IHasBugHeat, IProductSeries)
302
303 _table = 'ProductSeries'
304
305@@ -428,7 +427,7 @@
306 """See `IHasTranslationTemplates`."""
307 result = POTemplate.selectBy(
308 productseries=self, orderBy=['-priority', 'name'])
309- return shortlist(result, 300)
310+ return result
311
312 def getCurrentTranslationTemplates(self, just_ids=False):
313 """See `IHasTranslationTemplates`."""
314@@ -614,4 +613,3 @@
315 import_mode != TranslationsBranchImportMode.NO_IMPORT)
316
317 return Store.of(branch).find(ProductSeries, And(*conditions))
318-
319
320=== modified file 'lib/lp/registry/model/sourcepackage.py'
321--- lib/lp/registry/model/sourcepackage.py 2010-04-14 18:31:51 +0000
322+++ lib/lp/registry/model/sourcepackage.py 2010-06-03 16:14:30 +0000
323@@ -54,7 +54,6 @@
324 from canonical.launchpad.helpers import shortlist
325 from lp.soyuz.interfaces.buildrecords import IHasBuildRecords
326 from lp.registry.interfaces.packaging import PackagingType
327-from lp.translations.interfaces.potemplate import IHasTranslationTemplates
328 from lp.registry.interfaces.distribution import NoPartnerArchive
329 from lp.registry.interfaces.pocket import PackagePublishingPocket
330 from lp.soyuz.interfaces.publishing import PackagePublishingStatus
331@@ -170,8 +169,7 @@
332 """
333
334 implements(
335- ISourcePackage, IHasBugHeat, IHasBuildRecords,
336- IHasTranslationTemplates, IQuestionTarget)
337+ ISourcePackage, IHasBugHeat, IHasBuildRecords, IQuestionTarget)
338
339 classProvides(ISourcePackageFactory)
340
341@@ -604,7 +602,7 @@
342 result = POTemplate.selectBy(
343 distroseries=self.distroseries,
344 sourcepackagename=self.sourcepackagename)
345- return shortlist(result.orderBy(['-priority', 'name']), 300)
346+ return result.orderBy(['-priority', 'name'])
347
348 def getCurrentTranslationTemplates(self, just_ids=False):
349 """See `IHasTranslationTemplates`."""
350
351=== modified file 'lib/lp/registry/stories/webservice/xx-distroseries.txt'
352--- lib/lp/registry/stories/webservice/xx-distroseries.txt 2010-03-06 01:24:05 +0000
353+++ lib/lp/registry/stories/webservice/xx-distroseries.txt 2010-06-03 16:14:30 +0000
354@@ -1,4 +1,5 @@
355-= Distribution Series =
356+Distribution Series
357+===================
358
359 We can get a distroseries object via a distribution object in several ways:
360
361
362=== modified file 'lib/lp/registry/stories/webservice/xx-project-registry.txt'
363--- lib/lp/registry/stories/webservice/xx-project-registry.txt 2010-05-07 18:18:56 +0000
364+++ lib/lp/registry/stories/webservice/xx-project-registry.txt 2010-06-03 16:14:30 +0000
365@@ -858,8 +858,10 @@
366 >>> babadoo_foobadoo = webservice.get('/babadoo/foobadoo').jsonBody()
367 >>> pprint_entry(babadoo_foobadoo)
368 active: True
369- active_milestones_collection_link: u'http://.../babadoo/foobadoo/active_milestones'
370- all_milestones_collection_link: u'http://.../babadoo/foobadoo/all_milestones'
371+ active_milestones_collection_link:
372+ u'http://.../babadoo/foobadoo/active_milestones'
373+ all_milestones_collection_link:
374+ u'http://.../babadoo/foobadoo/all_milestones'
375 branch_link: u'http://.../~babadoo-owner/babadoo/fooey'
376 bug_reporting_guidelines: None
377 date_created: u'...'
378@@ -1088,12 +1090,15 @@
379 >>> pprint_entry(result)
380 date_uploaded: u'2005-06-06T08:59:51.926792+00:00'
381 description: None
382- file_link: u'http://.../firefox/trunk/0.9.2/+file/firefox_0.9.2.orig.tar.gz/file'
383+ file_link:
384+ u'http://.../firefox/trunk/0.9.2/+file/firefox_0.9.2.orig.tar.gz/file'
385 file_type: u'Code Release Tarball'
386 project_release_link: u'http://.../firefox/trunk/0.9.2'
387 resource_type_link: u'http://.../#project_release_file'
388- self_link: u'http://.../firefox/trunk/0.9.2/+file/firefox_0.9.2.orig.tar.gz'
389- signature_link: u'http://.../firefox/trunk/0.9.2/+file/firefox_0.9.2.orig.tar.gz/signature'
390+ self_link:
391+ u'http://.../firefox/trunk/0.9.2/+file/firefox_0.9.2.orig.tar.gz'
392+ signature_link:
393+ u'http://.../trunk/0.9.2/+file/firefox_0.9.2.orig.tar.gz/signature'
394
395 The actual file redirects to the librarian when accessed.
396
397
398=== modified file 'lib/lp/translations/browser/potemplate.py'
399--- lib/lp/translations/browser/potemplate.py 2010-04-22 17:07:56 +0000
400+++ lib/lp/translations/browser/potemplate.py 2010-06-03 16:14:30 +0000
401@@ -763,8 +763,8 @@
402 def traverse(self, name):
403 """Return the IPOTemplate associated with the given name."""
404
405- assert self.request.method in ['GET', 'HEAD', 'POST'], (
406- 'We only know about GET, HEAD, and POST')
407+ assert self.request.method in ['GET', 'HEAD', 'PATCH', 'POST'], (
408+ 'We only know about GET, HEAD, PATCH and POST')
409
410 # Get the requested potemplate.
411 potemplate = self.context.getPOTemplateByName(name)
412@@ -774,18 +774,17 @@
413
414 # Get whether the target for the requested template is officially
415 # using Launchpad Translations.
416- if potemplate.distribution is not None:
417- official_rosetta = potemplate.distribution.official_rosetta
418- elif potemplate.product is not None:
419- official_rosetta = potemplate.product.official_rosetta
420+ if potemplate.distribution is None:
421+ product_or_distro = potemplate.productseries.product
422 else:
423- raise AssertionError('Unknown context for %s' % potemplate.title)
424+ product_or_distro = potemplate.distroseries.distribution
425+ official_rosetta = product_or_distro.official_rosetta
426
427- if ((official_rosetta and potemplate.iscurrent) or
428- check_permission('launchpad.Edit', potemplate)):
429- # The target is using officially Launchpad Translations and the
430- # template is available to be translated, or the user is a is a
431- # Launchpad administrator in which case we show everything.
432+ if official_rosetta and potemplate.iscurrent:
433+ # This template is available for translation.
434+ return potemplate
435+ elif check_permission('launchpad.Edit', potemplate):
436+ # User has Edit privileges for this template and can access it.
437 return potemplate
438 else:
439 raise NotFoundError(name)
440
441=== modified file 'lib/lp/translations/interfaces/pofile.py'
442--- lib/lp/translations/interfaces/pofile.py 2010-01-08 12:39:51 +0000
443+++ lib/lp/translations/interfaces/pofile.py 2010-06-03 16:14:30 +0000
444@@ -19,6 +19,9 @@
445 from zope.schema.vocabulary import (
446 getVocabularyRegistry, SimpleTerm, SimpleVocabulary)
447
448+from lazr.restful.declarations import (
449+ exported, export_as_webservice_entry)
450+
451 from canonical.launchpad import _
452 from canonical.launchpad.webapp.interfaces import ILaunchBag
453 from lp.registry.interfaces.person import IPerson
454@@ -33,8 +36,12 @@
455 class IPOFile(IRosettaStats):
456 """A translation file."""
457
458- id = Int(
459- title=_('The translation file id.'), required=True, readonly=True)
460+ export_as_webservice_entry(
461+ singular_name="translation_file",
462+ plural_name="translation_files")
463+
464+ id = exported(Int(
465+ title=_('The translation file id.'), required=True, readonly=True))
466
467 potemplate = Object(
468 title=_('The translation file template.'),
469@@ -121,8 +128,8 @@
470 required=False)
471
472 translation_messages = Attribute(_(
473- 'All `ITranslationMessage` objects related to this translation file.'
474- ))
475+ "All `ITranslationMessage` objects related to this "
476+ "translation file."))
477
478 plural_forms = Int(
479 title=_('Number of plural forms for the language of this PO file.'),
480@@ -273,7 +280,7 @@
481
482 def getTranslationRows():
483 """Return exportable rows of translation data.
484-
485+
486 :return: a list of `VPOExport` objects.
487 """
488
489
490=== modified file 'lib/lp/translations/interfaces/potemplate.py'
491--- lib/lp/translations/interfaces/potemplate.py 2009-11-27 12:50:16 +0000
492+++ lib/lp/translations/interfaces/potemplate.py 2010-06-03 16:14:30 +0000
493@@ -7,13 +7,15 @@
494 from zope.schema import (
495 Bool, Bytes, Choice, Datetime, Int, Object, Text, TextLine)
496 from lazr.enum import DBEnumeratedType, DBItem
497+from lazr.restful.fields import CollectionField, Reference
498+from lazr.restful.declarations import (
499+ exported, export_as_webservice_entry, export_read_operation,
500+ operation_returns_collection_of)
501
502+from canonical.launchpad.fields import ParticipatingPersonChoice
503 from canonical.launchpad.interfaces.launchpad import NotFoundError
504 from canonical.launchpad.interfaces.librarian import ILibraryFileAlias
505 from lp.registry.interfaces.distribution import IDistribution
506-from lp.registry.interfaces.distroseries import IDistroSeries
507-from lp.registry.interfaces.product import IProduct
508-from lp.registry.interfaces.productseries import IProductSeries
509 from lp.translations.interfaces.rosettastats import IRosettaStats
510 from lp.registry.interfaces.sourcepackagename import (
511 ISourcePackageName)
512@@ -74,95 +76,55 @@
513 search or complete listing is requested by the user. """)
514
515
516-class IHasTranslationTemplates(Interface):
517- """An entity that has translation templates attached.
518-
519- Examples include `ISourcePackage`, `IDistroSeries`, and `IProductSeries`.
520- """
521-
522- has_current_translation_templates = Bool(
523- title=_("Does this object have current translation templates?"),
524- readonly=True)
525-
526- def getCurrentTranslationTemplates(just_ids=False):
527- """Return an iterator over all active translation templates.
528-
529- A translation template is considered active when both
530- `IPOTemplate`.iscurrent and parent official_rosetta flags
531- are set to True.
532- """
533-
534- def getCurrentTranslationFiles(just_ids=False):
535- """Return an iterator over all active translation files.
536-
537- A translation file is active if it's attached to an
538- active translation template.
539- """
540-
541- def getObsoleteTranslationTemplates():
542- """Return an iterator over its not active translation templates.
543-
544- A translation template is considered not active when any of
545- `IPOTemplate`.iscurrent or `IDistribution`.official_rosetta flags
546- are set to False.
547- """
548-
549- def getTranslationTemplates():
550- """Return an iterator over all its translation templates.
551-
552- The returned templates are either obsolete or current.
553- """
554-
555- def getTranslationTemplateFormats():
556- """A list of native formats for all current translation templates.
557- """
558-
559-
560 class IPOTemplate(IRosettaStats):
561 """A translation template."""
562
563- id = Int(
564+ export_as_webservice_entry(
565+ singular_name='translation_template',
566+ plural_name='translation_templates')
567+
568+ id = exported(Int(
569 title=u"The translation template id.",
570- required=True, readonly=True)
571+ required=True, readonly=True))
572
573- name = TextLine(
574+ name = exported(TextLine(
575 title=_("Template name"),
576 description=_("The name of this PO template, for example "
577 "'evolution-2.2'. Each translation template has a "
578 "unique name in its package. It's important to get this "
579 "correct, because Launchpad will recommend alternative "
580 "translations based on the name."),
581- required=True)
582+ required=True))
583
584- translation_domain = TextLine(
585+ translation_domain = exported(TextLine(
586 title=_("Translation domain"),
587 description=_("The translation domain for a translation template. "
588 "Used with PO file format when generating MO files for inclusion "
589 "in language pack or MO tarball exports."),
590- required=True)
591+ required=True))
592
593- description = Text(
594+ description = exported(Text(
595 title=_("Description"),
596 description=_("Please provide a brief description of the content "
597 "of this translation template, for example, telling translators "
598 "if this template contains strings for end-users or other "
599 "developers."),
600- required=False)
601+ required=False))
602
603 header = Text(
604 title=_('Header'),
605 description=_("The standard template header in its native format."),
606 required=True)
607
608- iscurrent = Bool(
609+ iscurrent = exported(Bool(
610 title=_("Accept translations?"),
611 description=_(
612 "If unchecked, people can no longer change the template's "
613 "translations."),
614 required=True,
615- default=True)
616+ default=True), exported_as='active')
617
618- owner = Choice(
619+ owner = exported(ParticipatingPersonChoice(
620 title=_("Owner"),
621 required=True,
622 description=_(
623@@ -170,7 +132,7 @@
624 "and change it's status, and can also upload new versions "
625 "of the template when a new release is made or when the "
626 "translation strings have been changed during development."),
627- vocabulary="ValidOwner")
628+ vocabulary="ValidOwner"))
629
630 productseries = Choice(
631 title=_("Series"),
632@@ -209,29 +171,29 @@
633 required=False,
634 vocabulary="BinaryPackageName")
635
636- languagepack = Bool(
637+ languagepack = exported(Bool(
638 title=_("Include translations for this template in language packs?"),
639 description=_(
640 "Check this box if this template is part of a language pack so "
641 "its translations should be exported that way."),
642 required=True,
643- default=False)
644+ default=False), exported_as='exported_in_languagepacks')
645
646- path = TextLine(
647+ path = exported(TextLine(
648 title=_(
649 "Path of the template in the source tree, including filename."),
650- required=False)
651+ required=False))
652
653 source_file = Object(
654 title=_('Source file for this translation template'),
655 readonly=True, schema=ILibraryFileAlias)
656
657- source_file_format = Choice(
658+ source_file_format = exported(Choice(
659 title=_("File format for the source file"),
660 required=False,
661- vocabulary=TranslationFileFormat)
662+ vocabulary=TranslationFileFormat), exported_as='format')
663
664- priority = Int(
665+ priority = exported(Int(
666 title=_('Priority'),
667 required=True,
668 default=0,
669@@ -240,7 +202,7 @@
670 'there are multiple templates, and you can use this as a way '
671 'of indicating which are more important and should be '
672 'translated first. Pick any number - higher priority '
673- 'templates will generally be listed first.'))
674+ 'templates will generally be listed first.')))
675
676 datecreated = Datetime(
677 title=_('When this translation template was created.'), required=True,
678@@ -265,8 +227,12 @@
679 '''),
680 vocabulary='TranslationPermission')
681
682- pofiles = Attribute(
683- _('All `IPOFile` that exist for this template.'))
684+ pofiles = exported(
685+ CollectionField(
686+ title=_("All translation files that exist for this template."),
687+ # Really IPOFile, see _schema_circular_imports.py.
688+ value_type=Reference(schema=Interface)),
689+ exported_as='translation_files')
690
691 relatives_by_name = Attribute(
692 _('All `IPOTemplate` objects that have the same name asa this one.'))
693@@ -287,17 +253,24 @@
694
695 product = Object(
696 title=_('The `IProduct` to which this translation template belongs.'),
697- required=False, readonly=True, schema=IProduct)
698+ required=False, readonly=True,
699+ # Really IProduct, see _schema_circular_imports.py.
700+ schema=Interface)
701
702 distribution = Object(
703 title=_(
704- 'The `IDistribution` to which this translation template belongs.'
705- ),
706+ 'The `IDistribution` to which this translation template '
707+ 'belongs.'),
708 readonly=True, schema=IDistribution)
709
710- language_count = Int(
711+ messagecount = exported(Int(
712+ title=_('The number of translation messages for this template.'),
713+ required=True, readonly=True),
714+ exported_as='message_count')
715+
716+ language_count = exported(Int(
717 title=_('The number of languages for which we have translations.'),
718- required=True, readonly=True)
719+ required=True, readonly=True))
720
721 translationtarget = Attribute(
722 _('''
723@@ -305,9 +278,9 @@
724 This will either be an `ISourcePackage` or an `IProductSeries`.
725 '''))
726
727- date_last_updated = Datetime(
728+ date_last_updated = exported(Datetime(
729 title=_('Date for last update'),
730- required=True)
731+ required=True))
732
733 uses_english_msgids = Bool(
734 title=_("Uses English strings as msgids"), readonly=True,
735@@ -537,12 +510,14 @@
736 distroseries = Object(
737 title=_(
738 'The `IDistroSeries` associated with this subset.'),
739- schema=IDistroSeries)
740+ # Really IDistroSeries, see _schema_circular_imports.py.
741+ schema=Interface)
742
743 productseries = Object(
744 title=_(
745 'The `IProductSeries` associated with this subset.'),
746- schema=IProductSeries)
747+ # Really IProductSeries, see _schema_circular_imports.py.
748+ schema=Interface)
749
750 iscurrent = Bool(
751 title=_("Filter for iscurrent flag."),
752@@ -599,9 +574,9 @@
753 def getClosestPOTemplate(path):
754 """Return a `IPOTemplate` with a path closer to given path, or None.
755
756- If there is no `IPOTemplate` with a common path with the given argument,
757- or if there are more than one `IPOTemplate` with the same common path,
758- and both are the closer ones, returns None.
759+ If there is no `IPOTemplate` with a common path with the given,
760+ argument or if there are more than one `IPOTemplate` with the same
761+ common path, and both are the closer ones, returns None.
762 """
763
764 def findUniquePathlessMatch(filename):
765@@ -677,7 +652,8 @@
766 product = Object(
767 title=_(
768 'The `IProduct` associated with this subset.'),
769- schema=IProduct)
770+ # Really IProduct, see _schema_circular_imports.py.
771+ schema=Interface)
772
773 sourcepackagename = Object(
774 title=_(
775@@ -722,3 +698,52 @@
776 content = Bytes(
777 title=_("PO Template File to Import"),
778 required=True)
779+
780+
781+class IHasTranslationTemplates(Interface):
782+ """An entity that has translation templates attached.
783+
784+ Examples include `ISourcePackage`, `IDistroSeries`, and `IProductSeries`.
785+ """
786+
787+ has_current_translation_templates = Bool(
788+ title=_("Does this object have current translation templates?"),
789+ readonly=True)
790+
791+ def getCurrentTranslationTemplates(just_ids=False):
792+ """Return an iterator over all active translation templates.
793+
794+ A translation template is considered active when both
795+ `IPOTemplate`.iscurrent and parent official_rosetta flags
796+ are set to True.
797+ """
798+
799+ def getCurrentTranslationFiles(just_ids=False):
800+ """Return an iterator over all active translation files.
801+
802+ A translation file is active if it's attached to an
803+ active translation template.
804+ """
805+
806+ def getObsoleteTranslationTemplates():
807+ """Return an iterator over its not active translation templates.
808+
809+ A translation template is considered not active when any of
810+ `IPOTemplate`.iscurrent or `IDistribution`.official_rosetta flags
811+ are set to False.
812+ """
813+
814+ @export_read_operation()
815+ @operation_returns_collection_of(IPOTemplate)
816+ def getTranslationTemplates():
817+ """Return an iterator over all its translation templates.
818+
819+ The returned templates are either obsolete or current.
820+ """
821+
822+ def getTranslationTemplateFormats():
823+ """A list of native formats for all current translation templates.
824+ """
825+
826+# Monkey patch for circular import avoidance done in
827+# _schema_circular_imports.py
828
829=== modified file 'lib/lp/translations/interfaces/webservice.py'
830--- lib/lp/translations/interfaces/webservice.py 2009-07-17 02:25:09 +0000
831+++ lib/lp/translations/interfaces/webservice.py 2010-06-03 16:14:30 +0000
832@@ -1,9 +1,24 @@
833 # Copyright 2009 Canonical Ltd. This software is licensed under the
834 # GNU Affero General Public License version 3 (see the file LICENSE).
835
836+# pylint: disable-msg=W0611
837+
838 """All the interfaces that are exposed through the webservice."""
839
840+__all__ = [
841+ 'IHasTranslationImports',
842+ 'IPOFile',
843+ 'IPOTemplate',
844+ 'ITranslationImportQueue',
845+ 'ITranslationImportQueueEntry',
846+ ]
847+
848 from lp.translations.interfaces.translationimportqueue import (
849 IHasTranslationImports,
850 ITranslationImportQueue,
851 ITranslationImportQueueEntry)
852+
853+from lp.translations.interfaces.potemplate import (
854+ IPOTemplate)
855+from lp.translations.interfaces.pofile import (
856+ IPOFile)
857
858=== modified file 'lib/lp/translations/model/distroseries_translations_copy.py'
859--- lib/lp/translations/model/distroseries_translations_copy.py 2009-07-17 00:26:05 +0000
860+++ lib/lp/translations/model/distroseries_translations_copy.py 2010-06-03 16:14:30 +0000
861@@ -44,7 +44,7 @@
862 copier = MultiTableCopy(full_name, translation_tables, logger=logger)
863
864 # Incremental copy of updates is no longer supported
865- assert len(child.getTranslationTemplates()) == 0, (
866+ assert child.getTranslationTemplates().is_empty(), (
867 "The child series must not yet have any translation templates.")
868
869 logger.info(
870
871=== modified file 'lib/lp/translations/model/potemplate.py'
872--- lib/lp/translations/model/potemplate.py 2010-01-12 21:29:03 +0000
873+++ lib/lp/translations/model/potemplate.py 2010-06-03 16:14:30 +0000
874@@ -1297,8 +1297,8 @@
875 elif sourcepackagename is None:
876 # Multiple matches, and for a product not a package.
877 logging.warn(
878- "Found %d templates with path '%s' for productseries %s" % (
879- len(matches), path, productseries.title))
880+ "Found %d templates with path '%s' for productseries %s",
881+ len(matches), path, productseries.title)
882 return None
883 else:
884 # Multiple matches, for a distribution package. Prefer a
885@@ -1316,9 +1316,9 @@
886 else:
887 logging.warn(
888 "Found %d templates with path '%s' for package %s "
889- "(%d matched on from_sourcepackagename)." % (
890- len(matches), path, sourcepackagename.name,
891- len(preferred_matches)))
892+ "(%d matched on from_sourcepackagename).",
893+ len(matches), path, sourcepackagename.name,
894+ len(preferred_matches))
895 return None
896
897 @staticmethod
898@@ -1510,10 +1510,6 @@
899 if flag
900 ])
901
902- # Store sequences so we can detect later whether we changed the
903- # message.
904- sequence = row.sequence
905-
906 # Store the message.
907 messages.append(msgset)
908
909
910=== added file 'lib/lp/translations/stories/webservice/xx-potemplate.txt'
911--- lib/lp/translations/stories/webservice/xx-potemplate.txt 1970-01-01 00:00:00 +0000
912+++ lib/lp/translations/stories/webservice/xx-potemplate.txt 2010-06-03 16:14:30 +0000
913@@ -0,0 +1,121 @@
914+PO Template webservices
915+=======================
916+
917+
918+Getting the attributes of a POTemplate
919+--------------------------------------
920+
921+Anonymous users have read access to PO templates attributes.
922+
923+ >>> from lazr.restful.testing.webservice import pprint_entry
924+ >>> potemplate = anon_webservice.get(
925+ ... '/ubuntu/hoary/+source/pmount/+pots/pmount').jsonBody()
926+ >>> pprint_entry(potemplate)
927+ active: True
928+ date_last_updated: u'2005-05-06T20:09:23.775993+00:00'
929+ description: None
930+ exported_in_languagepacks: True
931+ format: u'PO format'
932+ id: 2
933+ language_count: 8
934+ message_count: 63
935+ name: u'pmount'
936+ owner_link: u'http://.../~rosetta-admins'
937+ path: u'po/template.pot'
938+ priority: 0
939+ resource_type_link: u'http://.../#translation_template'
940+ self_link: u'http://.../ubuntu/hoary/+source/pmount/+pots/pmount'
941+ translation_domain: u'pmount'
942+ translation_files_collection_link:
943+ u'http://.../pmount/+pots/pmount/translation_files'
944+
945+"translation_files" will list all POFiles associated with this template.
946+
947+ >>> translation_files = anon_webservice.get(
948+ ... potemplate['translation_files_collection_link']).jsonBody()
949+ >>> print translation_files['total_size']
950+ 9
951+ >>> print(translation_files['entries'][0]['resource_type_link'])
952+ http://.../#translation_file
953+
954+
955+Getting all potemplates for a distribution series
956+-------------------------------------------------
957+
958+All templates associated to a distribution series are available from the
959+'getTranslationTemplates' GET method.
960+
961+ >>> from zope.component import getUtility
962+ >>> from canonical.launchpad.interfaces.launchpad import (
963+ ... ILaunchpadCelebrities)
964+ >>> from lp.translations.interfaces.potemplate import IPOTemplateSet
965+ >>> login('admin@canonical.com')
966+ >>> hoary = getUtility(ILaunchpadCelebrities).ubuntu.getSeries('hoary')
967+ >>> templates = getUtility(IPOTemplateSet).getSubset(distroseries=hoary)
968+ >>> db_count = len(list(templates))
969+ >>> logout()
970+ >>> all_translation_templates = anon_webservice.named_get(
971+ ... '/ubuntu/hoary/', 'getTranslationTemplates').jsonBody()
972+ >>> api_count = all_translation_templates['total_size']
973+ >>> api_count == db_count
974+ True
975+ >>> print(all_translation_templates['entries'][0]['resource_type_link'])
976+ http://.../#translation_template
977+
978+
979+Getting all potemplates for a product series
980+--------------------------------------------
981+
982+All translation templates for a product series are available using the
983+'getTranslationTemplates' GET method.
984+
985+ >>> login('admin@canonical.com')
986+ >>> productseries = factory.makeProductSeries()
987+ >>> potemplate_1 = factory.makePOTemplate(productseries=productseries)
988+ >>> potemplate_2 = factory.makePOTemplate(productseries=productseries)
989+ >>> potemplate_count = 2
990+ >>> logout()
991+ >>> all_translation_templates = anon_webservice.named_get(
992+ ... '/%s/%s' % (
993+ ... productseries.product.name,
994+ ... productseries.name),
995+ ... 'getTranslationTemplates'
996+ ... ).jsonBody()
997+ >>> api_count = all_translation_templates['total_size']
998+ >>> api_count == potemplate_count
999+ True
1000+ >>> print(all_translation_templates['entries'][0]['resource_type_link'])
1001+ http://.../#translation_template
1002+
1003+
1004+Getting all translation templates for a source package
1005+------------------------------------------------------
1006+
1007+All translation templates for a source package are available using the
1008+'getTranslationTemplates' GET method.
1009+
1010+
1011+ >>> from zope.component import getUtility
1012+ >>> from canonical.launchpad.interfaces.launchpad import (
1013+ ... ILaunchpadCelebrities)
1014+ >>> from lp.registry.interfaces.sourcepackagename import (
1015+ ... ISourcePackageNameSet)
1016+ >>> from lp.translations.interfaces.potemplate import IPOTemplateSet
1017+ >>> login('admin@canonical.com')
1018+ >>> hoary = getUtility(ILaunchpadCelebrities).ubuntu.getSeries('hoary')
1019+ >>> evolution_package = getUtility(ISourcePackageNameSet)['evolution']
1020+ >>> templates = getUtility(
1021+ ... IPOTemplateSet).getSubset(
1022+ ... distroseries=hoary,
1023+ ... sourcepackagename=evolution_package)
1024+ >>> db_count = len(list(templates))
1025+ >>> logout()
1026+ >>> all_translation_templates = anon_webservice.named_get(
1027+ ... '/ubuntu/hoary/+source/evolution',
1028+ ... 'getTranslationTemplates').jsonBody()
1029+ >>> api_count = all_translation_templates['total_size']
1030+ >>> api_count == db_count
1031+ True
1032+ >>> print(all_translation_templates['entries'][0]['resource_type_link'])
1033+ http://.../#translation_template
1034+