Merge lp:~henninge/launchpad/bug-422466-api into lp:launchpad

Proposed by Henning Eggers
Status: Rejected
Rejected by: Henning Eggers
Proposed branch: lp:~henninge/launchpad/bug-422466-api
Merge into: lp:launchpad
Prerequisite: lp:~mars/launchpad/yui-3final-upgrade
Diff against target: 1442 lines (+412/-122)
26 files modified
lib/canonical/launchpad/security.py (+10/-14)
lib/lp/translations/browser/hastranslationimports.py (+4/-4)
lib/lp/translations/browser/translationimportqueue.py (+1/-1)
lib/lp/translations/doc/poexport-language-pack.txt (+6/-2)
lib/lp/translations/doc/poimport-pofile-not-exported-from-rosetta.txt (+5/-3)
lib/lp/translations/doc/poimport-pofile-old-po-imported.txt (+6/-4)
lib/lp/translations/doc/poimport-pofile-syntax-error.txt (+10/-8)
lib/lp/translations/doc/poimport-potemplate-syntax-error.txt (+5/-3)
lib/lp/translations/doc/poimport.txt (+14/-14)
lib/lp/translations/doc/rosetta-karma.txt (+6/-6)
lib/lp/translations/doc/rosetta-poimport-script.txt (+4/-2)
lib/lp/translations/doc/translationimportqueue.txt (+43/-32)
lib/lp/translations/interfaces/translationimportqueue.py (+37/-8)
lib/lp/translations/model/pofile.py (+5/-2)
lib/lp/translations/model/potemplate.py (+5/-2)
lib/lp/translations/model/translationbranchapprover.py (+3/-1)
lib/lp/translations/model/translationimportqueue.py (+64/-8)
lib/lp/translations/scripts/po_import.py (+2/-1)
lib/lp/translations/stories/webservice/xx-translationimportqueue.txt (+17/-2)
lib/lp/translations/tests/test_autoapproval.py (+7/-1)
lib/lp/translations/tests/test_translationbranchapprover.py (+3/-1)
lib/lp/translations/tests/test_translationimportqueue.py (+125/-0)
lib/lp/translations/utilities/permission_helpers.py (+21/-0)
lib/lp/translations/utilities/tests/helpers.py (+3/-1)
lib/lp/translations/utilities/tests/test_xpi_import.py (+3/-1)
lib/lp/translations/utilities/tests/test_xpi_po_exporter.py (+3/-1)
To merge this branch: bzr merge lp:~henninge/launchpad/bug-422466-api
Reviewer Review Type Date Requested Status
Henning Eggers (community) code Disapprove
Edwin Grubbs (community) code Needs Fixing
Review via email: mp+14841@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Henning Eggers (henninge) wrote :
Download full text (4.2 KiB)

= Bug 482267 =

In peparation for fixing bug 422466 which adds a lazr-js status picker to the translation import queue, the TranslationImportQueueEntry.setStatus needs to be made available through the API. The status attribute had been made read-only and the setStatus method had been introduced for this very purpose but setStatus was still lacking checks on the status value being set. This is were it gets tricky because the normal security policy enforces access restrictions on attributes and methods but not on the parameters to methods. These checks have to be added to the model code, parallelizing some of what is done by the security policy.

== Proposed Fix ==

This branch adds an extra parameter to setStatus called "user" that references the user that is requesting the change.
It also adds a canSetStatus method that encapsulates the necessary checks and is also callable from outside to be used by view code to prepare the status picker.
Some of the code in canSetStatus is identical to code in security.py so these are exposed as seperate method isUbuntuAndIsUserTranslationGroupOwner and isUserUploaderOrOwner and called from security.py.
Also, a new module permission_helpers.py was added to contain a function that is both used in security.py and the model code. It would be the subject of another branch to go through the code and find more places where this function can be used.

== Implemention Notes ==

A lot of tests are affected by the new parameter to setStatus and fixing all the call sites of setStatus is the bulk of the changes in this branch. Tests and scripts are using "rosetta_experts" to be able to freely set statuses.
In addition setStatus checks if an import target has been set before allowing anybody to set the status to "Approved". Some tests ignored this and had to be fixed.

== Tests ==

Oh well, just run all the translation tests ..

bin/test -vvct lp.translations

== Demo/QA ==

Launchpadlib is your friend.

    queue = launchpad.translation_import_queue_entries
    entry = queue.getAllEntries(import_status="Imported")[0]
    print repr(entry)
    print entry.status
    entry.setStatus(new_status='Blocked')
    entry_link = entry.self_link
    print entry_link

Try out with different credentials so see the results.

= 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/translations/browser/hastranslationimports.py
  lib/lp/translations/browser/translationimportqueue.py
  lib/lp/translations/doc/poexport-language-pack.txt
  lib/lp/translations/doc/poimport-pofile-not-exported-from-rosetta.txt
  lib/lp/translations/doc/poimport-pofile-old-po-imported.txt
  lib/lp/translations/doc/poimport-pofile-syntax-error.txt
  lib/lp/translations/doc/poimport-potemplate-syntax-error.txt
  lib/lp/translations/doc/poimport.txt
  lib/lp/translations/doc/rosetta-karma.txt
  lib/lp/translations/doc/rosetta-poimport-script.txt
  lib/lp/translations/doc/translationimportqueue.txt
  lib/lp/translations/interfaces/translationimportqueue.py
  lib/lp/translations/model/pofile.py
  lib/...

Read more...

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

Correct QA code for launchpadlib:

    queue = launchpad.translation_import_queue_entries
    entry = queue.getAllEntries(import_status="Imported")[0]
    print repr(entry)
    print entry.status

    entry.setStatus(new_status='Deleted')
    entry_link = entry.self_link
    entry = launchpad.load(entry_link)
    print repr(entry)
    print entry.status

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

 canonical/launchpad/security.py | 24 -
 lp/translations/browser/hastranslationimports.py | 8
 lp/translations/browser/translationimportqueue.py | 2
 lp/translations/doc/poexport-language-pack.txt | 8
 lp/translations/doc/poimport-pofile-not-exported-from-rosetta.txt | 8
 lp/translations/doc/poimport-pofile-old-po-imported.txt | 10
 lp/translations/doc/poimport-pofile-syntax-error.txt | 18 -
 lp/translations/doc/poimport-potemplate-syntax-error.txt | 8
 lp/translations/doc/poimport.txt | 28 +-
 lp/translations/doc/rosetta-karma.txt | 12
 lp/translations/doc/rosetta-poimport-script.txt | 6
 lp/translations/doc/translationimportqueue.txt | 75 +++---
 lp/translations/interfaces/translationimportqueue.py | 45 ++-
 lp/translations/model/pofile.py | 7
 lp/translations/model/potemplate.py | 7
 lp/translations/model/translationbranchapprover.py | 4
 lp/translations/model/translationimportqueue.py | 72 +++++
 lp/translations/scripts/po_import.py | 3
 lp/translations/stories/webservice/xx-translationimportqueue.txt | 19 +
 lp/translations/tests/test_autoapproval.py | 8
 lp/translations/tests/test_translationbranchapprover.py | 4
 lp/translations/tests/test_translationimportqueue.py | 125 ++++++++++
 lp/translations/utilities/permission_helpers.py | 21 +
 lp/translations/utilities/tests/helpers.py | 4
 lp/translations/utilities/tests/test_xpi_import.py | 4
 lp/translations/utilities/tests/test_xpi_po_exporter.py | 4
 26 files changed, 412 insertions(+), 122 deletions(-)

Revision history for this message
Edwin Grubbs (edwin-grubbs) wrote :
Download full text (11.9 KiB)

Hi Henning,

This is a nice branch with very good test coverage. I mostly have minor comments, but I do have one question below, so I'm marking this:

needs-reply

-Edwin

>=== added symlink 'lib/canonical/launchpad/icing/yui'
>=== target is u'../../../../lazr-js/build/yui'

I think this symlink must have been for testing so should be removed.

>=== modified file 'lib/lp/translations/model/translationimportqueue.py'
>--- lib/lp/translations/model/translationimportqueue.py 2009-10-22 07:32:02 +0000
>+++ lib/lp/translations/model/translationimportqueue.py 2009-11-13 18:41:14 +0000
>@@ -56,11 +56,14 @@
> ITranslationImportQueueEntry,
> RosettaImportStatus,
> SpecialTranslationImportTargetFilter,
>- TranslationImportQueueConflictError)
>+ TranslationImportQueueConflictError,
>+ UserCannotSetTranslationImportStatus)
> from lp.translations.interfaces.potemplate import IPOTemplate
> from lp.translations.interfaces.translations import TranslationConstants
> from lp.translations.utilities.gettext_po_importer import (
> GettextPOImporter)
>+from lp.translations.utilities.permission_helpers import (
>+ is_admin_or_rosetta_expert)
> from canonical.librarian.interfaces import ILibrarianClient
>
>
>@@ -271,9 +274,58 @@
> distroseries=self.distroseries,
> sourcepackagename=self.sourcepackagename)
>
>- def setStatus(self, status):
>- """See `ITranslationImportQueueEntry`."""
>- self.status = status
>+ def isUbuntuAndIsUserTranslationGroupOwner(self, user):
>+ """See `ITranslationImportQueueEntry`."""
>+ # As a special case, the Ubuntu translation group owners can
>+ # manage Ubuntu uploads.
>+ if self.is_targeted_to_ubuntu:
>+ group = self.distroseries.distribution.translationgroup
>+ if group is not None and user.inTeam(group.owner):
>+ return True
>+ return False
>+
>+ def isUserUploaderOrOwner(self, user):
>+ """See `ITranslationImportQueueEntry`."""
>+ if user.inTeam(self.importer):
>+ return True
>+ if self.productseries is not None:
>+ return user.inTeam(self.productseries.product.owner)
>+ if self.distroseries is not None:
>+ return user.inTeam(self.distroseries.distribution.owner)

Is there no situation where the series owner would be different from
the product or distro owner and would need access?

>+ return False
>+
>+ def canSetStatus(self, new_status, user):
>+ """See `ITranslationImportQueueEntry`."""
>+ if new_status == self.status:
>+ # Leaving status as it is is always allowed.
>+ return True
>+ if user is None:
>+ # Anonymous user cannot do anything.
>+ return False
>+ can_admin = (is_admin_or_rosetta_expert(user) or
>+ self.isUbuntuAndIsUserTranslationGroupOwner(user))
>+ if (new_status == RosettaImportStatus.APPROVED and
>+ not (self.import_into is not None and can_admin)):

I think it would be clearer without the double negatives.
  if (new_status == RosettaImportStatus.APPROVED and
      (self.import_into ...

review: Needs Fixing (code)
Revision history for this message
Henning Eggers (henninge) wrote :
Download full text (13.9 KiB)

Am 15.11.2009 01:10, Edwin Grubbs schrieb:
> This is a nice branch with very good test coverage. I mostly have minor comments, but I do have one question below, so I'm marking this:
>
> needs-reply

No problem. Thanks for taking this on in the first place. Let's see what
you got...

>
>> === added symlink 'lib/canonical/launchpad/icing/yui'
>> === target is u'../../../../lazr-js/build/yui'
>
>
> I think this symlink must have been for testing so should be removed.

Oops, something went seriously wrong here. I did not do anything with
that. I now realised that I based my branch on devel and merged Maris'
branch - which is based on db-devel ...

Since this branch is not dependent on yui-3final at all, I created a new
one and merged the relevant revisions. I just don't know how to proceed
now. Should I propose that branch for merging and request a new review?
Yeah, I'll just do that.

I have a diff of the diffs attached that shows that the two branches are
identical except for the mtimes of the files and without the above
symlink addition. This diff-diff was made before I addressed any of the
following issues.

>
>
>> === modified file 'lib/lp/translations/model/translationimportqueue.py'
>> --- lib/lp/translations/model/translationimportqueue.py 2009-10-22 07:32:02 +0000
>> +++ lib/lp/translations/model/translationimportqueue.py 2009-11-13 18:41:14 +0000
>> @@ -56,11 +56,14 @@
>> ITranslationImportQueueEntry,
>> RosettaImportStatus,
>> SpecialTranslationImportTargetFilter,
>> - TranslationImportQueueConflictError)
>> + TranslationImportQueueConflictError,
>> + UserCannotSetTranslationImportStatus)
>> from lp.translations.interfaces.potemplate import IPOTemplate
>> from lp.translations.interfaces.translations import TranslationConstants
>> from lp.translations.utilities.gettext_po_importer import (
>> GettextPOImporter)
>> +from lp.translations.utilities.permission_helpers import (
>> + is_admin_or_rosetta_expert)
>> from canonical.librarian.interfaces import ILibrarianClient
>>
>>
>> @@ -271,9 +274,58 @@
>> distroseries=self.distroseries,
>> sourcepackagename=self.sourcepackagename)
>>
>> - def setStatus(self, status):
>> - """See `ITranslationImportQueueEntry`."""
>> - self.status = status
>> + def isUbuntuAndIsUserTranslationGroupOwner(self, user):
>> + """See `ITranslationImportQueueEntry`."""
>> + # As a special case, the Ubuntu translation group owners can
>> + # manage Ubuntu uploads.
>> + if self.is_targeted_to_ubuntu:
>> + group = self.distroseries.distribution.translationgroup
>> + if group is not None and user.inTeam(group.owner):
>> + return True
>> + return False
>> +
>> + def isUserUploaderOrOwner(self, user):
>> + """See `ITranslationImportQueueEntry`."""
>> + if user.inTeam(self.importer):
>> + return True
>> + if self.productseries is not None:
>> + return user.inTeam(self.productseries.product.owner)
>> + if self.distroseries is not None:
>> + return user.inTeam(self.distroseries.distribution.owner)
>
>
> Is there n...

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

This branch was proposed for merging into devel but relies on the yui-3final branch which is based on db-devel. This resulted in conflicts. A new branch and proposal has been created here:
https://code.edge.launchpad.net/~henninge/launchpad/bug-482267/+merge/14947

review: Disapprove (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added symlink 'lib/canonical/launchpad/icing/yui'
2=== target is u'../../../../lazr-js/build/yui'
3=== modified file 'lib/canonical/launchpad/security.py'
4--- lib/canonical/launchpad/security.py 2009-11-13 05:13:07 +0000
5+++ lib/canonical/launchpad/security.py 2009-11-13 18:41:14 +0000
6@@ -10,6 +10,7 @@
7 from zope.component import getUtility
8
9 from canonical.launchpad.interfaces.account import IAccount
10+from canonical.launchpad.interfaces.emailaddress import IEmailAddress
11 from lp.archiveuploader.permission import verify_upload
12 from lp.registry.interfaces.announcement import IAnnouncement
13 from lp.soyuz.interfaces.archive import IArchive
14@@ -48,7 +49,8 @@
15 from lp.registry.interfaces.distroseries import IDistroSeries
16 from lp.translations.interfaces.distroserieslanguage import (
17 IDistroSeriesLanguage)
18-from canonical.launchpad.interfaces.emailaddress import IEmailAddress
19+from lp.translations.utilities.permission_helpers import (
20+ is_admin_or_rosetta_expert)
21 from lp.registry.interfaces.entitlement import IEntitlement
22 from canonical.launchpad.interfaces.hwdb import (
23 IHWDBApplication, IHWDevice, IHWDeviceClass, IHWDriver, IHWDriverName,
24@@ -475,10 +477,7 @@
25
26 def checkAuthenticated(self, user):
27 """Allow Launchpad's admins and Rosetta experts edit all fields."""
28- celebrities = getUtility(ILaunchpadCelebrities)
29- return (user.inTeam(celebrities.admin) or
30- user.inTeam(celebrities.rosetta_experts))
31-
32+ return is_admin_or_rosetta_expert(user)
33
34 class AdminProductTranslations(AuthorizationBase):
35 permission = 'launchpad.TranslationsAdmin'
36@@ -490,10 +489,8 @@
37 Any Launchpad/Launchpad Translations administrator or owners are
38 able to change translation settings for a product.
39 """
40- celebrities = getUtility(ILaunchpadCelebrities)
41 return (user.inTeam(self.obj.owner) or
42- user.inTeam(celebrities.admin) or
43- user.inTeam(celebrities.rosetta_experts))
44+ is_admin_or_rosetta_expert(user))
45
46
47 class AdminSeriesByVCSImports(AuthorizationBase):
48@@ -1310,10 +1307,8 @@
49
50 # As a special case, the Ubuntu translation group owners can
51 # manage Ubuntu uploads.
52- if self.obj.is_targeted_to_ubuntu:
53- group = self.obj.distroseries.distribution.translationgroup
54- if group is not None and user.inTeam(group.owner):
55- return True
56+ if self.obj.isUbuntuAndIsUserTranslationGroupOwner(user):
57+ return True
58
59 return False
60
61@@ -1323,11 +1318,12 @@
62 usedfor = ITranslationImportQueueEntry
63
64 def checkAuthenticated(self, user):
65- """Anyone who can admin an entry, plus its owner, can edit it.
66+ """Anyone who can admin an entry, plus its owner or the owner of the
67+ product or distribution, can edit it.
68 """
69 if AdminTranslationImportQueueEntry.checkAuthenticated(self, user):
70 return True
71- if user.inTeam(self.obj.importer):
72+ if self.obj.isUserUploaderOrOwner(user):
73 return True
74
75 return False
76
77=== modified file 'lib/lp/translations/browser/hastranslationimports.py'
78--- lib/lp/translations/browser/hastranslationimports.py 2009-09-17 20:11:48 +0000
79+++ lib/lp/translations/browser/hastranslationimports.py 2009-11-13 18:41:14 +0000
80@@ -223,17 +223,17 @@
81 # special permissions to change status.
82 if (new_status_name == RosettaImportStatus.DELETED.name and
83 check_permission('launchpad.Edit', entry)):
84- entry.setStatus(RosettaImportStatus.DELETED)
85+ entry.setStatus(RosettaImportStatus.DELETED, self.user)
86 elif (new_status_name == RosettaImportStatus.BLOCKED.name and
87 check_permission('launchpad.Admin', entry)):
88- entry.setStatus(RosettaImportStatus.BLOCKED)
89+ entry.setStatus(RosettaImportStatus.BLOCKED, self.user)
90 elif (new_status_name == RosettaImportStatus.APPROVED.name and
91 check_permission('launchpad.Admin', entry) and
92 entry.import_into is not None):
93- entry.setStatus(RosettaImportStatus.APPROVED)
94+ entry.setStatus(RosettaImportStatus.APPROVED, self.user)
95 elif (new_status_name == RosettaImportStatus.NEEDS_REVIEW.name and
96 check_permission('launchpad.Admin', entry)):
97- entry.setStatus(RosettaImportStatus.NEEDS_REVIEW)
98+ entry.setStatus(RosettaImportStatus.NEEDS_REVIEW, self.user)
99 else:
100 # The user was not the importer or we are trying to set a
101 # status that must not be set from this form. That means that
102
103=== modified file 'lib/lp/translations/browser/translationimportqueue.py'
104--- lib/lp/translations/browser/translationimportqueue.py 2009-09-17 20:11:48 +0000
105+++ lib/lp/translations/browser/translationimportqueue.py 2009-11-13 18:41:14 +0000
106@@ -414,7 +414,7 @@
107 # Store the associated IPOTemplate.
108 self.context.potemplate = potemplate
109
110- self.context.setStatus(RosettaImportStatus.APPROVED)
111+ self.context.setStatus(RosettaImportStatus.APPROVED, self.user)
112 self.context.date_status_changed = UTC_NOW
113
114
115
116=== modified file 'lib/lp/translations/doc/poexport-language-pack.txt'
117--- lib/lp/translations/doc/poexport-language-pack.txt 2009-08-13 19:03:36 +0000
118+++ lib/lp/translations/doc/poexport-language-pack.txt 2009-11-13 18:41:14 +0000
119@@ -12,10 +12,12 @@
120 >>> import transaction
121 >>> from canonical.launchpad.ftests import login
122 >>> from canonical.launchpad.helpers import string_to_tarfile
123+ >>> from canonical.launchpad.interfaces import ILaunchpadCelebrities
124 >>> from lp.translations.scripts.language_pack import \
125 ... export_language_pack
126 >>> from canonical.librarian.interfaces import ILibrarianClient
127 >>> from canonical.database.sqlbase import flush_database_caches
128+ >>> rosetta_experts = getUtility(ILaunchpadCelebrities).rosetta_experts
129 >>> login("daf@canonical.com")
130
131 # This is a dummy logger class to capture the export's log messages.
132@@ -183,8 +185,10 @@
133 Before we are ready to import the attached files, we need to approve
134 them first.
135
136- >>> template_entry.setStatus(RosettaImportStatus.APPROVED)
137- >>> translation_entry.setStatus(RosettaImportStatus.APPROVED)
138+ >>> template_entry.setStatus(
139+ ... RosettaImportStatus.APPROVED, rosetta_experts)
140+ >>> translation_entry.setStatus(
141+ ... RosettaImportStatus.APPROVED, rosetta_experts)
142
143 Given that the files are attached to Librarian, we need to commit the
144 transaction to make sure it's stored properly and available.
145
146=== modified file 'lib/lp/translations/doc/poimport-pofile-not-exported-from-rosetta.txt'
147--- lib/lp/translations/doc/poimport-pofile-not-exported-from-rosetta.txt 2009-08-13 15:12:16 +0000
148+++ lib/lp/translations/doc/poimport-pofile-not-exported-from-rosetta.txt 2009-11-13 18:41:14 +0000
149@@ -19,13 +19,15 @@
150
151 Here are some imports we need to get this test running.
152
153- >>> from canonical.launchpad.interfaces import IPersonSet
154+ >>> from canonical.launchpad.interfaces import (
155+ ... ILaunchpadCelebrities, IPersonSet)
156 >>> from lp.translations.interfaces.translationimportqueue import (
157 ... ITranslationImportQueue, RosettaImportStatus)
158 >>> from lp.translations.model.potemplate import POTemplateSubset
159 >>> import pytz
160 >>> UTC = pytz.timezone('UTC')
161 >>> translation_import_queue = getUtility(ITranslationImportQueue)
162+ >>> rosetta_experts = getUtility(ILaunchpadCelebrities).rosetta_experts
163
164 We need this for the Librarian to work properly.
165
166@@ -80,7 +82,7 @@
167
168 We must approve the entry to be able to import it.
169
170- >>> entry.setStatus(RosettaImportStatus.APPROVED)
171+ >>> entry.setStatus(RosettaImportStatus.APPROVED, rosetta_experts)
172
173 We do the import.
174
175@@ -150,7 +152,7 @@
176
177 We must approve the entry to be able to import it.
178
179- >>> entry.setStatus(RosettaImportStatus.APPROVED)
180+ >>> entry.setStatus(RosettaImportStatus.APPROVED, rosetta_experts)
181
182 We do the import.
183
184
185=== modified file 'lib/lp/translations/doc/poimport-pofile-old-po-imported.txt'
186--- lib/lp/translations/doc/poimport-pofile-old-po-imported.txt 2009-08-13 15:12:16 +0000
187+++ lib/lp/translations/doc/poimport-pofile-old-po-imported.txt 2009-11-13 18:41:14 +0000
188@@ -16,7 +16,8 @@
189
190 Here are some imports we need to get this test running.
191
192- >>> from canonical.launchpad.interfaces import IPersonSet
193+ >>> from canonical.launchpad.interfaces import (
194+ ... ILaunchpadCelebrities, IPersonSet)
195 >>> from lp.translations.interfaces.translationimportqueue import (
196 ... ITranslationImportQueue)
197 >>> from lp.translations.model.potemplate import POTemplateSubset
198@@ -24,6 +25,7 @@
199 >>> import pytz
200 >>> UTC = pytz.timezone('UTC')
201 >>> translation_import_queue = getUtility(ITranslationImportQueue)
202+ >>> rosetta_experts = getUtility(ILaunchpadCelebrities).rosetta_experts
203
204 And also, the DBSchema to change the imports status
205
206@@ -80,7 +82,7 @@
207
208 We must approve the entry to be able to import it.
209
210- >>> entry.setStatus(RosettaImportStatus.APPROVED)
211+ >>> entry.setStatus(RosettaImportStatus.APPROVED, rosetta_experts)
212
213 We do the import. This succeeds without errors.
214
215@@ -128,7 +130,7 @@
216
217 We must approve the entry to be able to import it.
218
219- >>> entry.setStatus(RosettaImportStatus.APPROVED)
220+ >>> entry.setStatus(RosettaImportStatus.APPROVED, rosetta_experts)
221
222 We do the import.
223
224@@ -191,7 +193,7 @@
225
226 We approve the entry and import it.
227
228- >>> entry.setStatus(RosettaImportStatus.APPROVED)
229+ >>> entry.setStatus(RosettaImportStatus.APPROVED, rosetta_experts)
230 >>> (subject, message) = pofile.importFromQueue(entry)
231
232 This succeeds although the file's timestamp is older than that of the
233
234=== modified file 'lib/lp/translations/doc/poimport-pofile-syntax-error.txt'
235--- lib/lp/translations/doc/poimport-pofile-syntax-error.txt 2009-08-13 15:12:16 +0000
236+++ lib/lp/translations/doc/poimport-pofile-syntax-error.txt 2009-11-13 18:41:14 +0000
237@@ -7,7 +7,8 @@
238
239 Here are some imports we need to get this test running.
240
241- >>> from canonical.launchpad.interfaces import IPersonSet
242+ >>> from canonical.launchpad.interfaces import (
243+ ... ILaunchpadCelebrities, IPersonSet)
244 >>> from lp.translations.interfaces.translationimportqueue import (
245 ... ITranslationImportQueue)
246 >>> from lp.translations.model.potemplate import POTemplateSubset
247@@ -15,6 +16,7 @@
248 >>> import pytz
249 >>> UTC = pytz.timezone('UTC')
250 >>> translation_import_queue = getUtility(ITranslationImportQueue)
251+ >>> rosetta_experts = getUtility(ILaunchpadCelebrities).rosetta_experts
252
253 We need this for the Librarian to work properly.
254
255@@ -76,7 +78,7 @@
256
257 We must approve the entry to be able to import it.
258
259- >>> entry.setStatus(RosettaImportStatus.APPROVED)
260+ >>> entry.setStatus(RosettaImportStatus.APPROVED, rosetta_experts)
261
262 The import fails.
263
264@@ -135,7 +137,7 @@
265 >>> entry = translation_import_queue.addOrUpdateEntry(
266 ... pofile.path, pofile_contents, published, person,
267 ... productseries=series, potemplate=potemplate, pofile=pofile)
268- >>> entry.setStatus(RosettaImportStatus.APPROVED)
269+ >>> entry.setStatus(RosettaImportStatus.APPROVED, rosetta_experts)
270 >>> transaction.commit()
271 >>> (subject, message) = pofile.importFromQueue(entry)
272 >>> print entry.status.name
273@@ -202,7 +204,7 @@
274 ... pofile.path, pofile_contents, False, person,
275 ... productseries=series, potemplate=potemplate, pofile=pofile)
276 >>> transaction.commit()
277- >>> entry.setStatus(RosettaImportStatus.APPROVED)
278+ >>> entry.setStatus(RosettaImportStatus.APPROVED, rosetta_experts)
279 >>> (subject, message) = pofile.importFromQueue(entry)
280
281
282@@ -245,7 +247,7 @@
283 ... pofile.path, pofile_contents, False, person,
284 ... productseries=series, potemplate=potemplate, pofile=pofile)
285 >>> transaction.commit()
286- >>> entry.setStatus(RosettaImportStatus.APPROVED)
287+ >>> entry.setStatus(RosettaImportStatus.APPROVED, rosetta_experts)
288 >>> (subject, message) = pofile.importFromQueue(entry)
289
290 >>> print entry.status.name
291@@ -283,7 +285,7 @@
292 ... pofile.path, pofile_contents, False, person,
293 ... productseries=series, potemplate=potemplate, pofile=pofile)
294 >>> transaction.commit()
295- >>> entry.setStatus(RosettaImportStatus.APPROVED)
296+ >>> entry.setStatus(RosettaImportStatus.APPROVED, rosetta_experts)
297 >>> (subject, message) = pofile.importFromQueue(entry)
298
299 >>> print entry.status.name
300@@ -336,7 +338,7 @@
301 ... pofile.path, pofile_contents, False, person,
302 ... productseries=series, potemplate=potemplate, pofile=pofile)
303 >>> transaction.commit()
304- >>> entry.setStatus(RosettaImportStatus.APPROVED)
305+ >>> entry.setStatus(RosettaImportStatus.APPROVED, rosetta_experts)
306 >>> (subject, message) = pofile.importFromQueue(entry)
307
308 >>> print entry.status.name
309@@ -398,7 +400,7 @@
310 ... pofile.path, pofile_contents, False, person,
311 ... productseries=series, potemplate=potemplate, pofile=pofile)
312 >>> transaction.commit()
313- >>> entry.setStatus(RosettaImportStatus.APPROVED)
314+ >>> entry.setStatus(RosettaImportStatus.APPROVED, rosetta_experts)
315 >>> (subject, message) = pofile.importFromQueue(entry)
316
317 >>> print entry.status.name
318
319=== modified file 'lib/lp/translations/doc/poimport-potemplate-syntax-error.txt'
320--- lib/lp/translations/doc/poimport-potemplate-syntax-error.txt 2009-08-13 15:12:16 +0000
321+++ lib/lp/translations/doc/poimport-potemplate-syntax-error.txt 2009-11-13 18:41:14 +0000
322@@ -7,13 +7,15 @@
323
324 Here are some imports we need to get this test running.
325
326- >>> from canonical.launchpad.interfaces import IPersonSet
327+ >>> from canonical.launchpad.interfaces import (
328+ ... ILaunchpadCelebrities, IPersonSet)
329 >>> from lp.translations.interfaces.translationimportqueue import (
330 ... ITranslationImportQueue)
331 >>> from lp.translations.model.potemplate import POTemplateSubset
332 >>> import pytz
333 >>> UTC = pytz.timezone('UTC')
334 >>> translation_import_queue = getUtility(ITranslationImportQueue)
335+ >>> rosetta_experts = getUtility(ILaunchpadCelebrities).rosetta_experts
336
337 We need this for the Librarian to work properly.
338
339@@ -59,7 +61,7 @@
340 ... potemplate.path, potemplate_contents, published, person,
341 ... productseries=series, potemplate=potemplate)
342 >>> transaction.commit()
343- >>> entry.setStatus(RosettaImportStatus.APPROVED)
344+ >>> entry.setStatus(RosettaImportStatus.APPROVED, rosetta_experts)
345 >>> (subject, message) = potemplate.importFromQueue(entry)
346
347 The import failed.
348@@ -120,7 +122,7 @@
349 ... potemplate.path, potemplate_contents, published, person,
350 ... productseries=series, potemplate=potemplate)
351 >>> transaction.commit()
352- >>> entry.setStatus(RosettaImportStatus.APPROVED)
353+ >>> entry.setStatus(RosettaImportStatus.APPROVED, rosetta_experts)
354 >>> (subject, message) = potemplate.importFromQueue(entry)
355
356 The import failed.
357
358=== modified file 'lib/lp/translations/doc/poimport.txt'
359--- lib/lp/translations/doc/poimport.txt 2009-10-29 17:46:00 +0000
360+++ lib/lp/translations/doc/poimport.txt 2009-11-13 18:41:14 +0000
361@@ -21,6 +21,7 @@
362 >>> import datetime
363 >>> import pytz
364 >>> UTC = pytz.timezone('UTC')
365+ >>> rosetta_experts = getUtility(ILaunchpadCelebrities).rosetta_experts
366
367 We need this for the Librarian to work properly.
368
369@@ -125,7 +126,7 @@
370
371 The entry gets approved, so it can be imported.
372
373- >>> entry.setStatus(RosettaImportStatus.APPROVED)
374+ >>> entry.setStatus(RosettaImportStatus.APPROVED, rosetta_experts)
375
376 >>> import datetime
377 >>> import pytz
378@@ -201,7 +202,7 @@
379 The entry indicates what file it is to be imported to; importing it to
380 any other file would be an error.
381
382- >>> entry.setStatus(RosettaImportStatus.APPROVED)
383+ >>> entry.setStatus(RosettaImportStatus.APPROVED, rosetta_experts)
384 >>> from lp.translations.interfaces.potemplate import IPOTemplateSet
385 >>> other_product = getUtility(IProductSet).getByName('netapplet')
386 >>> other_productseries = other_product.getSeries('trunk')
387@@ -306,7 +307,7 @@
388
389 We must approve the entry to be able to import it.
390
391- >>> entry.setStatus(RosettaImportStatus.APPROVED)
392+ >>> entry.setStatus(RosettaImportStatus.APPROVED, rosetta_experts)
393
394 And we do the import.
395
396@@ -448,7 +449,7 @@
397 ... productseries=series, potemplate=potemplate,
398 ... pofile=sumerian_pofile)
399 >>> transaction.commit()
400- >>> warning_entry.setStatus(RosettaImportStatus.APPROVED)
401+ >>> warning_entry.setStatus(RosettaImportStatus.APPROVED, rosetta_experts)
402 >>> (subject, message) = sumerian_pofile.importFromQueue(warning_entry)
403
404 The warning is noted in the confirmation email. Note that this
405@@ -477,14 +478,13 @@
406 <BLANKLINE>
407 Line 12: We got a second header.
408
409- >>> warning_entry.setStatus(RosettaImportStatus.DELETED)
410+ >>> warning_entry.setStatus(RosettaImportStatus.DELETED, rosetta_experts)
411
412
413 === Import Without Errors ===
414
415 Now, let's import one without errors.
416
417- >>> rosetta_experts = getUtility(ILaunchpadCelebrities).rosetta_experts
418 >>> pofile_without_errors = r'''
419 ... msgid ""
420 ... msgstr ""
421@@ -522,7 +522,7 @@
422
423 We must approve the entry to be able to import it.
424
425- >>> entry.setStatus(RosettaImportStatus.APPROVED)
426+ >>> entry.setStatus(RosettaImportStatus.APPROVED, rosetta_experts)
427
428 At this point, the statistics note that we have 2 translations coming
429 from imported files (currentCount), and none updated in Launchpad
430@@ -608,7 +608,7 @@
431 The entry indicates what file it is to be imported to; importing it to
432 any other file would be an error.
433
434- >>> entry.setStatus(RosettaImportStatus.APPROVED)
435+ >>> entry.setStatus(RosettaImportStatus.APPROVED, rosetta_experts)
436 >>> other_pofile = potemplate.newPOFile('de')
437 >>> other_pofile.importFromQueue(entry)
438 Traceback (most recent call last):
439@@ -638,7 +638,7 @@
440 ... if (entry.status == RosettaImportStatus.IMPORTED or
441 ... entry.status == RosettaImportStatus.FAILED) and (
442 ... entry.productseries == series):
443- ... entry.setStatus(RosettaImportStatus.APPROVED)
444+ ... entry.setStatus(RosettaImportStatus.APPROVED, rosetta_experts)
445 ... syncUpdate(entry)
446 >>> transaction.commit()
447
448@@ -859,7 +859,7 @@
449 ... potemplate.path, potemplate_contents, True, potemplate.owner,
450 ... sourcepackagename=firefox_name, distroseries=warty,
451 ... potemplate=potemplate)
452- >>> entry.setStatus(RosettaImportStatus.APPROVED)
453+ >>> entry.setStatus(RosettaImportStatus.APPROVED, rosetta_experts)
454 >>> syncUpdate(entry)
455 >>> transaction.commit()
456
457@@ -941,7 +941,7 @@
458 >>> # Allow Librarian to see the change.
459 >>> transaction.commit()
460 >>> entry.pofile = firefox_dv
461- >>> entry.setStatus(RosettaImportStatus.APPROVED)
462+ >>> entry.setStatus(RosettaImportStatus.APPROVED, rosetta_experts)
463 >>> (subject, body) = firefox_dv.importFromQueue(entry, FakeLogger())
464 >>> flush_database_updates()
465 >>> print entry.status.name
466@@ -989,7 +989,7 @@
467 ... productseries=pofile.potemplate.productseries,
468 ... potemplate=pofile.potemplate, pofile=pofile)
469 >>> transaction.commit()
470- >>> entry.setStatus(RosettaImportStatus.APPROVED)
471+ >>> entry.setStatus(RosettaImportStatus.APPROVED, rosetta_experts)
472 >>> (subject, message) = pofile.importFromQueue(entry)
473
474 Import succeeds but no email is sent out.
475@@ -1008,7 +1008,7 @@
476 ... productseries=pofile.potemplate.productseries,
477 ... potemplate=pofile.potemplate, pofile=pofile)
478 >>> transaction.commit()
479- >>> entry.setStatus(RosettaImportStatus.APPROVED)
480+ >>> entry.setStatus(RosettaImportStatus.APPROVED, rosetta_experts)
481 >>> (subject, message) = pofile.importFromQueue(entry)
482
483 Import fails and email is sent out even though it's a published upload.
484@@ -1042,7 +1042,7 @@
485 ... 'lo.po', 'Invalid content', True, hermit,
486 ... pofile=pofile, potemplate=pofile.potemplate,
487 ... productseries=pofile.potemplate.productseries)
488- >>> entry.setStatus(RosettaImportStatus.APPROVED)
489+ >>> entry.setStatus(RosettaImportStatus.APPROVED, rosetta_experts)
490 >>> transaction.commit()
491
492 The import fails. The importer would like to send Kermit an email about
493
494=== modified file 'lib/lp/translations/doc/rosetta-karma.txt'
495--- lib/lp/translations/doc/rosetta-karma.txt 2009-07-23 17:49:31 +0000
496+++ lib/lp/translations/doc/rosetta-karma.txt 2009-11-13 18:41:14 +0000
497@@ -58,7 +58,7 @@
498
499 # Login as a rosetta expert to be able to change the import's status.
500 >>> login('carlos@canonical.com')
501- >>> entry.setStatus(RosettaImportStatus.APPROVED)
502+ >>> entry.setStatus(RosettaImportStatus.APPROVED, rosetta_experts)
503 >>> entry_id = entry.id
504
505 The file data is stored in the Librarian, so we have to commit the transaction
506@@ -90,7 +90,7 @@
507 ... potemplate.path, potemplate_contents, comes_from_upstream,
508 ... foo_bar, productseries=potemplate.productseries,
509 ... potemplate=potemplate)
510- >>> entry.setStatus(RosettaImportStatus.APPROVED)
511+ >>> entry.setStatus(RosettaImportStatus.APPROVED, rosetta_experts)
512 >>> entry_id = entry.id
513
514 The file data is stored in the Librarian, so we have to commit the transaction
515@@ -146,7 +146,7 @@
516 ... pofile.path, pofile_contents, comes_from_upstream,
517 ... rosetta_experts, productseries=potemplate.productseries,
518 ... potemplate=potemplate, pofile=pofile)
519- >>> entry.setStatus(RosettaImportStatus.APPROVED)
520+ >>> entry.setStatus(RosettaImportStatus.APPROVED, rosetta_experts)
521 >>> entry_id = entry.id
522
523 The file data is stored in the Librarian, so we have to commit the transaction
524@@ -173,7 +173,7 @@
525 ... pofile.path, pofile_contents, comes_from_upstream, foo_bar,
526 ... productseries=potemplate.productseries, potemplate=potemplate,
527 ... pofile=pofile)
528- >>> entry.setStatus(RosettaImportStatus.APPROVED)
529+ >>> entry.setStatus(RosettaImportStatus.APPROVED, rosetta_experts)
530 >>> entry_id = entry.id
531
532 The file data is stored in the Librarian, so we have to commit the transaction
533@@ -210,7 +210,7 @@
534 ... pofile.path, pofile_contents, not comes_from_upstream, foo_bar,
535 ... productseries=potemplate.productseries, potemplate=potemplate,
536 ... pofile=pofile)
537- >>> entry.setStatus(RosettaImportStatus.APPROVED)
538+ >>> entry.setStatus(RosettaImportStatus.APPROVED, rosetta_experts)
539 >>> entry_id = entry.id
540
541 The file data is stored in the Librarian, so we have to commit the transaction
542@@ -242,7 +242,7 @@
543 ... pofile.path, pofile_contents, not comes_from_upstream, foo_bar,
544 ... productseries=potemplate.productseries, potemplate=potemplate,
545 ... pofile=pofile)
546- >>> entry.setStatus(RosettaImportStatus.APPROVED)
547+ >>> entry.setStatus(RosettaImportStatus.APPROVED, rosetta_experts)
548 >>> entry_id = entry.id
549
550 The file data is stored in the Librarian, so we have to commit the transaction
551
552=== modified file 'lib/lp/translations/doc/rosetta-poimport-script.txt'
553--- lib/lp/translations/doc/rosetta-poimport-script.txt 2009-07-02 17:16:50 +0000
554+++ lib/lp/translations/doc/rosetta-poimport-script.txt 2009-11-13 18:41:14 +0000
555@@ -1,7 +1,8 @@
556 = PO import script =
557
558 >>> from lp.translations.model.potemplate import POTemplate
559- >>> from canonical.launchpad.interfaces import IPersonSet
560+ >>> from canonical.launchpad.interfaces import (
561+ ... ILaunchpadCelebrities, IPersonSet)
562 >>> from lp.translations.interfaces.translationimportqueue import (
563 ... ITranslationImportQueue,
564 ... RosettaImportStatus)
565@@ -9,6 +10,7 @@
566 >>> import datetime
567 >>> import pytz
568 >>> UTC = pytz.timezone('UTC')
569+ >>> rosetta_experts = getUtility(ILaunchpadCelebrities).rosetta_experts
570
571 Login as an admin to be able to do changes to the import queue.
572
573@@ -66,7 +68,7 @@
574 ... distroseries=pofile.potemplate.distroseries,
575 ... productseries=pofile.potemplate.productseries)
576 >>> entry.pofile = pofile
577- >>> entry.setStatus(RosettaImportStatus.APPROVED)
578+ >>> entry.setStatus(RosettaImportStatus.APPROVED, rosetta_experts)
579 >>> transaction.commit()
580 >>> translation_import_queue.countEntries()
581 1
582
583=== modified file 'lib/lp/translations/doc/translationimportqueue.txt'
584--- lib/lp/translations/doc/translationimportqueue.txt 2009-10-01 07:04:18 +0000
585+++ lib/lp/translations/doc/translationimportqueue.txt 2009-11-13 18:41:14 +0000
586@@ -136,7 +136,7 @@
587 we need to be logged in as an admin.
588
589 >>> login('carlos@canonical.com')
590- >>> entry.setStatus(RosettaImportStatus.IMPORTED)
591+ >>> entry.setStatus(RosettaImportStatus.IMPORTED, rosetta_experts)
592
593 The status change updates date_status_changed as well.
594
595@@ -202,7 +202,7 @@
596
597 But if that entry is imported, the guessing algorithm works.
598
599- >>> pot_entry.setStatus(RosettaImportStatus.IMPORTED)
600+ >>> pot_entry.setStatus(RosettaImportStatus.IMPORTED, rosetta_experts)
601 >>> guessed_pofile = po_sr_entry.getGuessedPOFile()
602 >>> guessed_pofile is None
603 False
604@@ -276,7 +276,7 @@
605
606 And set this entry as already imported.
607
608- >>> kde_pot_entry.setStatus(RosettaImportStatus.IMPORTED)
609+ >>> kde_pot_entry.setStatus(RosettaImportStatus.IMPORTED, rosetta_experts)
610 >>> flush_database_updates()
611
612 Let's attach a .po file from kde-i18n-es
613@@ -342,7 +342,7 @@
614
615 And set this entry as already imported.
616
617- >>> kde_pot_entry.setStatus(RosettaImportStatus.IMPORTED)
618+ >>> kde_pot_entry.setStatus(RosettaImportStatus.IMPORTED, rosetta_experts)
619 >>> flush_database_updates()
620
621 Let's attach a .po file from kde-i18n-es
622@@ -402,7 +402,7 @@
623
624 And set this entry as already imported.
625
626- >>> koffice_pot_entry.setStatus(RosettaImportStatus.IMPORTED)
627+ >>> koffice_pot_entry.setStatus(RosettaImportStatus.IMPORTED, rosetta_experts)
628 >>> flush_database_updates()
629
630 Let's attach a .po file from koffice-l10n
631@@ -500,7 +500,7 @@
632
633 And set this entry as already imported.
634
635- >>> adept_pot_entry.setStatus(RosettaImportStatus.IMPORTED)
636+ >>> adept_pot_entry.setStatus(RosettaImportStatus.IMPORTED, rosetta_experts)
637 >>> flush_database_updates()
638
639 Let's attach a .po file now.
640@@ -551,7 +551,7 @@
641
642 And set this entry as already imported.
643
644- >>> ktorrent_pot_entry.setStatus(RosettaImportStatus.IMPORTED)
645+ >>> ktorrent_pot_entry.setStatus(RosettaImportStatus.IMPORTED, rosetta_experts)
646 >>> flush_database_updates()
647
648 Let's attach a .po file now.
649@@ -604,7 +604,7 @@
650
651 And set this entry as already imported.
652
653- >>> zope_pot_entry.setStatus(RosettaImportStatus.IMPORTED)
654+ >>> zope_pot_entry.setStatus(RosettaImportStatus.IMPORTED, rosetta_experts)
655 >>> flush_database_updates()
656
657 Let's attach a .po file now.
658@@ -656,7 +656,7 @@
659
660 And set this entry as already imported.
661
662- >>> k3b_pot_entry.setStatus(RosettaImportStatus.IMPORTED)
663+ >>> k3b_pot_entry.setStatus(RosettaImportStatus.IMPORTED, rosetta_experts)
664 >>> flush_database_updates()
665
666 Let's attach a .po file now.
667@@ -706,7 +706,7 @@
668
669 And set this entry as already imported.
670
671- >>> k3b_pot_entry.setStatus(RosettaImportStatus.IMPORTED)
672+ >>> k3b_pot_entry.setStatus(RosettaImportStatus.IMPORTED, rosetta_experts)
673 >>> flush_database_updates()
674
675 Let's attach a .po file now.
676@@ -782,7 +782,7 @@
677
678 We need it blocked for this test.
679
680- >>> entry5.setStatus(RosettaImportStatus.BLOCKED)
681+ >>> entry5.setStatus(RosettaImportStatus.BLOCKED, rosetta_experts)
682
683 Let's see how many entries are blocked.
684
685@@ -873,7 +873,7 @@
686 For the third entry, we have one .pot file on that directory, which is already
687 in sample data.
688
689- >>> entry3.setStatus(RosettaImportStatus.NEEDS_REVIEW)
690+ >>> entry3.setStatus(RosettaImportStatus.NEEDS_REVIEW, rosetta_experts)
691 >>> entries = entry3.getTemplatesOnSameDirectory()
692 >>> entries.count()
693 1
694@@ -884,7 +884,7 @@
695
696 For the fourth entry, we have one.
697
698- >>> entry4.setStatus(RosettaImportStatus.NEEDS_REVIEW)
699+ >>> entry4.setStatus(RosettaImportStatus.NEEDS_REVIEW, rosetta_experts)
700 >>> entries = entry4.getTemplatesOnSameDirectory()
701 >>> entries.count()
702 1
703@@ -898,7 +898,7 @@
704 obviously, we are not returning it as being at the same directory as it makes
705 no sense at all.
706
707- >>> entry5.setStatus(RosettaImportStatus.NEEDS_REVIEW)
708+ >>> entry5.setStatus(RosettaImportStatus.NEEDS_REVIEW, rosetta_experts)
709 >>> entries = entry5.getTemplatesOnSameDirectory()
710 >>> entries.count()
711 0
712@@ -967,10 +967,17 @@
713 NEEDS_REVIEW
714
715 Now we approve these entries (we need to be Rosetta administrator to do this).
716+We also need to set an import target.
717
718 >>> login('carlos@canonical.com')
719- >>> entry1.setStatus(RosettaImportStatus.APPROVED)
720- >>> entry2.setStatus(RosettaImportStatus.APPROVED)
721+ >>> entry1.potemplate = factory.makePOTemplate(
722+ ... distroseries=hoary_distroseries,
723+ ... sourcepackagename=evolution_sourcepackagename)
724+ >>> entry1.pofile = factory.makePOFile('sr', potemplate=entry1.potemplate)
725+ >>> entry1.setStatus(RosettaImportStatus.APPROVED, rosetta_experts)
726+ >>> entry2.potemplate = factory.makePOTemplate(
727+ ... productseries=productseries)
728+ >>> entry2.setStatus(RosettaImportStatus.APPROVED, rosetta_experts)
729
730 >>> flush_database_updates()
731 >>> translationimportqueue = getUtility(ITranslationImportQueue)
732@@ -1100,8 +1107,8 @@
733
734 >>> queue = getUtility(ITranslationImportQueue)
735 >>> print_queue_entries(queue)
736- hoary evolution | None | po/sr.po
737- firefox | None | foo/bar.pot
738+ hoary evolution | generic-string1 | po/sr.po
739+ firefox | generic-string3 | foo/bar.pot
740 evolution | evolution-2.2-test | po/evolution-2.2-test.pot
741 evolution | evolution-2.2-test | po/pt_BR.po
742 firefox | None | foo/bar.po
743@@ -1121,8 +1128,8 @@
744 And those new entries in the queue appear in the list.
745
746 >>> print_queue_entries(queue)
747- hoary evolution | None | po/sr.po
748- firefox | None | foo/bar.pot
749+ hoary evolution | generic-string1 | po/sr.po
750+ firefox | generic-string3 | foo/bar.pot
751 evolution | evolution-2.2-test | po/evolution-2.2-test.pot
752 evolution | evolution-2.2-test | po/pt_BR.po
753 firefox | None | foo/bar.po
754@@ -1320,7 +1327,8 @@
755 >>> entry.import_into is None
756 True
757
758- >>> entry.setStatus(RosettaImportStatus.APPROVED)
759+Set the entry to approved, which is only possible if we don't use setStatus.
760+ >>> removeSecurityProxy(entry).status = RosettaImportStatus.APPROVED
761
762 >>> import logging
763 >>> from canonical.launchpad.scripts import FakeLogger
764@@ -1355,7 +1363,8 @@
765 >>> entry.import_into is None
766 True
767
768- >>> entry.setStatus(RosettaImportStatus.APPROVED)
769+Set the entry to approved, which is only possible if we don't use setStatus.
770+ >>> removeSecurityProxy(entry).status = RosettaImportStatus.APPROVED
771
772 >>> script = TranslationsImport('poimport', test_args=[])
773 >>> script.logger.setLevel(logging.FATAL)
774@@ -1382,11 +1391,13 @@
775 ... distro = distroset[distro_name]
776 ... series = distro.getSeries(series_name)
777 ... package = packageset['pmount']
778+ ... template = factory.makePOTemplate(distroseries=series,
779+ ... sourcepackagename=package)
780 ... # In a completely arbitrary move, we make all import requests for
781 ... # distro series imported.
782 ... return translationimportqueue.addOrUpdateEntry('messages.pot',
783 ... 'dummy file', True, rosetta_experts, distroseries=series,
784- ... sourcepackagename=package)
785+ ... sourcepackagename=package, potemplate=template)
786
787 >>> def create_product_request(product_name, template_name):
788 ... """Enqueue an import request for given product and template."""
789@@ -1403,19 +1414,19 @@
790
791 >>> # Populate import queue with wild mix of requests.
792 >>> entry = create_product_request('evolution', 'evolution-2.2')
793- >>> entry.setStatus(RosettaImportStatus.APPROVED)
794+ >>> entry.setStatus(RosettaImportStatus.APPROVED, rosetta_experts)
795 >>> entry = create_distro_request('debian', 'sarge')
796- >>> entry.setStatus(RosettaImportStatus.NEEDS_REVIEW)
797+ >>> entry.setStatus(RosettaImportStatus.NEEDS_REVIEW, rosetta_experts)
798 >>> entry = create_product_request('alsa-utils', 'alsa-utils')
799- >>> entry.setStatus(RosettaImportStatus.FAILED)
800+ >>> entry.setStatus(RosettaImportStatus.FAILED, rosetta_experts)
801 >>> entry = create_distro_request('ubuntu', 'grumpy')
802- >>> entry.setStatus(RosettaImportStatus.BLOCKED)
803+ >>> entry.setStatus(RosettaImportStatus.BLOCKED, rosetta_experts)
804 >>> entry = create_distro_request('kubuntu', 'krunch')
805- >>> entry.setStatus(RosettaImportStatus.APPROVED)
806+ >>> entry.setStatus(RosettaImportStatus.APPROVED, rosetta_experts)
807 >>> entry = create_product_request('evolution', 'evolution-2.2-test')
808- >>> entry.setStatus(RosettaImportStatus.IMPORTED)
809+ >>> entry.setStatus(RosettaImportStatus.IMPORTED, rosetta_experts)
810 >>> entry = create_distro_request('debian', 'woody')
811- >>> entry.setStatus(RosettaImportStatus.NEEDS_REVIEW)
812+ >>> entry.setStatus(RosettaImportStatus.NEEDS_REVIEW, rosetta_experts)
813 >>> flush_database_updates()
814
815 TranslationImportQueue.getRequestTargets first lists distro series
816@@ -1479,7 +1490,7 @@
817 >>> entry = translationimportqueue.addOrUpdateEntry(
818 ... u'po/nl.po', 'hoi', True, rosetta_experts,
819 ... productseries=evolution_productseries)
820- >>> entry.setStatus(RosettaImportStatus.DELETED)
821+ >>> entry.setStatus(RosettaImportStatus.DELETED, rosetta_experts)
822
823 The entry stays on the queue long enough to make it a candidate for
824 purging.
825@@ -1507,7 +1518,7 @@
826 >>> entry = translationimportqueue.addOrUpdateEntry(
827 ... u'po/nl.po', 'hoi', True, rosetta_experts,
828 ... productseries=evolution_productseries)
829- >>> entry.setStatus(RosettaImportStatus.IMPORTED)
830+ >>> entry.setStatus(RosettaImportStatus.IMPORTED, rosetta_experts)
831 >>> print_queue_entries(translationimportqueue)
832 evolution | None | po/nl.po
833
834
835=== modified file 'lib/lp/translations/interfaces/translationimportqueue.py'
836--- lib/lp/translations/interfaces/translationimportqueue.py 2009-10-09 13:20:38 +0000
837+++ lib/lp/translations/interfaces/translationimportqueue.py 2009-11-13 18:41:14 +0000
838@@ -6,6 +6,7 @@
839 from zope.interface import Interface, Attribute
840 from zope.schema import (
841 Bool, Choice, Datetime, Field, Int, Object, Text, TextLine)
842+from zope.security.interfaces import Unauthorized
843 from lazr.enum import DBEnumeratedType, DBItem, EnumeratedType, Item
844
845 from canonical.launchpad import _
846@@ -18,11 +19,12 @@
847
848 from lazr.restful.interface import copy_field
849 from lazr.restful.fields import Reference
850-from lazr.restful.declarations import (
851+from lazr.restful.declarations import (call_with,
852 collection_default_content, exported, export_as_webservice_collection,
853- export_as_webservice_entry, export_read_operation, operation_parameters,
854- operation_returns_entry, operation_returns_collection_of)
855-
856+ export_as_webservice_entry, export_read_operation,
857+ export_write_operation, operation_parameters,
858+ operation_returns_entry, operation_returns_collection_of,
859+ REQUEST_USER, webservice_error)
860 from lp.translations.interfaces.translationcommonformat import (
861 TranslationImportExportBaseException)
862
863@@ -37,6 +39,7 @@
864 'RosettaImportStatus',
865 'SpecialTranslationImportTargetFilter',
866 'TranslationFileType',
867+ 'UserCannotSetTranslationImportStatus',
868 ]
869
870
871@@ -46,6 +49,15 @@
872 conflicts with existing entries."""
873
874
875+class UserCannotSetTranslationImportStatus(Unauthorized):
876+ """User not permitted to change status.
877+
878+ Raised when a user tries to transition to a new status who doesn't
879+ have the necessary permissions.
880+ """
881+ webservice_error(401) # HTTP Error: 'Unauthorized'
882+
883+
884 class RosettaImportStatus(DBEnumeratedType):
885 """Rosetta Import Status
886
887@@ -254,10 +266,27 @@
888 required=False,
889 readonly=True))
890
891- def setStatus(status):
892- """Set status.
893-
894- :param status: new status to set.
895+ def isUbuntuAndIsUserTranslationGroupOwner(self, user):
896+ """Check for special Ubuntu Translation Group.
897+
898+ Return true if the entry is targeted to Ubuntu and the user is in
899+ the team owning the Ubuntu translation group.
900+ """
901+
902+ def isUserUploaderOrOwner(user):
903+ """Check for entry uploader or series owner."""
904+
905+ def canSetStatus(new_status, user):
906+ """Check if the user can set this new status."""
907+
908+ @call_with(user=REQUEST_USER)
909+ @operation_parameters(new_status=copy_field(status))
910+ @export_write_operation()
911+ def setStatus(new_status, user):
912+ """Transition to a new status if possible.
913+
914+ :param new_status: Status to transition to.
915+ :param user: The user that is doing the transition.
916 """
917
918 def setErrorOutput(output):
919
920=== modified file 'lib/lp/translations/model/pofile.py'
921--- lib/lp/translations/model/pofile.py 2009-10-29 12:36:32 +0000
922+++ lib/lp/translations/model/pofile.py 2009-11-13 18:41:14 +0000
923@@ -1140,10 +1140,12 @@
924 subject = 'Translation import - %s - %s' % (
925 self.language.displayname, self.potemplate.displayname)
926
927+ rosetta_experts = getUtility(ILaunchpadCelebrities).rosetta_experts
928 if import_rejected:
929 # There were no imports at all and the user needs to review that
930 # file, we tag it as FAILED.
931- entry_to_import.setStatus(RosettaImportStatus.FAILED)
932+ entry_to_import.setStatus(RosettaImportStatus.FAILED,
933+ rosetta_experts)
934 else:
935 if (entry_to_import.is_published and
936 not needs_notification_for_imported):
937@@ -1152,7 +1154,8 @@
938 # are needed.
939 subject = None
940
941- entry_to_import.setStatus(RosettaImportStatus.IMPORTED)
942+ entry_to_import.setStatus(RosettaImportStatus.IMPORTED,
943+ rosetta_experts)
944 # Assign karma to the importer if this is not an automatic import
945 # (all automatic imports come from the rosetta expert user) and
946 # comes from upstream.
947
948=== modified file 'lib/lp/translations/model/potemplate.py'
949--- lib/lp/translations/model/potemplate.py 2009-11-06 21:06:38 +0000
950+++ lib/lp/translations/model/potemplate.py 2009-11-13 18:41:14 +0000
951@@ -891,6 +891,7 @@
952
953 translation_importer = getUtility(ITranslationImporter)
954
955+ rosetta_experts = getUtility(ILaunchpadCelebrities).rosetta_experts
956 subject = 'Translation template import - %s' % self.displayname
957 template_mail = 'poimport-template-confirmation.txt'
958 errors, warnings = None, None
959@@ -908,7 +909,8 @@
960 template_mail = 'poimport-bad-encoding.txt'
961 else:
962 template_mail = 'poimport-syntax-error.txt'
963- entry_to_import.setStatus(RosettaImportStatus.FAILED)
964+ entry_to_import.setStatus(RosettaImportStatus.FAILED,
965+ rosetta_experts)
966 error_text = str(exception)
967 entry_to_import.setErrorOutput(error_text)
968 else:
969@@ -926,7 +928,8 @@
970 entry_to_import.addWarningOutput(replacements['warnings'])
971
972 if entry_to_import.status != RosettaImportStatus.FAILED:
973- entry_to_import.setStatus(RosettaImportStatus.IMPORTED)
974+ entry_to_import.setStatus(RosettaImportStatus.IMPORTED,
975+ rosetta_experts)
976
977 # Assign karma to the importer if this is not an automatic import
978 # (all automatic imports come from the rosetta expert team).
979
980=== modified file 'lib/lp/translations/model/translationbranchapprover.py'
981--- lib/lp/translations/model/translationbranchapprover.py 2009-10-10 19:37:03 +0000
982+++ lib/lp/translations/model/translationbranchapprover.py 2009-11-13 18:41:14 +0000
983@@ -11,6 +11,7 @@
984
985 from zope.component import getUtility
986
987+from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
988 from canonical.launchpad.validators.name import sanitize_name
989 from lp.translations.interfaces.potemplate import IPOTemplateSet
990 from lp.translations.interfaces.translationimportqueue import (
991@@ -165,6 +166,7 @@
992 # Approve the entry
993 entry.potemplate = potemplate
994 if entry.status == RosettaImportStatus.NEEDS_REVIEW:
995- entry.setStatus(RosettaImportStatus.APPROVED)
996+ entry.setStatus(RosettaImportStatus.APPROVED,
997+ getUtility(ILaunchpadCelebrities).rosetta_experts)
998 return entry
999
1000
1001=== modified file 'lib/lp/translations/model/translationimportqueue.py'
1002--- lib/lp/translations/model/translationimportqueue.py 2009-10-22 07:32:02 +0000
1003+++ lib/lp/translations/model/translationimportqueue.py 2009-11-13 18:41:14 +0000
1004@@ -56,11 +56,14 @@
1005 ITranslationImportQueueEntry,
1006 RosettaImportStatus,
1007 SpecialTranslationImportTargetFilter,
1008- TranslationImportQueueConflictError)
1009+ TranslationImportQueueConflictError,
1010+ UserCannotSetTranslationImportStatus)
1011 from lp.translations.interfaces.potemplate import IPOTemplate
1012 from lp.translations.interfaces.translations import TranslationConstants
1013 from lp.translations.utilities.gettext_po_importer import (
1014 GettextPOImporter)
1015+from lp.translations.utilities.permission_helpers import (
1016+ is_admin_or_rosetta_expert)
1017 from canonical.librarian.interfaces import ILibrarianClient
1018
1019
1020@@ -271,9 +274,58 @@
1021 distroseries=self.distroseries,
1022 sourcepackagename=self.sourcepackagename)
1023
1024- def setStatus(self, status):
1025- """See `ITranslationImportQueueEntry`."""
1026- self.status = status
1027+ def isUbuntuAndIsUserTranslationGroupOwner(self, user):
1028+ """See `ITranslationImportQueueEntry`."""
1029+ # As a special case, the Ubuntu translation group owners can
1030+ # manage Ubuntu uploads.
1031+ if self.is_targeted_to_ubuntu:
1032+ group = self.distroseries.distribution.translationgroup
1033+ if group is not None and user.inTeam(group.owner):
1034+ return True
1035+ return False
1036+
1037+ def isUserUploaderOrOwner(self, user):
1038+ """See `ITranslationImportQueueEntry`."""
1039+ if user.inTeam(self.importer):
1040+ return True
1041+ if self.productseries is not None:
1042+ return user.inTeam(self.productseries.product.owner)
1043+ if self.distroseries is not None:
1044+ return user.inTeam(self.distroseries.distribution.owner)
1045+ return False
1046+
1047+ def canSetStatus(self, new_status, user):
1048+ """See `ITranslationImportQueueEntry`."""
1049+ if new_status == self.status:
1050+ # Leaving status as it is is always allowed.
1051+ return True
1052+ if user is None:
1053+ # Anonymous user cannot do anything.
1054+ return False
1055+ can_admin = (is_admin_or_rosetta_expert(user) or
1056+ self.isUbuntuAndIsUserTranslationGroupOwner(user))
1057+ if (new_status == RosettaImportStatus.APPROVED and
1058+ not (self.import_into is not None and can_admin)):
1059+ # Only administrators are able to set the APPROVED status, and
1060+ # that's only possible if we know where to import it
1061+ # (import_into not None).
1062+ return False
1063+ if (new_status == RosettaImportStatus.BLOCKED and not can_admin):
1064+ # Only administrators are able to set an entry to BLOCKED.
1065+ return False
1066+ if (new_status in (RosettaImportStatus.FAILED,
1067+ RosettaImportStatus.IMPORTED)
1068+ and not is_admin_or_rosetta_expert(user)):
1069+ # Only scripts set these statuses and they report as a rosetta
1070+ # expert.
1071+ return False
1072+ return (self.isUserUploaderOrOwner(user) or can_admin)
1073+
1074+ def setStatus(self, new_status, user):
1075+ """See `ITranslationImportQueueEntry`."""
1076+ if not self.canSetStatus(new_status, user):
1077+ raise UserCannotSetTranslationImportStatus()
1078+ self.status = new_status
1079 self.date_status_changed = UTC_NOW
1080
1081 def setErrorOutput(self, output):
1082@@ -473,7 +525,8 @@
1083 if guessed_language is None:
1084 # Custom language code says to ignore imports with this language
1085 # code.
1086- self.setStatus(RosettaImportStatus.DELETED)
1087+ self.setStatus(RosettaImportStatus.DELETED,
1088+ getUtility(ILaunchpadCelebrities).rosetta_experts)
1089 return None
1090 elif guessed_language == '':
1091 # We don't recognize this as a translation file with a name
1092@@ -877,7 +930,7 @@
1093 # We got an update for this entry. If the previous import is
1094 # deleted or failed or was already imported we should retry
1095 # the import now, just in case it can be imported now.
1096- entry.setStatus(RosettaImportStatus.NEEDS_REVIEW)
1097+ entry.setStatus(RosettaImportStatus.NEEDS_REVIEW, importer)
1098
1099 entry.date_status_changed = UTC_NOW
1100 entry.format = format
1101@@ -1130,7 +1183,8 @@
1102
1103 # Already know where it should be imported. The entry is approved
1104 # automatically.
1105- entry.setStatus(RosettaImportStatus.APPROVED)
1106+ entry.setStatus(RosettaImportStatus.APPROVED,
1107+ getUtility(ILaunchpadCelebrities).rosetta_experts)
1108
1109 if txn is not None:
1110 txn.commit()
1111@@ -1165,7 +1219,9 @@
1112 if has_templates and not has_templates_unblocked:
1113 # All templates on the same directory as this entry are
1114 # blocked, so we can block it too.
1115- entry.setStatus(RosettaImportStatus.BLOCKED)
1116+ entry.setStatus(
1117+ RosettaImportStatus.BLOCKED,
1118+ getUtility(ILaunchpadCelebrities).rosetta_experts)
1119 num_blocked += 1
1120 if txn is not None:
1121 txn.commit()
1122
1123=== modified file 'lib/lp/translations/scripts/po_import.py'
1124--- lib/lp/translations/scripts/po_import.py 2009-10-20 05:17:01 +0000
1125+++ lib/lp/translations/scripts/po_import.py 2009-11-13 18:41:14 +0000
1126@@ -73,7 +73,8 @@
1127 def _registerFailure(self, entry, reason, traceback=False, abort=False):
1128 """Note that a queue entry is unusable in some way."""
1129 reason_text = unicode(reason)
1130- entry.setStatus(RosettaImportStatus.FAILED)
1131+ entry.setStatus(RosettaImportStatus.FAILED,
1132+ getUtility(ILaunchpadCelebrities).rosetta_experts)
1133 entry.setErrorOutput(reason_text)
1134
1135 if abort:
1136
1137=== modified file 'lib/lp/translations/stories/webservice/xx-translationimportqueue.txt'
1138--- lib/lp/translations/stories/webservice/xx-translationimportqueue.txt 2009-07-01 20:45:39 +0000
1139+++ lib/lp/translations/stories/webservice/xx-translationimportqueue.txt 2009-11-13 18:41:14 +0000
1140@@ -114,8 +114,23 @@
1141 ...
1142 status: You tried to modify a read-only attribute.
1143
1144-An attempt to do so will leave the entry's status unchanged.
1145+But you can set the status using the setStatus method.
1146+
1147+ >>> print webservice.named_post(
1148+ ... first_entry, 'setStatus', {}, new_status='Approved')
1149+ HTTP/1.1 200 Ok
1150+ ...
1151+
1152+The entry's status is changed.
1153
1154 >>> queue = webservice.get("/+imports").jsonBody()
1155 >>> print queue['entries'][0]['status']
1156- Imported
1157+ Approved
1158+
1159+Unprivileged users cannot change the status.
1160+
1161+ >>> print user_webservice.named_post(
1162+ ... first_entry, 'setStatus', {}, new_status='Deleted')
1163+ HTTP/1.1 401 Unauthorized
1164+ ...
1165+
1166
1167=== modified file 'lib/lp/translations/tests/test_autoapproval.py'
1168--- lib/lp/translations/tests/test_autoapproval.py 2009-10-29 17:46:00 +0000
1169+++ lib/lp/translations/tests/test_autoapproval.py 2009-11-13 18:41:14 +0000
1170@@ -13,6 +13,9 @@
1171 import transaction
1172 import unittest
1173
1174+from zope.component import getUtility
1175+
1176+from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
1177 from canonical.launchpad.interfaces.lpstorm import IMasterStore
1178
1179 from lp.registry.interfaces.distroseries import DistroSeriesStatus
1180@@ -749,7 +752,8 @@
1181
1182 def _setStatus(self, entry, status, when=None):
1183 """Simulate status on queue entry having been set at a given time."""
1184- entry.setStatus(status)
1185+ entry.setStatus(status,
1186+ getUtility(ILaunchpadCelebrities).rosetta_experts)
1187 if when is not None:
1188 entry.date_status_changed = when
1189 entry.syncUpdate()
1190@@ -760,6 +764,8 @@
1191 # are.
1192 one_year_ago = datetime.now(UTC) - timedelta(days=366)
1193 entry = self._makeProductEntry()
1194+ entry.potemplate = (
1195+ self.factory.makePOTemplate(productseries=entry.productseries))
1196 entry_id = entry.id
1197
1198 self._setStatus(entry, RosettaImportStatus.APPROVED, one_year_ago)
1199
1200=== modified file 'lib/lp/translations/tests/test_translationbranchapprover.py'
1201--- lib/lp/translations/tests/test_translationbranchapprover.py 2009-10-19 10:57:28 +0000
1202+++ lib/lp/translations/tests/test_translationbranchapprover.py 2009-11-13 18:41:14 +0000
1203@@ -8,6 +8,7 @@
1204 from unittest import TestLoader
1205 from zope.component import getUtility
1206
1207+from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
1208 from canonical.launchpad.validators.name import valid_name
1209 from canonical.testing import LaunchpadZopelessLayer
1210 from lp.translations.interfaces.translationimportqueue import (
1211@@ -213,7 +214,8 @@
1212 RosettaImportStatus.BLOCKED,
1213 )
1214 for status in not_approve_status:
1215- entry.setStatus(status)
1216+ entry.setStatus(
1217+ status, getUtility(ILaunchpadCelebrities).rosetta_experts)
1218 approver = self._create_approver(pot_path)
1219 approver.approve(entry)
1220 self.assertEqual(status, entry.status)
1221
1222=== added file 'lib/lp/translations/tests/test_translationimportqueue.py'
1223--- lib/lp/translations/tests/test_translationimportqueue.py 1970-01-01 00:00:00 +0000
1224+++ lib/lp/translations/tests/test_translationimportqueue.py 2009-11-13 18:41:14 +0000
1225@@ -0,0 +1,125 @@
1226+# Copyright 2009 Canonical Ltd. This software is licensed under the
1227+# GNU Affero General Public License version 3 (see the file LICENSE).
1228+
1229+# pylint: disable-msg=C0102
1230+
1231+__metaclass__ = type
1232+
1233+import unittest
1234+
1235+from zope.component import getUtility
1236+
1237+from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
1238+from lp.translations.interfaces.translationimportqueue import (
1239+ ITranslationImportQueue, RosettaImportStatus)
1240+
1241+from lp.testing import TestCaseWithFactory
1242+from canonical.testing import LaunchpadZopelessLayer
1243+
1244+
1245+class TestTranslationImpportQueueEntryStatus(TestCaseWithFactory):
1246+ """Test handling of the status of a queue entry."""
1247+
1248+ layer = LaunchpadZopelessLayer
1249+
1250+ def setUp(self):
1251+ """Set up context to test in."""
1252+ super(TestTranslationImpportQueueEntryStatus, self).setUp()
1253+
1254+ self.queue = getUtility(ITranslationImportQueue)
1255+ self.rosetta_experts = (
1256+ getUtility(ILaunchpadCelebrities).rosetta_experts)
1257+ self.productseries = self.factory.makeProductSeries()
1258+ self.uploaderperson = self.factory.makePerson()
1259+ self.potemplate = self.factory.makePOTemplate(
1260+ productseries=self.productseries)
1261+ self.entry = self.queue.addOrUpdateEntry(
1262+ 'demo.pot', '#demo', False, self.uploaderperson,
1263+ productseries=self.productseries, potemplate=self.potemplate)
1264+
1265+ def _assertCanSetStatus(self, user, entry, expected_list):
1266+ # Helper to check for all statuses.
1267+ # Could iterate RosettaImportStatus.items but listing them here
1268+ # explicitely is better to read. They are sorted alphabetically.
1269+ possible_statuses = [
1270+ RosettaImportStatus.APPROVED,
1271+ RosettaImportStatus.BLOCKED,
1272+ RosettaImportStatus.DELETED,
1273+ RosettaImportStatus.FAILED,
1274+ RosettaImportStatus.IMPORTED,
1275+ RosettaImportStatus.NEEDS_REVIEW,
1276+ ]
1277+ # Do *not* use assertContentEqual here, as the order matters.
1278+ self.assertEqual(expected_list,
1279+ [entry.canSetStatus(status, user)
1280+ for status in possible_statuses])
1281+
1282+ def test_canSetStatus_non_admin(self):
1283+ # A non-privileged users cannot set any status except for retaining
1284+ # the current status of an entry.
1285+ some_user = self.factory.makePerson()
1286+ self._assertCanSetStatus(some_user, self.entry,
1287+ # A B D F I NR
1288+ [False, False, False, False, False, True])
1289+ self.entry.setStatus(
1290+ RosettaImportStatus.DELETED, self.rosetta_experts)
1291+ self._assertCanSetStatus(some_user, self.entry,
1292+ # A B D F I NR
1293+ [False, False, True, False, False, False])
1294+
1295+ def test_canSetStatus_rosetta_expert(self):
1296+ # Rosetta experts are all-powerful, didn't you know that?
1297+ self._assertCanSetStatus(self.rosetta_experts, self.entry,
1298+ # A B D F I NR
1299+ [True, True, True, True, True, True])
1300+
1301+ def test_canSetStatus_rosetta_expert_no_target(self):
1302+ # If the entry has no import target set, even Rosetta experts
1303+ # cannot set it to approved.
1304+ self.entry.potemplate = None
1305+ self._assertCanSetStatus(self.rosetta_experts, self.entry,
1306+ # A B D F I NR
1307+ [False, True, True, True, True, True])
1308+
1309+ def test_canSetStatus_uploader(self):
1310+ # The uploader can set some statuses.
1311+ self._assertCanSetStatus(self.uploaderperson, self.entry,
1312+ # A B D F I NR
1313+ [False, False, True, False, False, True])
1314+
1315+ def test_canSetStatus_owner(self):
1316+ # The owner gets the same permissions.
1317+ self._assertCanSetStatus(self.productseries.product.owner, self.entry,
1318+ # A B D F I NR
1319+ [False, False, True, False, False, True])
1320+
1321+ def _setUpUbuntu(self):
1322+ self.ubuntu = getUtility(ILaunchpadCelebrities).ubuntu
1323+ self.ubuntu_group_owner = self.factory.makePerson()
1324+ self.ubuntu.translationgroup = (
1325+ self.factory.makeTranslationGroup(self.ubuntu_group_owner))
1326+
1327+ def test_canSetStatus_ubuntu_translation_group(self):
1328+ # Owners of the Ubuntu translation Groups can set entries
1329+ # that are targeted to Ubuntu.
1330+ self._setUpUbuntu()
1331+ ubuntu_entry = self.queue.addOrUpdateEntry(
1332+ 'demo.pot', '#demo', False, self.uploaderperson,
1333+ distroseries=self.factory.makeDistroRelease(self.ubuntu),
1334+ sourcepackagename=self.factory.makeSourcePackageName(),
1335+ potemplate=self.potemplate)
1336+ self._assertCanSetStatus(self.ubuntu_group_owner, ubuntu_entry,
1337+ # A B D F I NR
1338+ [True, True, True, False, False, True])
1339+
1340+ def test_canSetStatus_ubuntu_translation_group_not_ubuntu(self):
1341+ # Outside of Ubuntu, owners of the Ubuntu translation Groups have no
1342+ # powers.
1343+ self._setUpUbuntu()
1344+ self._assertCanSetStatus(self.ubuntu_group_owner, self.entry,
1345+ # A B D F I NR
1346+ [False, False, False, False, False, True])
1347+
1348+
1349+def test_suite():
1350+ return unittest.TestLoader().loadTestsFromName(__name__)
1351
1352=== added file 'lib/lp/translations/utilities/permission_helpers.py'
1353--- lib/lp/translations/utilities/permission_helpers.py 1970-01-01 00:00:00 +0000
1354+++ lib/lp/translations/utilities/permission_helpers.py 2009-11-13 18:41:14 +0000
1355@@ -0,0 +1,21 @@
1356+# Copyright 2009 Canonical Ltd. This software is licensed under the
1357+# GNU Affero General Public License version 3 (see the file LICENSE).
1358+
1359+"""Helpful functions to enforce permissions."""
1360+
1361+__metaclass__ = type
1362+
1363+__all__ = [
1364+ 'is_admin_or_rosetta_expert',
1365+ ]
1366+
1367+from zope.component import getUtility
1368+
1369+from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
1370+
1371+
1372+def is_admin_or_rosetta_expert(user):
1373+ """Check if the user is a Launchpad admin or a Rosettta expert."""
1374+ celebrities = getUtility(ILaunchpadCelebrities)
1375+ return (user.inTeam(celebrities.admin) or
1376+ user.inTeam(celebrities.rosetta_experts))
1377
1378=== modified file 'lib/lp/translations/utilities/tests/helpers.py'
1379--- lib/lp/translations/utilities/tests/helpers.py 2009-07-17 00:26:05 +0000
1380+++ lib/lp/translations/utilities/tests/helpers.py 2009-11-13 18:41:14 +0000
1381@@ -15,6 +15,7 @@
1382
1383 from zope.component import getUtility
1384
1385+from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
1386 from lp.translations.interfaces.translationimportqueue import (
1387 ITranslationImportQueue, RosettaImportStatus)
1388 from canonical.launchpad.scripts import FakeLogger
1389@@ -64,7 +65,8 @@
1390 else:
1391 commit()
1392
1393- entry.setStatus(RosettaImportStatus.APPROVED)
1394+ entry.setStatus(RosettaImportStatus.APPROVED,
1395+ getUtility(ILaunchpadCelebrities).rosetta_experts)
1396 (subject, body) = target.importFromQueue(entry, FakeLogger())
1397 return entry
1398
1399
1400=== modified file 'lib/lp/translations/utilities/tests/test_xpi_import.py'
1401--- lib/lp/translations/utilities/tests/test_xpi_import.py 2009-08-13 15:12:16 +0000
1402+++ lib/lp/translations/utilities/tests/test_xpi_import.py 2009-11-13 18:41:14 +0000
1403@@ -9,6 +9,7 @@
1404
1405 from zope.component import getUtility
1406
1407+from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
1408 from lp.registry.interfaces.person import IPersonSet
1409 from lp.registry.interfaces.product import IProductSet
1410 from lp.translations.interfaces.potemplate import IPOTemplateSet
1411@@ -204,7 +205,8 @@
1412 ).count()
1413
1414 # Force the entry to be imported again:
1415- entry.setStatus(RosettaImportStatus.APPROVED)
1416+ entry.setStatus(RosettaImportStatus.APPROVED,
1417+ getUtility(ILaunchpadCelebrities).rosetta_experts)
1418 # Now, we tell the PO template to import from the file data it has.
1419 (subject, body) = self.firefox_template.importFromQueue(entry)
1420
1421
1422=== modified file 'lib/lp/translations/utilities/tests/test_xpi_po_exporter.py'
1423--- lib/lp/translations/utilities/tests/test_xpi_po_exporter.py 2009-08-13 15:12:16 +0000
1424+++ lib/lp/translations/utilities/tests/test_xpi_po_exporter.py 2009-11-13 18:41:14 +0000
1425@@ -10,6 +10,7 @@
1426
1427 from canonical.database.sqlbase import commit
1428 from canonical.launchpad.ftests import sync
1429+from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
1430 from lp.registry.interfaces.person import IPersonSet
1431 from lp.registry.interfaces.product import IProductSet
1432 from lp.translations.interfaces.potemplate import IPOTemplateSet
1433@@ -78,7 +79,8 @@
1434 potemplate=self.firefox_template)
1435
1436 # We must approve the entry to be able to import it.
1437- entry.setStatus(RosettaImportStatus.APPROVED)
1438+ entry.setStatus(RosettaImportStatus.APPROVED,
1439+ getUtility(ILaunchpadCelebrities).rosetta_experts)
1440 # The file data is stored in the Librarian, so we have to commit the
1441 # transaction to make sure it's stored properly.
1442 commit()