Merge lp:~adiroiban/launchpad/bug-509252-take-2 into lp:launchpad

Proposed by Adi Roiban
Status: Merged
Approved by: Henning Eggers
Approved revision: not available
Merged at revision: not available
Proposed branch: lp:~adiroiban/launchpad/bug-509252-take-2
Merge into: lp:launchpad
Diff against target: 760 lines (+184/-213)
11 files modified
lib/canonical/launchpad/security.py (+0/-15)
lib/lp/registry/browser/distroseries.py (+9/-17)
lib/lp/registry/browser/sourcepackage.py (+8/-4)
lib/lp/registry/doc/distroseries.txt (+18/-76)
lib/lp/registry/interfaces/distroseries.py (+4/-13)
lib/lp/registry/model/distroseries.py (+12/-37)
lib/lp/translations/browser/browser_helpers.py (+4/-6)
lib/lp/translations/browser/distroseries.py (+52/-29)
lib/lp/translations/browser/tests/distroseries-views.txt (+69/-11)
lib/lp/translations/stories/distroseries/xx-distroseries-translations.txt (+3/-5)
lib/lp/translations/templates/distroseries-langchart.pt (+5/-0)
To merge this branch: bzr merge lp:~adiroiban/launchpad/bug-509252-take-2
Reviewer Review Type Date Requested Status
Henning Eggers (community) code Approve
Michael Nelson (community) code Needs Information
Review via email: mp+19484@code.launchpad.net

Commit message

Refactor checkTranslationsViewable(). Merge the model and view logic and move it to a view for distroseries. Landed by henninge.

To post a comment you must log in.
Revision history for this message
Adi Roiban (adiroiban) wrote :
Download full text (4.0 KiB)

= Bug 509252 =
After landing the fix for bug 340662 (https://code.edge.launchpad.net/~adiroiban/launchpad/bug-340662-take-2/+merge/17598) the POTemplateSubsetNavigation will render the AdminPOTemplateSubset useless.

We should clean the security.py.

AdminPOTemplateSubset was added to fix bug 497438

== Proposed fix ==

Remove unused classed from security.py and refactor

== Pre-implementation notes ==

Talking with Henning we decide not to include security checking code in the model. This is why the logic of showing translations of a series was spitted between the model and the view and the security checks were done in multiple places.

This restriction lead to a strange implementation where the check was done both in the view and the model and it was a source of confusion.

I merged this logic in a browser helper function and now the security check is only done once.

== Implementation details ==

These changes renders the AdminDistroSeriesLanguage class from security.py useless, so I have also removed it.

== Tests ==
lp-test -t distroseries-translations -t distroseries-views

== Demo and Q/A ==
Make sure you are a member of Ubuntu Translation Coordinators team (ubuntu-l10n-coordinator).
https://launchpad.dev/~ubuntu-l10n-coordinator

Go to the translation page for a distribution and hide translations for this series
ie: https://translations.launchpad.dev/ubuntu/hoary/+admin

Next go to the distribution series +template page
https://translations.launchpad.dev/ubuntu/hoary/+templates

You should see the Administer page for Evolution templates, including disabled-template .

As a member of Ubuntu Translation Coordinators team you should be able to administer them, just like for a normal series.

Login as a normal user you should see a page informing that the translations are currently closed

= Launchpad lint =

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

Linting changed files:
  lib/canonical/launchpad/security.py
  lib/lp/registry/browser/distroseries.py
  lib/lp/registry/browser/sourcepackage.py
  lib/lp/registry/interfaces/distroseries.py
  lib/lp/registry/model/distroseries.py
  lib/lp/translations/browser/browser_helpers.py
  lib/lp/translations/browser/distroseries.py
  lib/lp/translations/stories/distroseries/xx-distroseries-translations.txt

== Pylint notices ==

lib/lp/registry/browser/sourcepackage.py
    29: [F0401] Unable to import 'lazr.restful.interface' (No module named restful)

lib/lp/registry/interfaces/distroseries.py
    23: [F0401] Unable to import 'lazr.enum' (No module named enum)
    50: [F0401] Unable to import 'lazr.restful.fields' (No module named restful)
    51: [F0401] Unable to import 'lazr.restful.declarations' (No module named restful)
    117: [E1002, DistroSeriesVersionField._validate] Use super on an old style class
    447: [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"),
    des...

Read more...

Revision history for this message
Michael Nelson (michael.nelson) wrote :

Hi Adi,

Thanks for all the cleanups that you've done in this branch.

After reading the recent email discussion at:

https://lists.launchpad.net/launchpad-dev/msg02442.html

and particularly BjornT's message:

https://lists.launchpad.net/launchpad-dev/msg02504.html

I don't think putting the security check into a browser helper is the way to go.

As outlined in the above email:
"The current solution is to have this code in model code, and have the
security adapter ask the model. No code duplication really."

this ensures that any security can be applied not only in the view, but also via other modes of access (API), even if they are not yet enabled.

As it turns out in your code, it seems most of IDistroSeries.checkTranslationsViewable() is not actually security code, but simply appropriate error generation. The first few lines however are security related.

So I wonder if we can do something like this:

http://pastebin.ubuntu.com/379120/

That is, simply add a launchpad.View check for IDistroSeriesLanguage, which itself ensures that launchpad.View is granted if the user is an admin. That way, as in the example in the diff, the current double security checks:

         if not check_permission(
            'launchpad.TranslationsAdmin', distroserieslang):
            self.context.checkTranslationsViewable()

could be replaced simply by:

         if not check_permission(
            'launchpad.View', distroserieslang):
            translation_unavailable_error()

that way you'd have all the security in the right place, separate from the error generation (ie. translation_unavailable_error() would be your proposed view helper, check_distroseries_translations_viewable, just without the security checks?

I'm not 100%, but as far as I can see, the above should do what you're after while at the same time keeping all the security code in the one place. But I'll check with Henninge to be sure (hence marking this as needs info).

IRC log of conversation starts at:

http://irclogs.ubuntu.com/2010/02/18/%23launchpad-reviews.html#t11:18

review: Needs Information (code)
Revision history for this message
Michael Nelson (michael.nelson) wrote :

OK, I spoke to Adi, and he said that the security for IDistroSeriesLanguage needs to go anyway, which means that the above paste is useless, as we can't update the permission checks for DistroSeries or SourcePackage (or is there a way to have checks for these objects only when they are traversed in the translations context? - an extra 'facet' option to check_permission?)

Without this, I can't see a way to keep these security checks encapsulated in security.py or on the model. I'm going to have to defer to Danilo or Henninge.

Revision history for this message
Henning Eggers (henninge) wrote :

Hi Adi,
thanks for cleaning this up. Your code is almost good, I'd just ask two changes of you.

I agree with Michael's point that check_distroseries_translations_viewable should not go into browser_helper. DistroSeries.checkTranslationsViewable also is a bad place to raise exceptions because these should go into the browser code. So you were right in removing this.

The code in check_distroseries_translations_viewable is not a security check but an error handling routine that uses existing security checks. So I think it is perfectly valid in browser code.

Something that was already wrong before this branch but that should be cleaned up is this: DistroSeriesView.checkTranslationsViewable is only used in a tal:omit-tag and is therefore expected to return a bool. Even with your branch it raises exceptions. I don't know why that worked before but I guess this is some historic garbage floating around.

So my first request is that you find a new implementation for DistroSeriesView.checkTranslationsViewable that is a bool view property and does not raise exceptions. But maybe you can remove it all together if that one call site proves to be garbage.

My other request is that you move check_distroseries_translations_viewable into lib/lp/translations/browser/distroseries.py. I would have liked to see it in a Mixin that you can add to DistroSeriesNavigation and SourcePackageNavigation but that may not make too much sense. Think about it, though.

Finally I noticed DistroSeries.getDistroSeriesLanguageOrDummy which could be used to make the code in DistroSeriesNavigation.traverse_lang much shorter ;-)

Thanks. ;-)

review: Needs Fixing (code)
Revision history for this message
Adi Roiban (adiroiban) wrote :

DistroSeriesView.checkTranslationsViewable() is used in a „metal” tag... so it really doesn't matter its return value as its sole purpose is to raise those exceptions.

<metal:check-available tal:omit-tag="view/checkTranslationsViewable" />

If we want to remove this behavior, we would need to raise those exception in Distribution URL traversal for series... and then things will get a bit complicated as those exception should be raised only for „translations” facet.

-------

There are a couple of view tests in translations/browser/tests/distroseries-views.txt and registry/doc/distroseries.txt using these exceptions. I guess we need to put them in a single file (ie: translations/browser/tests/distroseries-views.txt)

-----

Thanks for the getDistroSeriesLanguageOrDummy tip :)

Revision history for this message
Henning Eggers (henninge) wrote :

Hi Adi,
sorry for not replying but I had expected that you push an updated branch with whatever changes you see possible.

I did not know that metal tags are being used to raise exceptions and I am pretty sure that should not be that way. If you think it's too much work to improve that in this branch, I can understand that. Please file a bug so that it gets done at some later time.

So, a new push to the branch would be the next thing to get the review forward.

Cheers,
Henning

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

Hi,
Don't worry about the communication problem ... be happy :)

I have opened a new bug for TAL expression used for raising an exception.
I have merged the tests for translation permissions.

Here is the diff. Thanks!
=== modified file 'lib/lp/registry/browser/distroseries.py'
--- lib/lp/registry/browser/distroseries.py 2010-02-17 13:06:35 +0000
+++ lib/lp/registry/browser/distroseries.py 2010-02-24 14:57:59 +0000
@@ -37,10 +37,8 @@
 from lp.services.worlddata.interfaces.country import ICountry
 from lp.registry.interfaces.series import SeriesStatus
 from lp.registry.interfaces.distroseries import IDistroSeries
-from lp.translations.browser.browser_helpers import (
+from lp.translations.browser.distroseries import (
     check_distroseries_translations_viewable)
-from lp.translations.interfaces.distroserieslanguage import (
- IDistroSeriesLanguageSet)
 from lp.services.worlddata.interfaces.language import ILanguageSet
 from lp.registry.browser.structuralsubscription import (
     StructuralSubscriptionMenuMixin,
@@ -81,19 +79,12 @@
         except IndexError:
             # Unknown language code.
             raise NotFoundError
- distroserieslang = self.context.getDistroSeriesLanguage(lang)

- if distroserieslang is None:
- # There is no IDistroSeriesLanguage yet for this IDistroSeries,
- # but we still need to list it as an available language, so we
- # generate a dummy one so users have a chance to get to it in the
- # navigation and start adding translations for it.
- distroserieslangset = getUtility(IDistroSeriesLanguageSet)
- distroserieslang = distroserieslangset.getDummy(
- self.context, lang)
+ distroserieslang = self.context.getDistroSeriesLanguageOrDummy(lang)

         # Check if user is able to view the translations for
- # this distribution series
+ # this distribution series language.
+ # If not, raise TranslationUnavailable.
         check_distroseries_translations_viewable(self.context)

         return distroserieslang

=== modified file 'lib/lp/registry/browser/sourcepackage.py'
--- lib/lp/registry/browser/sourcepackage.py 2010-02-17 13:06:35 +0000
+++ lib/lp/registry/browser/sourcepackage.py 2010-02-24 14:55:26 +0000
@@ -40,7 +40,7 @@
 from lp.registry.interfaces.packaging import IPackaging
 from lp.registry.interfaces.pocket import PackagePublishingPocket
 from lp.registry.interfaces.sourcepackage import ISourcePackage
-from lp.translations.browser.browser_helpers import (
+from lp.translations.browser.distroseries import (
     check_distroseries_translations_viewable)
 from lp.translations.interfaces.potemplate import IPOTemplateSet
 from canonical.launchpad import _
@@ -68,7 +68,8 @@

         # If we are able to view the translations for distribution series
         # we should also be allowed to see them for a distribution
- # source package
+ # source package.
+ # If not, raise TranslationUnavailable.
         check_distroseries_translations_viewable(self.context.distroseries)

         return sourcepackage_pots

=== modified file 'lib/lp/registry/doc/distroseries.t...

Revision history for this message
Henning Eggers (henninge) wrote :

Thanks, this looks much better. Unfortunately the diff shows conflicts, so I guess you need to merge devel and push again for the diff to be correct.

Also, is there no unit test that could go through all the different SeriesStatus values? Doing this in a doc test looks like it's in the wrong place. But please only do this if it is fairly easy. I think this branch is already old enough ... ;-)

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

În data de Mi, 24-02-2010 la 17:42 +0000, Henning Eggers a scris:
> Thanks, this looks much better. Unfortunately the diff shows
> conflicts, so I guess you need to merge devel and push again for the
> diff to be correct.
Conflict solved.

> Also, is there no unit test that could go through all the different
> SeriesStatus values? Doing this in a doc test looks like it's in the
> wrong place. But please only do this if it is fairly easy. I think
> this branch is already old enough ... ;-)
I was thinking that view unit tests should be put in:
lib/lp/<application>/browser/tests

translations/browser/tests/distroseries-views.txt was already there and
I have just moved those tests from registry/doc/distroseries.txt to this
file.

Are you saying that we should not write unit tests using doctest format?

Cheers

--
Adi Roiban

Revision history for this message
Henning Eggers (henninge) wrote :

> Conflict solved.

Cool, looks good. ;)

> Are you saying that we should not write unit tests using doctest format?

Yes, at least that's what I do. They execute slower, don't they? But as you pointed out, the test was already there, so you just extended it. That's what I do, too ... ;) So you can keep it as it is if you want to.

Thanks for the extra comments in the code, btw. And all the clean-up work!
You have a double "allowed allowed" in there somewhere, btw.

Cheers,
Henning

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

În data de Mi, 24-02-2010 la 18:27 +0000, Henning Eggers a scris:
> Review: Approve code
> > Conflict solved.
>
> Cool, looks good. ;)
>
>
> > Are you saying that we should not write unit tests using doctest format?
>
> Yes, at least that's what I do. They execute slower, don't they? But
> as you pointed out, the test was already there, so you just extended
> it. That's what I do, too ... ;) So you can keep it as it is if you
> want to.
OK. This is not the only unit test written in doctest format.

$ ls lib/lp/translations/browser/tests/*.txt
lib/lp/translations/browser/tests/distroseries-views.txt
lib/lp/translations/browser/tests/language-views.txt
lib/lp/translations/browser/tests/poexport-request-views.txt
lib/lp/translations/browser/tests/pofile-base-views.txt
lib/lp/translations/browser/tests/pofile-views.txt
lib/lp/translations/browser/tests/potemplate-views.txt
lib/lp/translations/browser/tests/productseries-views.txt
lib/lp/translations/browser/tests/translationimportqueue-views.txt
lib/lp/translations/browser/tests/translationmessage-views.txt
lib/lp/translations/browser/tests/translator-views.txt

Maybe we should open a bug to convert all those test from doctest to
"pure" python code.

> Thanks for the extra comments in the code, btw. And all the clean-up
> work!
> You have a double "allowed allowed" in there somewhere, btw.

Typo fixed and pushed.

When you have time, can you please send this branch to ec2 test?

Kindest regards,

--
Adi Roiban

Revision history for this message
Henning Eggers (henninge) wrote :

I am having trouble landing this. First, there is the current problem with ec2 test not sending emails, so the failure was kind of obscure. Secondly, after merging the current devel, it failed because "python-zope.interface" needed to be installed. You will have to do that, too!

Now, this error remains when running "make build":

    ZopeXMLConfigurationError: File "/home/henning/canonical/lp-branches/adiroiban-bug-509252-take-2/lib/lp/registry/configure.zcml", line 172.4-176.34
    ImportError: cannot import name check_distroseries_translations_viewable

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

Hm...

I did not get the „python-zope.interface” error but got the circular import error.

I have fixed the problem and pushed the branch.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/canonical/launchpad/security.py'
--- lib/canonical/launchpad/security.py 2010-02-24 23:02:56 +0000
+++ lib/canonical/launchpad/security.py 2010-03-01 23:03:20 +0000
@@ -49,8 +49,6 @@
49from lp.registry.interfaces.distributionsourcepackage import (49from lp.registry.interfaces.distributionsourcepackage import (
50 IDistributionSourcePackage)50 IDistributionSourcePackage)
51from lp.registry.interfaces.distroseries import IDistroSeries51from lp.registry.interfaces.distroseries import IDistroSeries
52from lp.translations.interfaces.distroserieslanguage import (
53 IDistroSeriesLanguage)
54from lp.registry.interfaces.entitlement import IEntitlement52from lp.registry.interfaces.entitlement import IEntitlement
55from lp.hardwaredb.interfaces.hwdb import (53from lp.hardwaredb.interfaces.hwdb import (
56 IHWDBApplication, IHWDevice, IHWDeviceClass, IHWDriver, IHWDriverName,54 IHWDBApplication, IHWDevice, IHWDeviceClass, IHWDriver, IHWDriverName,
@@ -1664,19 +1662,6 @@
1664 self.obj.distribution).checkAuthenticated(user))1662 self.obj.distribution).checkAuthenticated(user))
16651663
16661664
1667class AdminDistroSeriesLanguage(AuthorizationBase):
1668 permission = 'launchpad.TranslationsAdmin'
1669 usedfor = IDistroSeriesLanguage
1670
1671 def checkAuthenticated(self, user):
1672 """Is the user able to manage `IDistroSeriesLanguage` translations.
1673
1674 Distribution managers can also manage IDistroSeriesLanguage
1675 """
1676 return (AdminDistroSeriesTranslations(
1677 self.obj.distroseries).checkAuthenticated(user))
1678
1679
1680class BranchSubscriptionEdit(AuthorizationBase):1665class BranchSubscriptionEdit(AuthorizationBase):
1681 permission = 'launchpad.Edit'1666 permission = 'launchpad.Edit'
1682 usedfor = IBranchSubscription1667 usedfor = IBranchSubscription
16831668
=== modified file 'lib/lp/registry/browser/distroseries.py'
--- lib/lp/registry/browser/distroseries.py 2010-02-26 19:15:17 +0000
+++ lib/lp/registry/browser/distroseries.py 2010-03-01 23:03:20 +0000
@@ -37,8 +37,8 @@
37from lp.services.worlddata.interfaces.country import ICountry37from lp.services.worlddata.interfaces.country import ICountry
38from lp.registry.interfaces.series import SeriesStatus38from lp.registry.interfaces.series import SeriesStatus
39from lp.registry.interfaces.distroseries import IDistroSeries39from lp.registry.interfaces.distroseries import IDistroSeries
40from lp.translations.interfaces.distroserieslanguage import (40from lp.translations.browser.distroseries import (
41 IDistroSeriesLanguageSet)41 check_distroseries_translations_viewable)
42from lp.services.worlddata.interfaces.language import ILanguageSet42from lp.services.worlddata.interfaces.language import ILanguageSet
43from lp.registry.browser.structuralsubscription import (43from lp.registry.browser.structuralsubscription import (
44 StructuralSubscriptionMenuMixin,44 StructuralSubscriptionMenuMixin,
@@ -48,7 +48,6 @@
48from canonical.launchpad.webapp import (48from canonical.launchpad.webapp import (
49 StandardLaunchpadFacets, GetitemNavigation, action, custom_widget)49 StandardLaunchpadFacets, GetitemNavigation, action, custom_widget)
50from canonical.launchpad.webapp.batching import BatchNavigator50from canonical.launchpad.webapp.batching import BatchNavigator
51from canonical.launchpad.webapp.authorization import check_permission
52from canonical.launchpad.webapp.breadcrumb import Breadcrumb51from canonical.launchpad.webapp.breadcrumb import Breadcrumb
53from canonical.launchpad.webapp.launchpadform import (52from canonical.launchpad.webapp.launchpadform import (
54 LaunchpadEditFormView, LaunchpadFormView)53 LaunchpadEditFormView, LaunchpadFormView)
@@ -80,20 +79,13 @@
80 except IndexError:79 except IndexError:
81 # Unknown language code.80 # Unknown language code.
82 raise NotFoundError81 raise NotFoundError
83 distroserieslang = self.context.getDistroSeriesLanguage(lang)82
8483 distroserieslang = self.context.getDistroSeriesLanguageOrDummy(lang)
85 if distroserieslang is None:84
86 # There is no IDistroSeriesLanguage yet for this IDistroSeries,85 # Check if user is able to view the translations for
87 # but we still need to list it as an available language, so we86 # this distribution series language.
88 # generate a dummy one so users have a chance to get to it in the87 # If not, raise TranslationUnavailable.
89 # navigation and start adding translations for it.88 check_distroseries_translations_viewable(self.context)
90 distroserieslangset = getUtility(IDistroSeriesLanguageSet)
91 distroserieslang = distroserieslangset.getDummy(
92 self.context, lang)
93
94 if not check_permission(
95 'launchpad.TranslationsAdmin', distroserieslang):
96 self.context.checkTranslationsViewable()
9789
98 return distroserieslang90 return distroserieslang
9991
10092
=== modified file 'lib/lp/registry/browser/sourcepackage.py'
--- lib/lp/registry/browser/sourcepackage.py 2010-02-23 19:43:58 +0000
+++ lib/lp/registry/browser/sourcepackage.py 2010-03-01 23:03:20 +0000
@@ -52,7 +52,6 @@
52 action, ApplicationMenu, custom_widget, GetitemNavigation,52 action, ApplicationMenu, custom_widget, GetitemNavigation,
53 LaunchpadFormView, Link, redirection, StandardLaunchpadFacets, stepto)53 LaunchpadFormView, Link, redirection, StandardLaunchpadFacets, stepto)
54from canonical.launchpad.webapp import canonical_url54from canonical.launchpad.webapp import canonical_url
55from canonical.launchpad.webapp.authorization import check_permission
56from canonical.launchpad.webapp.breadcrumb import Breadcrumb55from canonical.launchpad.webapp.breadcrumb import Breadcrumb
57from canonical.launchpad.webapp.menu import structured56from canonical.launchpad.webapp.menu import structured
5857
@@ -70,9 +69,13 @@
70 distroseries=self.context.distroseries,69 distroseries=self.context.distroseries,
71 sourcepackagename=self.context.sourcepackagename)70 sourcepackagename=self.context.sourcepackagename)
7271
73 if not check_permission(72 # If we are able to view the translations for distribution series
74 'launchpad.TranslationsAdmin', sourcepackage_pots):73 # we should also be allowed to see them for a distribution
75 self.context.distroseries.checkTranslationsViewable()74 # source package.
75 # If not, raise TranslationUnavailable.
76 from lp.translations.browser.distroseries import (
77 check_distroseries_translations_viewable)
78 check_distroseries_translations_viewable(self.context.distroseries)
7679
77 return sourcepackage_pots80 return sourcepackage_pots
7881
@@ -92,6 +95,7 @@
9295
93class SourcePackageBreadcrumb(Breadcrumb):96class SourcePackageBreadcrumb(Breadcrumb):
94 """Builds a breadcrumb for an `ISourcePackage`."""97 """Builds a breadcrumb for an `ISourcePackage`."""
98
95 @property99 @property
96 def text(self):100 def text(self):
97 return smartquote('"%s" source package') % (self.context.name)101 return smartquote('"%s" source package') % (self.context.name)
98102
=== modified file 'lib/lp/registry/doc/distroseries.txt'
--- lib/lp/registry/doc/distroseries.txt 2010-02-25 16:27:34 +0000
+++ lib/lp/registry/doc/distroseries.txt 2010-03-01 23:03:20 +0000
@@ -1,4 +1,5 @@
1= DistroSeries =1DistroSeries
2============
23
3From the DerivationOverview spec4From the DerivationOverview spec
4<https://launchpad.canonical.com/DerivationOverview>:5<https://launchpad.canonical.com/DerivationOverview>:
@@ -200,7 +201,8 @@
200 True201 True
201202
202203
203== Package searching ==204Package searching
205-----------------
204206
205You can search through binary packages publishing in a distribution207You can search through binary packages publishing in a distribution
206release by using the searchPackages method, which uses magical fti:208release by using the searchPackages method, which uses magical fti:
@@ -223,7 +225,8 @@
223 DistroSeriesBinaryPackage: at225 DistroSeriesBinaryPackage: at
224226
225227
226== DistroSeriess have components and sections ==228DistroSeriess have components and sections
229------------------------------------------
227230
228A distroseries has some number of components and/or sections which231A distroseries has some number of components and/or sections which
229are valid for that distroseries. These selections are used by (among232are valid for that distroseries. These selections are used by (among
@@ -286,7 +289,8 @@
286 partner289 partner
287290
288291
289== DistroSeries can be initialised from their parents ==292DistroSeries can be initialised from their parents
293--------------------------------------------------
290294
291When a distroseries is derived from another distroseries (be it a295When a distroseries is derived from another distroseries (be it a
292derivative distribution, or simply the next release in a sequence from296derivative distribution, or simply the next release in a sequence from
@@ -396,74 +400,8 @@
396 netapplet400 netapplet
397401
398402
399Hiding translations403Translatable Packages and Packaging
400-------------------404-----------------------------------
401
402The hide_all_translations flag, if set, hides a distroseries'
403translations in the user interface. The check for visibility happens in
404checkTranslationsViewable.
405
406 >>> untranslatable_series = factory.makeDistroRelease()
407 >>> untranslatable_series.hide_all_translations = False
408
409When translations are visible, checkTranslationsViewable() completes
410normally.
411
412 >>> from canonical.launchpad.webapp.interfaces import (
413 ... TranslationUnavailable)
414 >>> def get_visibility_notice(series):
415 ... """Print the notice about series' translations being hidden."""
416 ... try:
417 ... series.checkTranslationsViewable()
418 ... except TranslationUnavailable, e:
419 ... return str(e)
420 ... return None
421
422 >>> print get_visibility_notice(untranslatable_series)
423 None
424
425But when translations are hidden, it raises TranslationUnavailable.
426
427 >>> untranslatable_series.hide_all_translations = True
428 >>> print get_visibility_notice(untranslatable_series)
429 Translations for this release series...
430
431Exactly what message is displayed depends on the series' status.
432
433 >>> untranslatable_series.status = SeriesStatus.EXPERIMENTAL
434 >>> print get_visibility_notice(untranslatable_series)
435 Translations for this release series are not available yet.
436
437 >>> untranslatable_series.status = SeriesStatus.DEVELOPMENT
438 >>> print get_visibility_notice(untranslatable_series)
439 Translations for this release series are not available yet.
440
441 >>> untranslatable_series.status = SeriesStatus.FROZEN
442 >>> print get_visibility_notice(untranslatable_series)
443 Translations for this release series are not currently available.
444 Please come back soon.
445
446 >>> untranslatable_series.status = SeriesStatus.CURRENT
447 >>> print get_visibility_notice(untranslatable_series)
448 Translations for this release series are not currently available.
449 Please come back soon.
450
451 >>> untranslatable_series.status = SeriesStatus.SUPPORTED
452 >>> print get_visibility_notice(untranslatable_series)
453 Translations for this release series are not currently available.
454 Please come back soon.
455
456 >>> untranslatable_series.status = SeriesStatus.OBSOLETE
457 >>> print get_visibility_notice(untranslatable_series)
458 This release series is obsolete. Its translations are no longer
459 available.
460
461 >>> untranslatable_series.status = SeriesStatus.FUTURE
462 >>> print get_visibility_notice(untranslatable_series)
463 Translations for this release series are not available yet.
464
465
466== Translatable Packages and Packaging ==
467405
468You can easily find out what packages are translatable in a406You can easily find out what packages are translatable in a
469distribution release:407distribution release:
@@ -668,7 +606,8 @@
668 True606 True
669607
670608
671== SourcePackagePublishingHistory ==609SourcePackagePublishingHistory
610------------------------------
672611
673IDistroSeries.getSourcePackagePublishing returns all the ISPPH612IDistroSeries.getSourcePackagePublishing returns all the ISPPH
674records for a given status in a given pocket. It makes easy to613records for a given status in a given pocket. It makes easy to
@@ -1042,7 +981,8 @@
1042 thinclient-local-devices981 thinclient-local-devices
1043982
1044983
1045= Drivers =984Drivers
985=======
1046986
1047Distributions have drivers, who are people that have permission to approve987Distributions have drivers, who are people that have permission to approve
1048bugs and features for specific releases. The rules are that:988bugs and features for specific releases. The rules are that:
@@ -1124,7 +1064,8 @@
1124 mark1064 mark
11251065
11261066
1127== Lastest Uploads ==1067Lastest Uploads
1068---------------
11281069
1129IDistroSeries provides the 'getLatestUpload' method which returns a1070IDistroSeries provides the 'getLatestUpload' method which returns a
1130list of the last 5 (five) IDistroSeriesSourcePackageRelease (IDRSPR)1071list of the last 5 (five) IDistroSeriesSourcePackageRelease (IDRSPR)
@@ -1150,7 +1091,8 @@
1150 01091 0
11511092
11521093
1153== Getting build records for a distro series ==1094Getting build records for a distro series
1095-----------------------------------------
11541096
1155IDistroSeries inherits the IHasBuildRecords interfaces and therefore provides1097IDistroSeries inherits the IHasBuildRecords interfaces and therefore provides
1156a getBuildRecords() method.1098a getBuildRecords() method.
11571099
=== modified file 'lib/lp/registry/interfaces/distroseries.py'
--- lib/lp/registry/interfaces/distroseries.py 2010-02-27 10:19:18 +0000
+++ lib/lp/registry/interfaces/distroseries.py 2010-03-01 23:03:20 +0000
@@ -54,6 +54,7 @@
54from lp.translations.interfaces.languagepack import ILanguagePack54from lp.translations.interfaces.languagepack import ILanguagePack
5555
5656
57
57class DistroSeriesNameField(ContentNameField):58class DistroSeriesNameField(ContentNameField):
58 """A class to ensure `IDistroSeries` has unique names."""59 """A class to ensure `IDistroSeries` has unique names."""
59 errormessage = _("%s is already in use by another series.")60 errormessage = _("%s is already in use by another series.")
@@ -132,6 +133,7 @@
132133
133class IDistroSeriesEditRestricted(Interface):134class IDistroSeriesEditRestricted(Interface):
134 """IDistroSeries properties which require launchpad.Edit."""135 """IDistroSeries properties which require launchpad.Edit."""
136
135 @rename_parameters_as(dateexpected='date_targeted')137 @rename_parameters_as(dateexpected='date_targeted')
136 @export_factory_operation(138 @export_factory_operation(
137 IMilestone, ['name', 'dateexpected', 'summary', 'code_name'])139 IMilestone, ['name', 'dateexpected', 'summary', 'code_name'])
@@ -198,7 +200,7 @@
198 Interface, # Really IDistribution, see circular import fix below.200 Interface, # Really IDistribution, see circular import fix below.
199 title=_("Distribution"), required=True,201 title=_("Distribution"), required=True,
200 description=_("The distribution for which this is a series.")))202 description=_("The distribution for which this is a series.")))
201 named_version = Attribute('The combined display name and version.')203 named_version = Attribute('The combined display name and version.')
202 parent = Attribute('The structural parent of this series - the distro')204 parent = Attribute('The structural parent of this series - the distro')
203 components = Attribute("The series components.")205 components = Attribute("The series components.")
204 upload_components = Attribute("The series components that can be "206 upload_components = Attribute("The series components that can be "
@@ -240,7 +242,7 @@
240 title=_("Defer translation imports"),242 title=_("Defer translation imports"),
241 description=_("Suspends any translation imports for this series"),243 description=_("Suspends any translation imports for this series"),
242 default=True,244 default=True,
243 required=True245 required=True,
244 )246 )
245 binarycount = Attribute("Binary Packages Counter")247 binarycount = Attribute("Binary Packages Counter")
246248
@@ -497,17 +499,6 @@
497 :return: A result set containing `IPackageUpload`499 :return: A result set containing `IPackageUpload`
498 """500 """
499501
500 def checkTranslationsViewable():
501 """Raise `TranslationUnavailable` if translations are hidden.
502
503 Checks the `hide_all_translations` flag. If it is set, these
504 translations are not to be shown to the public. In that case an
505 appropriate message is composed based on the series' `status`,
506 and a `TranslationUnavailable` exception is raised.
507
508 Simply returns if translations are not hidden.
509 """
510
511 def getUnlinkedTranslatableSourcePackages():502 def getUnlinkedTranslatableSourcePackages():
512 """Return a list of source packages that can be translated in503 """Return a list of source packages that can be translated in
513 this distribution series but which lack Packaging links.504 this distribution series but which lack Packaging links.
514505
=== modified file 'lib/lp/registry/model/distroseries.py'
--- lib/lp/registry/model/distroseries.py 2010-02-25 16:27:34 +0000
+++ lib/lp/registry/model/distroseries.py 2010-03-01 23:03:20 +0000
@@ -118,8 +118,7 @@
118from canonical.launchpad.mail import signed_message_from_string118from canonical.launchpad.mail import signed_message_from_string
119from lp.registry.interfaces.person import validate_public_person119from lp.registry.interfaces.person import validate_public_person
120from canonical.launchpad.webapp.interfaces import (120from canonical.launchpad.webapp.interfaces import (
121 IStoreSelector, MAIN_STORE, NotFoundError, SLAVE_FLAVOR,121 IStoreSelector, MAIN_STORE, NotFoundError, SLAVE_FLAVOR)
122 TranslationUnavailable)
123from lp.soyuz.interfaces.sourcepackageformat import (122from lp.soyuz.interfaces.sourcepackageformat import (
124 ISourcePackageFormatSelectionSet)123 ISourcePackageFormatSelectionSet)
125124
@@ -134,7 +133,7 @@
134 SeriesStatus.DEVELOPMENT,133 SeriesStatus.DEVELOPMENT,
135 SeriesStatus.FROZEN,134 SeriesStatus.FROZEN,
136 SeriesStatus.CURRENT,135 SeriesStatus.CURRENT,
137 SeriesStatus.SUPPORTED136 SeriesStatus.SUPPORTED,
138 ]137 ]
139138
140139
@@ -162,7 +161,7 @@
162 dbName='releasestatus', notNull=True, schema=SeriesStatus)161 dbName='releasestatus', notNull=True, schema=SeriesStatus)
163 date_created = UtcDateTimeCol(notNull=False, default=UTC_NOW)162 date_created = UtcDateTimeCol(notNull=False, default=UTC_NOW)
164 datereleased = UtcDateTimeCol(notNull=False, default=None)163 datereleased = UtcDateTimeCol(notNull=False, default=None)
165 parent_series = ForeignKey(164 parent_series = ForeignKey(
166 dbName='parent_series', foreignKey='DistroSeries', notNull=False)165 dbName='parent_series', foreignKey='DistroSeries', notNull=False)
167 owner = ForeignKey(166 owner = ForeignKey(
168 dbName='owner', foreignKey='Person',167 dbName='owner', foreignKey='Person',
@@ -173,7 +172,7 @@
173 lucilleconfig = StringCol(notNull=False, default=None)172 lucilleconfig = StringCol(notNull=False, default=None)
174 changeslist = StringCol(notNull=False, default=None)173 changeslist = StringCol(notNull=False, default=None)
175 nominatedarchindep = ForeignKey(174 nominatedarchindep = ForeignKey(
176 dbName='nominatedarchindep',foreignKey='DistroArchSeries',175 dbName='nominatedarchindep', foreignKey='DistroArchSeries',
177 notNull=False, default=None)176 notNull=False, default=None)
178 messagecount = IntCol(notNull=True, default=0)177 messagecount = IntCol(notNull=True, default=0)
179 binarycount = IntCol(notNull=True, default=DEFAULT)178 binarycount = IntCol(notNull=True, default=DEFAULT)
@@ -751,7 +750,7 @@
751750
752 # filter based on completion. see the implementation of751 # filter based on completion. see the implementation of
753 # Specification.is_complete() for more details752 # Specification.is_complete() for more details
754 completeness = Specification.completeness_clause753 completeness = Specification.completeness_clause
755754
756 if SpecificationFilter.COMPLETE in filter:755 if SpecificationFilter.COMPLETE in filter:
757 query += ' AND ( %s ) ' % completeness756 query += ' AND ( %s ) ' % completeness
@@ -886,29 +885,6 @@
886 DistroSeriesSourcePackageRelease(self, release))885 DistroSeriesSourcePackageRelease(self, release))
887 for release in releases)886 for release in releases)
888887
889 def checkTranslationsViewable(self):
890 """See `IDistroSeries`."""
891 if not self.hide_all_translations:
892 # Yup, viewable.
893 return
894
895 future = [
896 SeriesStatus.EXPERIMENTAL,
897 SeriesStatus.DEVELOPMENT,
898 SeriesStatus.FUTURE,
899 ]
900 if self.status in future:
901 raise TranslationUnavailable(
902 "Translations for this release series are not available yet.")
903 elif self.status == SeriesStatus.OBSOLETE:
904 raise TranslationUnavailable(
905 "This release series is obsolete. Its translations are no "
906 "longer available.")
907 else:
908 raise TranslationUnavailable(
909 "Translations for this release series are not currently "
910 "available. Please come back soon.")
911
912 def getTranslatableSourcePackages(self):888 def getTranslatableSourcePackages(self):
913 """See `IDistroSeries`."""889 """See `IDistroSeries`."""
914 query = """890 query = """
@@ -1069,13 +1045,12 @@
1069 SourcePackagePublishingHistory.archive IN %s AND1045 SourcePackagePublishingHistory.archive IN %s AND
1070 SourcePackagePublishingHistory.status=%s AND1046 SourcePackagePublishingHistory.status=%s AND
1071 SourcePackagePublishingHistory.pocket=%s1047 SourcePackagePublishingHistory.pocket=%s
1072 """ % sqlvalues(self, archives, status, pocket)1048 """ % sqlvalues(self, archives, status, pocket)
10731049
1074 if component:1050 if component:
1075 clause += (1051 clause += (
1076 " AND SourcePackagePublishingHistory.component=%s"1052 " AND SourcePackagePublishingHistory.component=%s"
1077 % sqlvalues(component)1053 % sqlvalues(component))
1078 )
10791054
1080 orderBy = ['SourcePackageName.name']1055 orderBy = ['SourcePackageName.name']
1081 clauseTables = ['SourcePackageRelease', 'SourcePackageName']1056 clauseTables = ['SourcePackageRelease', 'SourcePackageName']
@@ -1136,7 +1111,7 @@
11361111
1137 clauseTables = ['BinaryPackagePublishingHistory', 'DistroArchSeries',1112 clauseTables = ['BinaryPackagePublishingHistory', 'DistroArchSeries',
1138 'BinaryPackageRelease', 'BinaryPackageName', 'Build',1113 'BinaryPackageRelease', 'BinaryPackageName', 'Build',
1139 'SourcePackageRelease', 'SourcePackageName' ]1114 'SourcePackageRelease', 'SourcePackageName']
11401115
1141 result = BinaryPackagePublishingHistory.select(1116 result = BinaryPackagePublishingHistory.select(
1142 query, distinct=False, clauseTables=clauseTables, orderBy=orderBy)1117 query, distinct=False, clauseTables=clauseTables, orderBy=orderBy)
@@ -1971,8 +1946,9 @@
1971 return '%s%s' % (self.name, pocketsuffix[pocket])1946 return '%s%s' % (self.name, pocketsuffix[pocket])
19721947
1973 def isSourcePackageFormatPermitted(self, format):1948 def isSourcePackageFormatPermitted(self, format):
1974 return getUtility(ISourcePackageFormatSelectionSet1949 return getUtility(
1975 ).getBySeriesAndFormat(self, format) is not None1950 ISourcePackageFormatSelectionSet).getBySeriesAndFormat(
1951 self, format) is not None
19761952
19771953
1978class DistroSeriesSet:1954class DistroSeriesSet:
@@ -1990,8 +1966,7 @@
1990 result_set = store.using((DistroSeries, POTemplate)).find(1966 result_set = store.using((DistroSeries, POTemplate)).find(
1991 DistroSeries,1967 DistroSeries,
1992 DistroSeries.hide_all_translations == False,1968 DistroSeries.hide_all_translations == False,
1993 DistroSeries.id == POTemplate.distroseriesID1969 DistroSeries.id == POTemplate.distroseriesID).config(distinct=True)
1994 ).config(distinct=True)
1995 # XXX: henninge 2009-02-11 bug=217644: Convert to sequence right here1970 # XXX: henninge 2009-02-11 bug=217644: Convert to sequence right here
1996 # because ResultSet reports a wrong count() when using DISTINCT. Also1971 # because ResultSet reports a wrong count() when using DISTINCT. Also
1997 # ResultSet does not implement __len__(), which would make it more1972 # ResultSet does not implement __len__(), which would make it more
19981973
=== modified file 'lib/lp/translations/browser/browser_helpers.py'
--- lib/lp/translations/browser/browser_helpers.py 2009-09-04 11:54:50 +0000
+++ lib/lp/translations/browser/browser_helpers.py 2010-03-01 23:03:20 +0000
@@ -30,16 +30,16 @@
30 """Replace Rosetta escape sequences with the real characters."""30 """Replace Rosetta escape sequences with the real characters."""
31 return helpers.text_replaced(text, {'[tab]': '\t',31 return helpers.text_replaced(text, {'[tab]': '\t',
32 r'\[tab]': '[tab]',32 r'\[tab]': '[tab]',
33 '[nbsp]' : u'\u00a0',33 '[nbsp]': u'\u00a0',
34 r'\[nbsp]' : '[nbsp]' })34 r'\[nbsp]': '[nbsp]'})
3535
3636
37def expand_rosetta_escapes(unicode_text):37def expand_rosetta_escapes(unicode_text):
38 """Replace characters needing a Rosetta escape sequences."""38 """Replace characters needing a Rosetta escape sequences."""
39 escapes = {u'\t': TranslationConstants.TAB_CHAR,39 escapes = {u'\t': TranslationConstants.TAB_CHAR,
40 u'[tab]': TranslationConstants.TAB_CHAR_ESCAPED,40 u'[tab]': TranslationConstants.TAB_CHAR_ESCAPED,
41 u'\u00a0' : TranslationConstants.NO_BREAK_SPACE_CHAR,41 u'\u00a0': TranslationConstants.NO_BREAK_SPACE_CHAR,
42 u'[nbsp]' : TranslationConstants.NO_BREAK_SPACE_CHAR_ESCAPED }42 u'[nbsp]': TranslationConstants.NO_BREAK_SPACE_CHAR_ESCAPED}
43 return helpers.text_replaced(unicode_text, escapes)43 return helpers.text_replaced(unicode_text, escapes)
4444
4545
@@ -187,5 +187,3 @@
187 raise UnrecognisedCFormatString(string)187 raise UnrecognisedCFormatString(string)
188188
189 return segments189 return segments
190
191
192190
=== modified file 'lib/lp/translations/browser/distroseries.py'
--- lib/lp/translations/browser/distroseries.py 2010-02-02 15:32:00 +0000
+++ lib/lp/translations/browser/distroseries.py 2010-03-01 23:03:20 +0000
@@ -11,15 +11,16 @@
11 'DistroSeriesTranslationsAdminView',11 'DistroSeriesTranslationsAdminView',
12 'DistroSeriesTranslationsMenu',12 'DistroSeriesTranslationsMenu',
13 'DistroSeriesView',13 'DistroSeriesView',
14 'check_distroseries_translations_viewable',
14 ]15 ]
1516
16from zope.component import getUtility17from zope.component import getUtility
1718
18from canonical.cachedproperty import cachedproperty19from canonical.cachedproperty import cachedproperty
19from canonical.launchpad import helpers20from canonical.launchpad import helpers
20from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
21from canonical.launchpad.webapp import action21from canonical.launchpad.webapp import action
22from canonical.launchpad.webapp.authorization import check_permission22from canonical.launchpad.webapp.authorization import check_permission
23from canonical.launchpad.webapp.interfaces import TranslationUnavailable
23from canonical.launchpad.webapp.launchpadform import LaunchpadEditFormView24from canonical.launchpad.webapp.launchpadform import LaunchpadEditFormView
24from canonical.launchpad.webapp.menu import (25from canonical.launchpad.webapp.menu import (
25 Link, NavigationMenu, enabled_with_permission)26 Link, NavigationMenu, enabled_with_permission)
@@ -27,6 +28,7 @@
27 canonical_url, LaunchpadView)28 canonical_url, LaunchpadView)
2829
29from lp.registry.interfaces.distroseries import IDistroSeries30from lp.registry.interfaces.distroseries import IDistroSeries
31from lp.registry.interfaces.series import SeriesStatus
30from lp.translations.browser.translations import TranslationsMixin32from lp.translations.browser.translations import TranslationsMixin
31from lp.translations.browser.potemplate import BaseSeriesTemplatesView33from lp.translations.browser.potemplate import BaseSeriesTemplatesView
32from lp.translations.interfaces.distroserieslanguage import (34from lp.translations.interfaces.distroserieslanguage import (
@@ -175,34 +177,11 @@
175 self.context.version)177 self.context.version)
176178
177 def checkTranslationsViewable(self):179 def checkTranslationsViewable(self):
178 """Check that these translations are visible to the current user.180 """ Check if user can view translations for this `IDistroSeries`"""
179181
180 Launchpad admins, Translations admins, and users with admin182 # Is user allowed to see translations for this distroseries?
181 rights on the `DistroSeries` are always allowed. For others183 # If not, raise TranslationUnavailable.
182 this delegates to `IDistroSeries.checkTranslationsViewable`,184 check_distroseries_translations_viewable(self.context)
183 which raises `TranslationUnavailable` if the translations are
184 set to be hidden.
185
186 :return: Returns normally if this series' translations are
187 viewable to the current user.
188 :raise TranslationUnavailable: if this series' translations are
189 hidden and the user is not one of the limited caste that is
190 allowed to access them.
191 """
192 if check_permission('launchpad.TranslationsAdmin', self.context):
193 # Anyone with admin rights on this series passes. This
194 # includes Launchpad admins.
195 return
196
197 user = self.user
198 experts = getUtility(ILaunchpadCelebrities).rosetta_experts
199 if user is not None and user.inTeam(experts):
200 # Translations admins also pass.
201 return
202
203 # Everyone else passes only if translations are viewable to the
204 # public.
205 self.context.checkTranslationsViewable()
206185
207 def distroserieslanguages(self):186 def distroserieslanguages(self):
208 """Produces a list containing a DistroSeriesLanguage object for187 """Produces a list containing a DistroSeriesLanguage object for
@@ -271,3 +250,47 @@
271250
272 def language_packs(self):251 def language_packs(self):
273 return Link('+language-packs', 'Language packs')252 return Link('+language-packs', 'Language packs')
253
254
255def check_distroseries_translations_viewable(distroseries):
256 """Check that these distribution series translations are visible.
257
258 Launchpad admins, Translations admins, and users with admin
259 rights on the `IDistroSeries` are always allowed.
260
261 Checks the `hide_all_translations` flag. If it is set, these
262 translations are not to be shown to the public. In that case an
263 appropriate message is composed based on the series' `status`,
264 and a `TranslationUnavailable` exception is raised.
265
266 :return: Returns normally if this series' translations are
267 viewable to the current user.
268 :raise TranslationUnavailable: if this series' translations are
269 hidden and the user is not one of the limited caste that is
270 allowed to access them.
271 """
272
273 if not distroseries.hide_all_translations:
274 # Yup, viewable.
275 return
276
277 if check_permission(
278 'launchpad.TranslationsAdmin', distroseries):
279 return
280
281 future = [
282 SeriesStatus.EXPERIMENTAL,
283 SeriesStatus.DEVELOPMENT,
284 SeriesStatus.FUTURE,
285 ]
286 if distroseries.status in future:
287 raise TranslationUnavailable(
288 "Translations for this release series are not available yet.")
289 elif distroseries.status == SeriesStatus.OBSOLETE:
290 raise TranslationUnavailable(
291 "This release series is obsolete. Its translations are no "
292 "longer available.")
293 else:
294 raise TranslationUnavailable(
295 "Translations for this release series are not currently "
296 "available. Please come back soon.")
274297
=== modified file 'lib/lp/translations/browser/tests/distroseries-views.txt'
--- lib/lp/translations/browser/tests/distroseries-views.txt 2009-12-13 11:55:40 +0000
+++ lib/lp/translations/browser/tests/distroseries-views.txt 2010-03-01 23:03:20 +0000
@@ -1,9 +1,9 @@
1= DistroSeries translations view classes =1DistroSeries translations view classes
2======================================
3
4Let's use ubuntu/hoary for these tests.
25
3 >>> from canonical.launchpad.webapp.servers import LaunchpadTestRequest6 >>> from canonical.launchpad.webapp.servers import LaunchpadTestRequest
4
5Let's use ubuntu/hoary for these tests.
6
7 >>> from canonical.launchpad.interfaces import IDistributionSet7 >>> from canonical.launchpad.interfaces import IDistributionSet
8 >>> from lp.registry.interfaces.series import SeriesStatus8 >>> from lp.registry.interfaces.series import SeriesStatus
9 >>> ubuntu = getUtility(IDistributionSet).getByName('ubuntu')9 >>> ubuntu = getUtility(IDistributionSet).getByName('ubuntu')
@@ -15,7 +15,9 @@
15 >>> login('foo.bar@canonical.com')15 >>> login('foo.bar@canonical.com')
16 >>> hoary.status = SeriesStatus.CURRENT16 >>> hoary.status = SeriesStatus.CURRENT
1717
18== Hiding translations ==18
19Hiding translations
20-------------------
1921
20Each distroseries has a switch that allows administrators to either22Each distroseries has a switch that allows administrators to either
21reveal its translations to the public or hide them from the public.23reveal its translations to the public or hide them from the public.
@@ -95,9 +97,65 @@
95 release series are not currently available. Please come back soon.97 release series are not currently available. Please come back soon.
9698
97The same goes for anonymous users.99The same goes for anonymous users.
98100Exactly what message is displayed depends on the series' status.
99 >>> login(ANONYMOUS)101
100 >>> check_effect_of_hiding(hoary)102 >>> login('foo.bar@canonical.com')
101 User can access revealed translations.103 >>> hoary.status = SeriesStatus.EXPERIMENTAL
102 User can not access hidden translations: Translations for this104 >>> login(ANONYMOUS)
103 release series are not currently available. Please come back soon.105 >>> check_effect_of_hiding(hoary)
106 User can access revealed translations.
107 User can not access hidden translations:
108 Translations for this release series are not available yet.
109
110 >>> login('foo.bar@canonical.com')
111 >>> hoary.status = SeriesStatus.DEVELOPMENT
112 >>> login(ANONYMOUS)
113 >>> check_effect_of_hiding(hoary)
114 User can access revealed translations.
115 User can not access hidden translations:
116 Translations for this release series are not available yet.
117
118 >>> login('foo.bar@canonical.com')
119 >>> hoary.status = SeriesStatus.FROZEN
120 >>> login(ANONYMOUS)
121 >>> check_effect_of_hiding(hoary)
122 User can access revealed translations.
123 User can not access hidden translations:
124 Translations for this release series are not currently available.
125 Please come back soon.
126
127 >>> login('foo.bar@canonical.com')
128 >>> hoary.status = SeriesStatus.CURRENT
129 >>> login(ANONYMOUS)
130 >>> check_effect_of_hiding(hoary)
131 User can access revealed translations.
132 User can not access hidden translations:
133 Translations for this release series are not currently available.
134 Please come back soon.
135
136 >>> login('foo.bar@canonical.com')
137 >>> hoary.status = SeriesStatus.SUPPORTED
138 >>> login(ANONYMOUS)
139 >>> check_effect_of_hiding(hoary)
140 User can access revealed translations.
141 User can not access hidden translations:
142 Translations for this release series are not currently available.
143 Please come back soon.
144
145 >>> login('foo.bar@canonical.com')
146 >>> hoary.status = SeriesStatus.OBSOLETE
147 >>> login(ANONYMOUS)
148 >>> check_effect_of_hiding(hoary)
149 User can access revealed translations.
150 User can not access hidden translations:
151 This release series is obsolete. Its translations are no longer
152 available.
153
154 >>> login('foo.bar@canonical.com')
155 >>> hoary.status = SeriesStatus.FUTURE
156 >>> login(ANONYMOUS)
157 >>> check_effect_of_hiding(hoary)
158 User can access revealed translations.
159 User can not access hidden translations:
160 Translations for this release series are not available yet.
161
104162
=== modified file 'lib/lp/translations/stories/distroseries/xx-distroseries-translations.txt'
--- lib/lp/translations/stories/distroseries/xx-distroseries-translations.txt 2009-12-21 17:18:12 +0000
+++ lib/lp/translations/stories/distroseries/xx-distroseries-translations.txt 2010-03-01 23:03:20 +0000
@@ -46,9 +46,7 @@
46... but the link is available to administrators:46... but the link is available to administrators:
4747
48 >>> dtc_browser = setupDTCBrowser()48 >>> dtc_browser = setupDTCBrowser()
49
50 >>> dtc_browser.open('http://translations.launchpad.dev/ubuntu/hoary')49 >>> dtc_browser.open('http://translations.launchpad.dev/ubuntu/hoary')
51
52 >>> dtc_browser.getLink('Change settings').click()50 >>> dtc_browser.getLink('Change settings').click()
5351
54Once the administrator hides all translations...52Once the administrator hides all translations...
@@ -115,8 +113,8 @@
115non-administrative users.113non-administrative users.
116114
117 >>> user_browser.open(115 >>> user_browser.open(
118 ... 'http://translations.launchpad.dev/ubuntu/hoary/+sources/evolution/'116 ... 'http://translations.launchpad.dev/ubuntu/hoary/'
119 ... '+pots/evolution-2.2')117 ... '+sources/evolution/+pots/evolution-2.2')
120 Traceback (most recent call last):118 Traceback (most recent call last):
121 ...119 ...
122 TranslationUnavailable: ...120 TranslationUnavailable: ...
@@ -126,7 +124,7 @@
126124
127 >>> dtc_browser.open(125 >>> dtc_browser.open(
128 ... 'http://translations.launchpad.dev/ubuntu/hoary/'126 ... 'http://translations.launchpad.dev/ubuntu/hoary/'
129 ... '+sources/evolution')127 ... '+sources/evolution/+pots/evolution-2.2')
130128
131There is also an option to set/unset whether translation imports for a129There is also an option to set/unset whether translation imports for a
132distribution should be deferred. That option is set also from the same130distribution should be deferred. That option is set also from the same
133131
=== modified file 'lib/lp/translations/templates/distroseries-langchart.pt'
--- lib/lp/translations/templates/distroseries-langchart.pt 2009-12-27 20:58:33 +0000
+++ lib/lp/translations/templates/distroseries-langchart.pt 2010-03-01 23:03:20 +0000
@@ -8,6 +8,11 @@
8 <strong>Translations for this series are currently hidden.</strong>8 <strong>Translations for this series are currently hidden.</strong>
99
10 <!-- Bounce regular users to "translations unavailable" page. -->10 <!-- Bounce regular users to "translations unavailable" page. -->
11 <tal:XXX condition="nothing">
12 20100224 adiroiban bug=527069: The following tal:omit-tag is only
13 used with the sole purpose of raising an exception.
14 There should be a better solution for this problem.
15 </tal:XXX>
11 <metal:check-available tal:omit-tag="view/checkTranslationsViewable" />16 <metal:check-available tal:omit-tag="view/checkTranslationsViewable" />
12 </p>17 </p>
1318