Merge lp:~henninge/launchpad/devel-bug-597539-translationmessage-pofile into lp:launchpad

Proposed by Henning Eggers
Status: Merged
Approved by: Henning Eggers
Approved revision: no longer in the source branch.
Merged at revision: 11504
Proposed branch: lp:~henninge/launchpad/devel-bug-597539-translationmessage-pofile
Merge into: lp:launchpad
Diff against target: 1384 lines (+279/-572)
18 files modified
lib/lp/translations/browser/tests/translationmessage-views.txt (+6/-34)
lib/lp/translations/browser/translationmessage.py (+69/-42)
lib/lp/translations/doc/gettext-check-messages.txt (+9/-44)
lib/lp/translations/doc/pofile.txt (+1/-1)
lib/lp/translations/doc/potmsgset.txt (+5/-3)
lib/lp/translations/doc/remove-upstream-translations-script.txt (+0/-142)
lib/lp/translations/interfaces/pofile.py (+7/-0)
lib/lp/translations/interfaces/translationmessage.py (+6/-0)
lib/lp/translations/model/pofile.py (+37/-15)
lib/lp/translations/model/potmsgset.py (+0/-1)
lib/lp/translations/model/translationmessage.py (+11/-1)
lib/lp/translations/scripts/gettext_check_messages.py (+4/-29)
lib/lp/translations/templates/currenttranslationmessage-translate-one.pt (+12/-12)
lib/lp/translations/templates/translationmessage-translate.pt (+1/-1)
lib/lp/translations/tests/pofiletranslator.txt (+1/-12)
lib/lp/translations/tests/test_doc.py (+2/-6)
lib/lp/translations/tests/test_pofile.py (+108/-2)
scripts/rosetta/remove-upstream-translations.py (+0/-227)
To merge this branch: bzr merge lp:~henninge/launchpad/devel-bug-597539-translationmessage-pofile
Reviewer Review Type Date Requested Status
Jeroen T. Vermeulen (community) code Approve
Review via email: mp+34441@code.launchpad.net

Commit message

Removed remaining uses of TranslationMessage.pofile.

Description of the change

= Bug 597539 =

With the advent of message sharing, the "pofile" attribute of a TranslationMessage became obsolete. TranslationMessage now has a "language" attribute which can be used to find the right POFile if you also know the POTemplate. Since a TranslationMessage can now be shared by multiple POFiles, a direct link makes no sense now.

A lot of code was still using this link, though, since the column in the database still exists and holds values. Since TranslationMessage was still setting pofile upon creation, it most likely pointed to the first POFile that this message was translated in and that mostly worked.

This branch cleans this mess up by hunting down all these uses of the pofile attribute and tries to find an equivalent replacement. Please excuse the diff size, it contains file deletions an repetitive changes in templates and such.

== Proposed fix ==

For each occurrence, one of these fixes was used:

- Use the new "language" attribute if the "pofile" was only used to get to the language. Luckily there were a lot of those cases.
- In view code it was mostly possible to use "browser_pofile" which caches the POFile that the message was currently being viewed in. Some extra code had to be added to make sure it is always set.
- Sometimes the POFile was actually known and there was no reason to use TranslationMessage.pofile.
- Sometimes getOnePOFile will do which just gets any POFile associated with the translation message.
- A new query to find all TranslationMessages for a POFile was introduced.
- Some scripts did not receive nice treatment, one was crippled, the other one plain deleted. ;)

== Implementation details ==

Notes for some specific files.

=== modified file 'lib/lp/translations/browser/tests/translationmessage-views.txt'
Using POFile statistics to show that a form submission succeeded is overkilll, so I removed them.

=== modified file 'lib/lp/translations/browser/translationmessage.py'
I introduced a new parameter 'local_to_pofile' which is passed in from the top to mark local translations explicitly instead of deriving that from the "pofile" attribute. The last chunk is the core of this change.

=== modified file 'lib/lp/translations/doc/gettext-check-messages.txt'
=== modified file 'lib/lp/translations/scripts/gettext_check_messages.py'
This script operates purely on the TranslationMessage table with no relation to a POFile. Finding an imported message is not possible without that information so it had be ridded of that functionality. Obviously this also affected the test. The change has just been commented out because that script will be revisited later anyway in our current feature work and maybe we come up with a better solution.

=== modified file 'lib/lp/translations/tests/test_doc.py'
=== removed file 'lib/lp/translations/doc/remove-upstream-translations-script.txt'
=== removed file 'scripts/rosetta/remove-upstream-translations.py'
This script is not needed anymore so why bother fixing it? Yeah for code deletion!

=== modified file 'lib/lp/translations/interfaces/pofile.py'
=== modified file 'lib/lp/translations/model/pofile.py'
=== modified file 'lib/lp/translations/tests/test_pofile.py'
As POFile.translation_messages cannot be a simple join anymore, I introduced getTranslationMessages to retrieve the messages. This could also be used (with an extra condition) to replace another direct SQL query on the pofile column. Complete with test.

=== modified file 'lib/lp/translations/interfaces/translationmessage.py'
=== modified file 'lib/lp/translations/model/translationmessage.py'
The method ensureBrowserPOFile is simple but had to be in the model class because otherwise assigment to browser_pofile would not be possible.

=== modified file 'lib/lp/translations/model/potmsgset.py'
This is at the heart of this branch because it stops TranslationMessage.pofile from being assigned a value during its creation.

== Test ==

Just run all translation tests, it takes about half an hour.

bin/test -vvcm lp.translations

== QA ==

When this branch is on staging, the pofile column needs to b NULL'ed.

ALTER TABLE translationmessage DISABLE TRIGGER ALL;
UPDATE translationmessage SET pofile=NULL;
ALTER TABLE translationmessage ENABLE TRIGGER ALL;

Then various +translate pages need to be opened and also some imports would be great and similar. Check that
- nothing oopses
- the pofile column remains NULL'ed.

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

Hi Henning,

Thanks for finally ridding us of this blemish. Excellent cover letter! There are a few things that need fixing.

Points discussed on IRC:

Good job on cutting down redundant testing in translationmessage-views.txt. Don't introduce actions in doctests with "Let's" though.

I really don't like the idea of keeping dead and broken code in gettext_check_messages.py. When we overhaul the script, it will help if all the code that's in there is valid. If we want to look up how it used to work, we have bzr.

The docstring for ensureBrowserPOFile is a bit ambivalent: it "ensures" and "makes sure" that the browser_pofile is set… "if possible." Which is it? The only scenario I can imagine it coming up empty is where all templates that contained a potmsgset have been deleted. (If a template merely stops using a potmsgset, there will still be a TTI linking the two). That could happen with manual database maintenance. Some 36% of our POTMsgSets (1.3 million) and 17% of our TMs (11.5 million) are orphans. You're adding a check to ensure that these don't show up as external suggestions; we should also clean them out of the database.

For convenience, ensureBrowserPOFile can also return self.browser_pofile.

In getTranslationMessages, the Coalesce is a bit hard to indent nicely. How about e.g. creating a variable "applicable_template = Coalesce(TranslationMessage.potemplateID, self.potemplate.id)" and then having the query say "applicable_template == self.potemplate.id"?

In updateTranslation you pass pofile=None to the TranslationMessage constructor. It's a bit moot with the Recife changes coming up, but better remove that argument altogether.

Points not yet discussed on IRC:

In pofiletranslator.txt, I'd say that the oldest TM "belongs to" pofile #1, rather than "references" it. The reference is obsolete!

Nice job fixing up all those empty result sets we generated.

In test_pofile.py, you use factory.makeTranslationMessage a lot. We now also have more specific factory methods that are 100% on the new model and don't use updateTranslation: makeSuggestion, makeCurrentTranslationMessage, makeDivergedTranslationMessage. Please use those where possible.

Actually test_getTranslationMessages_baseline rolls a bunch of tests into one. That tends to weigh down diagnosis and maintenance—it's similar to the statistics check you removed from that doctest above. Could you break it down into separate "I'm creating a {suggestion,shared,diverged,obsolete} message, and it shows up in getTranslationMessages" tests?

In test_getTranslationMessages_condition, you're testing two factors at the same time: the addition of a query condition, and the effect of this particular condition on different types of messages. All you really want to test here is that the query condition works properly, which is 3 tests with one message each: message matching the condition is found, message not matching the condition is not found, and message matching the condition but for another pofile is still not found (i.e. the query condition narrows the search criteria rather than replacing them). If that's a lot of setUp work, consider giving getTranslationMessages a...

Read more...

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

> Hi Henning,
>
> Thanks for finally ridding us of this blemish. Excellent cover letter! There
> are a few things that need fixing.

Yes, and it was fun, too. In parts at least. ;-)

>
> Points discussed on IRC:
>
> Good job on cutting down redundant testing in translationmessage-views.txt.
> Don't introduce actions in doctests with "Let's" though.

Oh, it felt like I was in Rome, you know...
Fixed. ;-)

>
> I really don't like the idea of keeping dead and broken code in
> gettext_check_messages.py. When we overhaul the script, it will help if all
> the code that's in there is valid. If we want to look up how it used to work,
> we have bzr.

Old code is gone.

>
> The docstring for ensureBrowserPOFile is a bit ambivalent: it "ensures" and
> "makes sure" that the browser_pofile is set… "if possible."

Yes, I kinda new that was bad. Improved.

> You're adding a check to ensure that
> these don't show up as external suggestions;

Yes, added. It had to go into four places, where ever ensureBrowserPofile is
used. Since one was in a loop, I added an extra function for it.

>
> For convenience, ensureBrowserPOFile can also return self.browser_pofile.

It does now.

>
> In getTranslationMessages, the Coalesce is a bit hard to indent nicely. How
> about e.g. creating a variable "applicable_template =
> Coalesce(TranslationMessage.potemplateID, self.potemplate.id)" and then having
> the query say "applicable_template == self.potemplate.id"?

Yes, that looks better. Thanks.

>
> In updateTranslation you pass pofile=None to the TranslationMessage
> constructor. It's a bit moot with the Recife changes coming up, but better
> remove that argument altogether.

Removed.

>
>
> Points not yet discussed on IRC:
>
> In pofiletranslator.txt, I'd say that the oldest TM "belongs to" pofile #1,
> rather than "references" it. The reference is obsolete!

Good point. ;)

>
> Nice job fixing up all those empty result sets we generated.

Yeah, they made my eyes hurt.

>
> In test_pofile.py, you use factory.makeTranslationMessage a lot. We now also
> have more specific factory methods that are 100% on the new model and don't
> use updateTranslation: makeSuggestion, makeCurrentTranslationMessage,
> makeDivergedTranslationMessage. Please use those where possible.

Jeroen ...
We don't have those ...
This is *not* the recife branch. ;-P

>
> Actually test_getTranslationMessages_baseline rolls a bunch of tests into one.

Thank you very much for those suggestions for splitting up the tests. I applied them all and like it much better. Great!

Thanks for your review!

Henning

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

Incremental diff.

1=== modified file 'lib/lp/translations/browser/tests/translationmessage-views.txt'
2--- lib/lp/translations/browser/tests/translationmessage-views.txt 2010-09-01 10:03:11 +0000
3+++ lib/lp/translations/browser/tests/translationmessage-views.txt 2010-09-03 15:33:28 +0000
4@@ -228,7 +228,7 @@
5
6 == Submitting translations ==
7
8-Let's submit a new translation through the view.
9+A new translation is submitted through the view.
10
11 >>> server_url = '/'.join(
12 ... [canonical_url(translationmessage), '+translate'])
13
14=== modified file 'lib/lp/translations/browser/translationmessage.py'
15--- lib/lp/translations/browser/translationmessage.py 2010-09-01 10:03:57 +0000
16+++ lib/lp/translations/browser/translationmessage.py 2010-09-03 15:15:19 +0000
17@@ -1033,34 +1033,41 @@
18 # Imported one matches the current one.
19 imported_submission = None
20 elif self.imported_translationmessage is not None:
21- self.imported_translationmessage.ensureBrowserPOFile()
22- pofile = self.imported_translationmessage.browser_pofile
23- imported_submission = (
24- convert_translationmessage_to_submission(
25- message=self.imported_translationmessage,
26- current_message=self.context,
27- plural_form=index,
28- pofile=pofile,
29- legal_warning_needed=False,
30- is_empty=False,
31- packaged=True,
32- local_to_pofile=True))
33+ pofile = (
34+ self.imported_translationmessage.ensureBrowserPOFile())
35+ if pofile is None:
36+ imported_submission = None
37+ else:
38+ imported_submission = (
39+ convert_translationmessage_to_submission(
40+ message=self.imported_translationmessage,
41+ current_message=self.context,
42+ plural_form=index,
43+ pofile=pofile,
44+ legal_warning_needed=False,
45+ is_empty=False,
46+ packaged=True,
47+ local_to_pofile=True))
48 else:
49 imported_submission = None
50
51- if (self.context.potemplate is not None and
52- self.shared_translationmessage is not None):
53- self.shared_translationmessage.ensureBrowserPOFile()
54- pofile = self.shared_translationmessage.browser_pofile
55- shared_submission = (
56- convert_translationmessage_to_submission(
57- message=self.shared_translationmessage,
58- current_message=self.context,
59- plural_form=index,
60- pofile=pofile,
61- legal_warning_needed=False,
62- is_empty=False,
63- local_to_pofile=True))
64+ diverged_and_have_shared = (
65+ self.context.potemplate is not None and
66+ self.shared_translationmessage is not None)
67+ if diverged_and_have_shared:
68+ pofile = self.shared_translationmessage.ensureBrowserPOFile()
69+ if pofile is None:
70+ shared_submission = None
71+ else:
72+ shared_submission = (
73+ convert_translationmessage_to_submission(
74+ message=self.shared_translationmessage,
75+ current_message=self.context,
76+ plural_form=index,
77+ pofile=pofile,
78+ legal_warning_needed=False,
79+ is_empty=False,
80+ local_to_pofile=True))
81 else:
82 shared_submission = None
83
84@@ -1143,6 +1150,24 @@
85 else:
86 self.can_confirm_and_dismiss = True
87
88+ def _setOnePOFile(self, messages):
89+ """Return a list of messages that all have a browser_pofile set.
90+
91+ If a pofile cannot be found for a message, it is not included in
92+ the resulting list.
93+ """
94+ result = []
95+ for message in messages:
96+ if message.browser_pofile is None:
97+ pofile = message.getOnePOFile()
98+ if pofile is None:
99+ # Do not include in result.
100+ continue
101+ else:
102+ message.setPOFile(pofile)
103+ result.append(message)
104+ return result
105+
106 def _buildAllSuggestions(self):
107 """Builds all suggestions and puts them into suggestions_block.
108
109@@ -1199,26 +1224,18 @@
110
111 # Get a list of translations which are _used_ as translations
112 # for this same message in a different translation template.
113- externally_used = sorted(
114+ externally_used = self._setOnePOFile(sorted(
115 potmsgset.getExternallyUsedTranslationMessages(language),
116 key=operator.attrgetter("date_created"),
117- reverse=True)
118- for suggestion in externally_used:
119- pofile = suggestion.getOnePOFile()
120- if suggestion.browser_pofile is None:
121- suggestion.setPOFile(pofile)
122+ reverse=True))
123
124 # Get a list of translations which are suggested as
125 # translations for this same message in a different translation
126 # template, but are not used.
127- externally_suggested = sorted(
128+ externally_suggested = self._setOnePOFile(sorted(
129 potmsgset.getExternallySuggestedTranslationMessages(language),
130 key=operator.attrgetter("date_created"),
131- reverse=True)
132- for suggestion in externally_suggested:
133- pofile = suggestion.getOnePOFile()
134- if suggestion.browser_pofile is None:
135- suggestion.setPOFile(pofile)
136+ reverse=True))
137 else:
138 # Don't show suggestions for anonymous users.
139 local = externally_used = externally_suggested = []
140
141=== modified file 'lib/lp/translations/doc/gettext-check-messages.txt'
142--- lib/lp/translations/doc/gettext-check-messages.txt 2010-09-01 17:10:35 +0000
143+++ lib/lp/translations/doc/gettext-check-messages.txt 2010-09-03 15:43:39 +0000
144@@ -100,7 +100,6 @@
145 INFO Messages checked: 1
146 INFO Validation errors: 0
147 INFO Messages disabled: 0
148- INFO Messages unmasked: 0
149 INFO Commit points: ...
150
151
152@@ -126,7 +125,6 @@
153 INFO Messages checked: 1
154 INFO Validation errors: 1
155 INFO Messages disabled: 1
156- INFO Messages unmasked: 0
157 INFO Commit points: ...
158
159 The failed message is demoted to a mere suggestion.
160@@ -134,45 +132,14 @@
161 >>> current_message.is_current
162 False
163
164-Unmasking of imported messages is disabled in the script. That's why
165-although there was a perfectly good imported message that was being
166-masked by the invalid current translation, it will not be activated.
167-
168- >>> imported_message.is_current
169- False
170-
171-If the imported message is also bad, this is reported and the imported
172-message is not activated. But unmasking is disabled anyway.
173-
174- >>> imported_message.is_current = False
175- >>> current_message.is_current = True
176- >>> imported_message.translations = [u'%s %s i']
177-
178- >>> run_checker(["-w id=%s" % quote(current_message.id)])
179- DEBUG Checking messages matching: id=...
180- DEBUG Checking message ...
181- INFO ... (current): format specifications ... are not the same
182- DEBUG Commit point.
183- COMMIT
184- DEBUG Commit point.
185- COMMIT
186- INFO Done.
187- INFO Messages checked: 1
188- INFO Validation errors: 1
189- INFO Messages disabled: 1
190- INFO Messages unmasked: 0
191- INFO Commit points: ...
192-
193- >>> current_message.is_current
194- False
195- >>> imported_message.is_current
196- False
197-
198
199 == Output ==
200
201 Besides current messages, the script's output also distinguishes
202-imported ones, and ones that are completely unused.
203+imported ones, and ones that are completely unused. The imported message
204+happens to produce validation errors.
205+
206+ >>> imported_message.translations = [u'%s %s i']
207
208 In this example we'd like to see a nicely predictable ordering, so we
209 add a sort order using the -o option.
210@@ -191,7 +158,6 @@
211 INFO Messages checked: 2
212 INFO Validation errors: 2
213 INFO Messages disabled: 0
214- INFO Messages unmasked: 0
215 INFO Commit points: 2
216
217 In this case the imported message is checked twice: once all by itself
218@@ -226,7 +192,6 @@
219 INFO Messages checked: 1
220 INFO Validation errors: 1
221 INFO Messages disabled: 1
222- INFO Messages unmasked: 0
223 INFO Commit points: 2
224
225
226@@ -254,5 +219,4 @@
227 INFO Messages checked: 2
228 INFO Validation errors: 2
229 INFO Messages disabled: 0
230- INFO Messages unmasked: 0
231 INFO Commit points: 3
232
233=== modified file 'lib/lp/translations/interfaces/translationmessage.py'
234--- lib/lp/translations/interfaces/translationmessage.py 2010-08-31 12:42:29 +0000
235+++ lib/lp/translations/interfaces/translationmessage.py 2010-09-03 15:19:51 +0000
236@@ -230,7 +230,10 @@
237 """Get any POFile containing this translation."""
238
239 def ensureBrowserPOFile():
240- """Make sure browser_pofile contains something if possible."""
241+ """Assign the result of getOnePOFile to browser_pofile.
242+
243+ If browser_pofile is already set, it is left unchanged.
244+ """
245
246 def isHidden(pofile):
247 """Whether this is an unused, hidden suggestion in `pofile`.
248
249=== modified file 'lib/lp/translations/model/pofile.py'
250--- lib/lp/translations/model/pofile.py 2010-09-02 14:26:24 +0000
251+++ lib/lp/translations/model/pofile.py 2010-09-03 14:49:14 +0000
252@@ -536,13 +536,13 @@
253
254 def getTranslationMessages(self, condition=None):
255 """See `IPOFile`."""
256+ applicable_template = Coalesce(
257+ TranslationMessage.potemplateID, self.potemplate.id)
258 clauses = [
259 TranslationTemplateItem.potmsgsetID == TranslationMessage.potmsgsetID,
260 TranslationTemplateItem.potemplate == self.potemplate,
261 TranslationMessage.language == self.language,
262- Coalesce(
263- TranslationMessage.potemplateID,
264- self.potemplate.id) == self.potemplate.id,
265+ applicable_template == self.potemplate.id,
266 ]
267 if condition is not None:
268 clauses.append(condition)
269
270=== modified file 'lib/lp/translations/model/potmsgset.py'
271--- lib/lp/translations/model/potmsgset.py 2010-09-01 10:02:34 +0000
272+++ lib/lp/translations/model/potmsgset.py 2010-09-03 14:47:09 +0000
273@@ -846,7 +846,6 @@
274 matching_message = TranslationMessage(
275 potmsgset=self,
276 potemplate=pofile.potemplate,
277- pofile=None,
278 language=pofile.language,
279 origin=origin,
280 submitter=submitter,
281
282=== modified file 'lib/lp/translations/model/translationmessage.py'
283--- lib/lp/translations/model/translationmessage.py 2010-09-01 10:02:34 +0000
284+++ lib/lp/translations/model/translationmessage.py 2010-09-03 14:53:24 +0000
285@@ -162,6 +162,7 @@
286
287 def ensureBrowserPOFile(self):
288 """See `ITranslationMessage`."""
289+ return self.browser_pofile
290
291 @property
292 def all_msgstrs(self):
293@@ -387,6 +388,7 @@
294 """See `ITranslationMessage`."""
295 if self.browser_pofile is None:
296 self.browser_pofile = self.getOnePOFile()
297+ return self.browser_pofile
298
299 def _getSharedEquivalent(self):
300 """Get shared message that otherwise exactly matches this one.
301
302=== modified file 'lib/lp/translations/scripts/gettext_check_messages.py'
303--- lib/lp/translations/scripts/gettext_check_messages.py 2010-09-01 17:10:35 +0000
304+++ lib/lp/translations/scripts/gettext_check_messages.py 2010-09-03 15:29:12 +0000
305@@ -42,7 +42,6 @@
306 _check_count = 0
307 _error_count = 0
308 _disable_count = 0
309- _unmask_count = 0
310 _commit_count = 0
311
312 _commit_interval = timedelta(0, 3)
313@@ -73,10 +72,9 @@
314 self.logger.info("Messages checked: %d" % self._check_count)
315 self.logger.info("Validation errors: %d" % self._error_count)
316 self.logger.info("Messages disabled: %d" % self._disable_count)
317- self.logger.info("Messages unmasked: %d" % self._unmask_count)
318 self.logger.info("Commit points: %d" % self._commit_count)
319
320- def _log_bad_message(self, bad_message, unmasked_message, error):
321+ def _log_bad_message(self, bad_message, error):
322 """Report gettext validation error for active message."""
323 currency_markers = []
324 if bad_message.is_current:
325@@ -87,9 +85,6 @@
326 currency_markers.append('unused')
327 currency = ', '.join(currency_markers)
328 self.logger.info("%d (%s): %s" % (bad_message.id, currency, error))
329- if unmasked_message is not None:
330- self.logger.info(
331- "%s: unmasked %s." % (bad_message.id, unmasked_message.id))
332
333 def _check_message_for_error(self, translationmessage):
334 """Return error message for `translationmessage`, if any.
335@@ -108,44 +103,19 @@
336
337 return None
338
339- def _get_imported_alternative(self, translationmessage):
340- """Look for a valid, imported alternative for this message."""
341- # Do not search for an imported alternative.
342- return None
343- # This code is disabled because translationmessage.pofile is not
344- # available anymore. Providing the same functionality now would
345- # require extensive changes to the whole script.
346- # if translationmessage.is_imported:
347- # return None
348-
349- # potmsgset = translationmessage.potmsgset
350- # pofile = translationmessage.pofile
351- # return potmsgset.getImportedTranslationMessage(
352- # pofile.potemplate, pofile.language)
353-
354 def _check_and_fix(self, translationmessage):
355 """Check message against gettext, and fix it if necessary."""
356 error = self._check_message_for_error(translationmessage)
357 if error is None:
358 return
359
360- imported = self._get_imported_alternative(translationmessage)
361- if imported is not None:
362- # There is also an imported message that the current message
363- # was previously masking. If that one passes checks, we can
364- # activate it instead. Disabling the current message
365- # "unmasks" the imported one.
366- imported_error = self._check_message_for_error(imported)
367- if imported_error is not None:
368- imported = None
369+ # Here would be the place to check if another message can be used
370+ # instead of the bad one.
371
372- self._log_bad_message(translationmessage, imported, error)
373+ self._log_bad_message(translationmessage, error)
374 if translationmessage.is_current:
375 translationmessage.is_current = False
376 self._disable_count += 1
377- if imported is not None:
378- imported.is_current = True
379- self._unmask_count += 1
380
381 def _do_commit(self):
382 """Commit ongoing transaction, start a new one."""
383
384=== modified file 'lib/lp/translations/tests/pofiletranslator.txt'
385--- lib/lp/translations/tests/pofiletranslator.txt 2010-09-01 10:03:11 +0000
386+++ lib/lp/translations/tests/pofiletranslator.txt 2010-09-03 14:43:41 +0000
387@@ -24,16 +24,13 @@
388 >>> language_id = 387
389 >>> potmsgset_id = 1
390
391-
392-Note that our oldest TranslationMessage references pofile #1.
393-
394+Note that our oldest TranslationMessage belongs to pofile #1.
395
396 Stub has so far not translated anything in this pofile
397
398 >>> pofiletranslator(stub_id, pofile_id) is None
399 True
400
401-
402 If we add a message, the cache is updated
403
404 >>> cur.execute("""
405
406=== modified file 'lib/lp/translations/tests/test_pofile.py'
407--- lib/lp/translations/tests/test_pofile.py 2010-09-02 14:05:26 +0000
408+++ lib/lp/translations/tests/test_pofile.py 2010-09-03 14:37:51 +0000
409@@ -1752,59 +1752,98 @@
410 "getTranslationRows does not sort obsolete messages "
411 "(sequence=0) to the end of the file.")
412
413- def test_getTranslationMessages_baseline(self):
414- # Return all translation messages for this POFile.
415- # Shared, diverged and obsolete.
416- potmsgset = self.factory.makePOTMsgSet(self.potemplate, sequence=1)
417- suggestion = self.factory.makeTranslationMessage(
418- potmsgset=potmsgset, pofile=self.pofile)
419- current_shared = self.factory.makeTranslationMessage(
420- potmsgset=potmsgset, pofile=self.pofile, force_shared=True)
421- current_diverged = self.factory.makeTranslationMessage(
422- potmsgset=potmsgset, pofile=self.pofile, force_diverged=True)
423- obsolete_potmsgset = self.factory.makePOTMsgSet(
424- self.potemplate, sequence=0)
425- obsolete = self.factory.makeTranslationMessage(
426- potmsgset=obsolete_potmsgset, pofile=self.pofile,
427- force_shared=True)
428-
429- self.assertContentEqual(
430- [current_shared, current_diverged, suggestion, obsolete],
431- self.pofile.getTranslationMessages())
432-
433- def test_getTranslationMessages_condition(self):
434- # Narrow the result set down using a condition.
435- potmsgset = self.factory.makePOTMsgSet(self.potemplate, sequence=1)
436- suggestion = self.factory.makeTranslationMessage(
437- potmsgset=potmsgset, pofile=self.pofile)
438- current_shared = self.factory.makeTranslationMessage(
439- potmsgset=potmsgset, pofile=self.pofile, force_shared=True)
440- current_diverged = self.factory.makeTranslationMessage(
441- potmsgset=potmsgset, pofile=self.pofile, force_diverged=True)
442- obsolete_potmsgset = self.factory.makePOTMsgSet(
443- self.potemplate, sequence=0)
444- obsolete = self.factory.makeTranslationMessage(
445- potmsgset=obsolete_potmsgset, pofile=self.pofile,
446- force_shared=True)
447-
448- self.assertContentEqual(
449- [current_shared, suggestion, obsolete],
450- self.pofile.getTranslationMessages(
451- "TranslationMessage.potemplate IS NULL"))
452-
453+
454+class TestPOFileTranslationMessages(TestCaseWithFactory):
455+ """Test PO file getTranslationMessages method."""
456+
457+ layer = ZopelessDatabaseLayer
458+
459+ def setUp(self):
460+ super(TestPOFileTranslationMessages, self).setUp()
461+ self.pofile = self.factory.makePOFile('eo')
462+ self.potemplate = self.pofile.potemplate
463+ self.potmsgset = self.factory.makePOTMsgSet(
464+ self.potemplate, sequence=1)
465+
466+ def test_getTranslationMessages_current_shared(self):
467+ # A shared message is included in this POFile's messages.
468+ message = self.factory.makeTranslationMessage(
469+ potmsgset=self.potmsgset, pofile=self.pofile, force_shared=True)
470+
471+ self.assertEqual(
472+ [message], list(self.pofile.getTranslationMessages()))
473+
474+ def test_getTranslationMessages_current_diverged(self):
475+ # A diverged message is included in this POFile's messages.
476+ message = self.factory.makeTranslationMessage(
477+ potmsgset=self.potmsgset, pofile=self.pofile, force_diverged=True)
478+
479+ self.assertEqual(
480+ [message], list(self.pofile.getTranslationMessages()))
481+
482+ def test_getTranslationMessages_suggestion(self):
483+ # A suggestion is included in this POFile's messages.
484+ message = self.factory.makeTranslationMessage(
485+ potmsgset=self.potmsgset, pofile=self.pofile)
486+
487+ self.assertEqual(
488+ [message], list(self.pofile.getTranslationMessages()))
489+
490+ def test_getTranslationMessages_obsolete(self):
491+ # A message on an obsolete POTMsgSEt is included in this
492+ # POFile's messages.
493+ potmsgset = self.factory.makePOTMsgSet(self.potemplate, sequence=0)
494+ message = self.factory.makeTranslationMessage(
495+ potmsgset=potmsgset, pofile=self.pofile, force_shared=True)
496+
497+ self.assertEqual(
498+ [message], list(self.pofile.getTranslationMessages()))
499+
500 def test_getTranslationMessages_other_pofile(self):
501- # Messages from other POFiles are not included.
502- potmsgset = self.factory.makePOTMsgSet(self.potemplate, sequence=1)
503- other_pofile = self.factory.makePOFile('de')
504- my_message = self.factory.makeTranslationMessage(
505- potmsgset=potmsgset, pofile=self.pofile)
506- other_message = self.factory.makeTranslationMessage(
507- potmsgset=potmsgset, pofile=other_pofile)
508-
509+ # A message from another POFiles is not included.
510+ other_pofile = self.factory.makePOFile('de')
511+ self.factory.makeTranslationMessage(
512+ potmsgset=self.potmsgset, pofile=other_pofile)
513+
514+ self.assertEqual([], list(self.pofile.getTranslationMessages()))
515+
516+ def test_getTranslationMessages_condition_matches(self):
517+ # A message matching the given condition is included.
518+ # Diverged messages are linked to a specific POTemplate.
519+ message = self.factory.makeTranslationMessage(
520+ potmsgset=self.potmsgset, pofile=self.pofile, force_diverged=True)
521+
522+ self.assertContentEqual(
523+ [message],
524+ self.pofile.getTranslationMessages(
525+ "TranslationMessage.potemplate IS NOT NULL"))
526+
527+ def test_getTranslationMessages_condition_matches_not(self):
528+ # A message not matching the given condition is excluded.
529+ # Shared messages are not linked to a POTemplate.
530+ self.factory.makeTranslationMessage(
531+ potmsgset=self.potmsgset, pofile=self.pofile, force_shared=True)
532+
533+ self.assertContentEqual(
534+ [],
535+ self.pofile.getTranslationMessages(
536+ "TranslationMessage.potemplate IS NOT NULL"))
537+
538+ def test_getTranslationMessages_condition_matches_in_other_pofile(self):
539+ # A message matching given condition but located in another POFile
540+ # is not included.
541+ other_pofile = self.factory.makePOFile('de')
542+ self.factory.makeTranslationMessage(
543+ potmsgset=self.potmsgset, pofile=other_pofile,
544+ force_diverged=True)
545+
546+ self.assertContentEqual(
547+ [],
548+ self.pofile.getTranslationMessages(
549+ "TranslationMessage.potemplate IS NOT NULL"))
550+
551 def test_getTranslationMessages_diverged_elsewhere(self):
552 # Diverged messages from sharing POTemplates are not included.
553- potmsgset = self.factory.makePOTMsgSet(self.potemplate, sequence=1)
554-
555 # Create a sharing potemplate in another product series and share
556 # potmsgset in both templates.
557 other_series = self.factory.makeProductSeries(
558@@ -1813,15 +1852,12 @@
559 productseries=other_series, name=self.potemplate.name)
560 other_pofile = other_template.getPOFileByLang(
561 self.pofile.language.code)
562- potmsgset.setSequence(other_template, 1)
563-
564- my_message = self.factory.makeTranslationMessage(
565- potmsgset=potmsgset, pofile=self.pofile)
566- other_message = self.factory.makeTranslationMessage(
567- potmsgset=potmsgset, pofile=other_pofile, force_diverged=True)
568+ self.potmsgset.setSequence(other_template, 1)
569+ self.factory.makeTranslationMessage(
570+ potmsgset=self.potmsgset, pofile=other_pofile,
571+ force_diverged=True)
572
573- self.assertContentEqual(
574- [my_message], self.pofile.getTranslationMessages())
575+ self.assertEqual([], list(self.pofile.getTranslationMessages()))
576
577
578 class TestPOFileToTranslationFileDataAdapter(TestCaseWithFactory):

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/translations/browser/tests/translationmessage-views.txt'
2--- lib/lp/translations/browser/tests/translationmessage-views.txt 2010-07-23 09:32:52 +0000
3+++ lib/lp/translations/browser/tests/translationmessage-views.txt 2010-09-03 16:06:39 +0000
4@@ -37,7 +37,7 @@
5
6 Here we can see that it's lacking that information.
7
8- >>> print translationmessage_page_view.context.pofile.language.pluralforms
9+ >>> print translationmessage_page_view.context.language.pluralforms
10 None
11
12 And the view class detects it correctly.
13@@ -61,7 +61,7 @@
14
15 We have the plural form information for this language.
16
17- >>> print translationmessage_page_view.context.pofile.language.pluralforms
18+ >>> print translationmessage_page_view.context.language.pluralforms
19 2
20
21 And thus, the view class should know that it doesn't lacks the plural forms
22@@ -228,25 +228,7 @@
23
24 == Submitting translations ==
25
26-It's time to check the submission of translations and the IPOFile statistics
27-update.
28-
29-But first, let's see current values.
30-
31- >>> translationmessage = TranslationMessage.get(1)
32- >>> pofile = translationmessage.pofile
33- >>> pofile.updateStatistics()
34- (7, 0, 1, 2)
35- >>> pofile.currentCount()
36- 7
37- >>> pofile.updatesCount()
38- 0
39- >>> pofile.rosettaCount()
40- 1
41- >>> pofile.unreviewedCount()
42- 2
43-
44-Now we do the submit.
45+A new translation is submitted through the view.
46
47 >>> server_url = '/'.join(
48 ... [canonical_url(translationmessage), '+translate'])
49@@ -287,17 +269,7 @@
50 >>> translationmessage_page_view.initialize()
51 >>> transaction.commit()
52
53-This time we didn't get any problem with the submission, and we can see that
54-statistics were updated accordingly.
55-
56- >>> pofile.currentCount()
57- 6
58- >>> pofile.updatesCount()
59- 1
60- >>> pofile.rosettaCount()
61- 2
62- >>> pofile.unreviewedCount()
63- 2
64+This time we didn't get any problem with the submission.
65
66 Now, let's see how the system prevents a submission that has a timestamp older
67 than when last current translation was submitted.
68@@ -715,7 +687,6 @@
69 >>> def submit_translation(
70 ... translationmessage, translation, force_suggestion=False):
71 ... global year_tick
72- ... pofile = translationmessage.pofile
73 ... potmsgset = translationmessage.potmsgset
74 ... server_url = '/'.join(
75 ... [canonical_url(translationmessage), '+translate'])
76@@ -723,7 +694,8 @@
77 ... datetime.now(UTC).strftime('-%m-%dT%H:%M:%S+00:00'))
78 ... year_tick += 1
79 ... msgset_id = 'msgset_' + str(potmsgset.id)
80- ... msgset_id_lang = msgset_id + '_' + pofile.language.code
81+ ... language_code = translationmessage.language.code
82+ ... msgset_id_lang = msgset_id + '_' + language_code
83 ... form = {
84 ... 'lock_timestamp': now,
85 ... 'alt': None,
86
87=== modified file 'lib/lp/translations/browser/translationmessage.py'
88--- lib/lp/translations/browser/translationmessage.py 2010-08-24 10:45:57 +0000
89+++ lib/lp/translations/browser/translationmessage.py 2010-09-03 16:06:39 +0000
90@@ -926,7 +926,7 @@
91 self.force_suggestion = force_suggestion
92 self.force_diverge = force_diverge
93 self.user_is_official_translator = (
94- current_translation_message.pofile.canEditTranslations(self.user))
95+ self.pofile.canEditTranslations(self.user))
96 self.form_is_writeable = form_is_writeable
97 if self.context.is_imported:
98 # The imported translation matches the current one.
99@@ -1033,28 +1033,41 @@
100 # Imported one matches the current one.
101 imported_submission = None
102 elif self.imported_translationmessage is not None:
103- imported_submission = (
104- convert_translationmessage_to_submission(
105- message=self.imported_translationmessage,
106- current_message=self.context,
107- plural_form=index,
108- pofile=self.imported_translationmessage.pofile,
109- legal_warning_needed=False,
110- is_empty=False,
111- packaged=True))
112+ pofile = (
113+ self.imported_translationmessage.ensureBrowserPOFile())
114+ if pofile is None:
115+ imported_submission = None
116+ else:
117+ imported_submission = (
118+ convert_translationmessage_to_submission(
119+ message=self.imported_translationmessage,
120+ current_message=self.context,
121+ plural_form=index,
122+ pofile=pofile,
123+ legal_warning_needed=False,
124+ is_empty=False,
125+ packaged=True,
126+ local_to_pofile=True))
127 else:
128 imported_submission = None
129
130- if (self.context.potemplate is not None and
131- self.shared_translationmessage is not None):
132- shared_submission = (
133- convert_translationmessage_to_submission(
134- message=self.shared_translationmessage,
135- current_message=self.context,
136- plural_form=index,
137- pofile=self.shared_translationmessage.pofile,
138- legal_warning_needed=False,
139- is_empty=False))
140+ diverged_and_have_shared = (
141+ self.context.potemplate is not None and
142+ self.shared_translationmessage is not None)
143+ if diverged_and_have_shared:
144+ pofile = self.shared_translationmessage.ensureBrowserPOFile()
145+ if pofile is None:
146+ shared_submission = None
147+ else:
148+ shared_submission = (
149+ convert_translationmessage_to_submission(
150+ message=self.shared_translationmessage,
151+ current_message=self.context,
152+ plural_form=index,
153+ pofile=pofile,
154+ legal_warning_needed=False,
155+ is_empty=False,
156+ local_to_pofile=True))
157 else:
158 shared_submission = None
159
160@@ -1137,6 +1150,24 @@
161 else:
162 self.can_confirm_and_dismiss = True
163
164+ def _setOnePOFile(self, messages):
165+ """Return a list of messages that all have a browser_pofile set.
166+
167+ If a pofile cannot be found for a message, it is not included in
168+ the resulting list.
169+ """
170+ result = []
171+ for message in messages:
172+ if message.browser_pofile is None:
173+ pofile = message.getOnePOFile()
174+ if pofile is None:
175+ # Do not include in result.
176+ continue
177+ else:
178+ message.setPOFile(pofile)
179+ result.append(message)
180+ return result
181+
182 def _buildAllSuggestions(self):
183 """Builds all suggestions and puts them into suggestions_block.
184
185@@ -1193,26 +1224,18 @@
186
187 # Get a list of translations which are _used_ as translations
188 # for this same message in a different translation template.
189- externally_used = sorted(
190+ externally_used = self._setOnePOFile(sorted(
191 potmsgset.getExternallyUsedTranslationMessages(language),
192 key=operator.attrgetter("date_created"),
193- reverse=True)
194- for suggestion in externally_used:
195- pofile = suggestion.getOnePOFile()
196- if suggestion.browser_pofile is None:
197- suggestion.setPOFile(pofile)
198+ reverse=True))
199
200 # Get a list of translations which are suggested as
201 # translations for this same message in a different translation
202 # template, but are not used.
203- externally_suggested = sorted(
204+ externally_suggested = self._setOnePOFile(sorted(
205 potmsgset.getExternallySuggestedTranslationMessages(language),
206 key=operator.attrgetter("date_created"),
207- reverse=True)
208- for suggestion in externally_suggested:
209- pofile = suggestion.getOnePOFile()
210- if suggestion.browser_pofile is None:
211- suggestion.setPOFile(pofile)
212+ reverse=True))
213 else:
214 # Don't show suggestions for anonymous users.
215 local = externally_used = externally_suggested = []
216@@ -1249,7 +1272,7 @@
217 self.seen_translations.add(imported.translations[index])
218 local_suggestions = (
219 self._buildTranslationMessageSuggestions(
220- 'Suggestions', local, index))
221+ 'Suggestions', local, index, local_to_pofile=True))
222 externally_used_suggestions = (
223 self._buildTranslationMessageSuggestions(
224 'Used in', externally_used, index, legal_warning=True))
225@@ -1272,7 +1295,8 @@
226 len(alternate_language_suggestions.submissions))
227
228 def _buildTranslationMessageSuggestions(self, title, suggestions, index,
229- legal_warning=False):
230+ legal_warning=False,
231+ local_to_pofile=False):
232 """Build filtered list of submissions to be shown in the view.
233
234 `title` is the title for the suggestion type, `suggestions` is
235@@ -1282,7 +1306,8 @@
236 title, self.context,
237 suggestions[:self.max_entries],
238 self.user_is_official_translator, self.form_is_writeable,
239- index, self.seen_translations, legal_warning=legal_warning)
240+ index, self.seen_translations, legal_warning=legal_warning,
241+ local_to_pofile=local_to_pofile)
242 self.seen_translations = iterable_submissions.seen_translations
243 return iterable_submissions
244
245@@ -1524,10 +1549,11 @@
246
247 def __init__(self, title, translation, submissions,
248 user_is_official_translator, form_is_writeable,
249- plural_form, seen_translations=None, legal_warning=False):
250+ plural_form, seen_translations=None, legal_warning=False,
251+ local_to_pofile=False):
252 self.title = title
253 self.potmsgset = translation.potmsgset
254- self.pofile = translation.pofile
255+ self.pofile = translation.browser_pofile
256 self.user_is_official_translator = user_is_official_translator
257 self.form_is_writeable = form_is_writeable
258 self.submissions = []
259@@ -1557,7 +1583,8 @@
260 plural_form,
261 self.pofile,
262 legal_warning,
263- is_empty=False))
264+ is_empty=False,
265+ local_to_pofile=local_to_pofile))
266 self.seen_translations = seen_translations
267
268
269@@ -1566,7 +1593,7 @@
270
271 def convert_translationmessage_to_submission(
272 message, current_message, plural_form, pofile, legal_warning_needed,
273- is_empty=False, packaged=False):
274+ is_empty=False, packaged=False, local_to_pofile=False):
275 """Turn a TranslationMessage to an object used for rendering a submission.
276
277 :param message: A TranslationMessage.
278@@ -1578,10 +1605,10 @@
279
280 submission = Submission()
281 submission.translationmessage = message
282- for attribute in ['id', 'language', 'potmsgset', 'pofile',
283- 'date_created']:
284+ for attribute in ['id', 'language', 'potmsgset', 'date_created']:
285 setattr(submission, attribute, getattr(message, attribute))
286
287+ submission.pofile = message.browser_pofile
288 submission.person = message.submitter
289
290 submission.is_empty = is_empty
291@@ -1589,7 +1616,7 @@
292 submission.suggestion_text = text_to_html(
293 message.translations[plural_form],
294 message.potmsgset.flags)
295- submission.is_local_to_pofile = (message.pofile == pofile)
296+ submission.is_local_to_pofile = local_to_pofile
297 submission.legal_warning = legal_warning_needed and (
298 message.origin == RosettaTranslationOrigin.SCM)
299 submission.suggestion_html_id = (
300
301=== modified file 'lib/lp/translations/doc/gettext-check-messages.txt'
302--- lib/lp/translations/doc/gettext-check-messages.txt 2010-02-19 16:54:42 +0000
303+++ lib/lp/translations/doc/gettext-check-messages.txt 2010-09-03 16:06:39 +0000
304@@ -100,7 +100,6 @@
305 INFO Messages checked: 1
306 INFO Validation errors: 0
307 INFO Messages disabled: 0
308- INFO Messages unmasked: 0
309 INFO Commit points: ...
310
311
312@@ -118,14 +117,14 @@
313 DEBUG Checking messages matching: id=...
314 DEBUG Checking message ...
315 INFO ... (current): format specifications ... are not the same
316- INFO ...: unmasked ...
317+ DEBUG Commit point.
318+ COMMIT
319 DEBUG Commit point.
320 COMMIT
321 INFO Done.
322 INFO Messages checked: 1
323 INFO Validation errors: 1
324 INFO Messages disabled: 1
325- INFO Messages unmasked: 1
326 INFO Commit points: ...
327
328 The failed message is demoted to a mere suggestion.
329@@ -133,45 +132,14 @@
330 >>> current_message.is_current
331 False
332
333-In this case, there was a perfectly good imported message that was being
334-masked by the invalid current translation. So in addition to disabling
335-the bad current message, the script activates the good imported one.
336-
337- >>> imported_message.is_current
338- True
339-
340-If the imported message is also bad, this is reported and the imported
341-message is not activated.
342-
343- >>> imported_message.is_current = False
344- >>> current_message.is_current = True
345- >>> imported_message.translations = [u'%s %s i']
346-
347- >>> run_checker(["-w id=%s" % quote(current_message.id)])
348- DEBUG Checking messages matching: id=...
349- DEBUG Checking message ...
350- INFO ... (current): format specifications ... are not the same
351- DEBUG Commit point.
352- COMMIT
353- DEBUG Commit point.
354- COMMIT
355- INFO Done.
356- INFO Messages checked: 1
357- INFO Validation errors: 2
358- INFO Messages disabled: 1
359- INFO Messages unmasked: 0
360- INFO Commit points: ...
361-
362- >>> current_message.is_current
363- False
364- >>> imported_message.is_current
365- False
366-
367
368 == Output ==
369
370 Besides current messages, the script's output also distinguishes
371-imported ones, and ones that are completely unused.
372+imported ones, and ones that are completely unused. The imported message
373+happens to produce validation errors.
374+
375+ >>> imported_message.translations = [u'%s %s i']
376
377 In this example we'd like to see a nicely predictable ordering, so we
378 add a sort order using the -o option.
379@@ -188,9 +156,8 @@
380 COMMIT
381 INFO Done.
382 INFO Messages checked: 2
383- INFO Validation errors: 3
384+ INFO Validation errors: 2
385 INFO Messages disabled: 0
386- INFO Messages unmasked: 0
387 INFO Commit points: 2
388
389 In this case the imported message is checked twice: once all by itself
390@@ -223,9 +190,8 @@
391 ABORT
392 INFO Done.
393 INFO Messages checked: 1
394- INFO Validation errors: 2
395+ INFO Validation errors: 1
396 INFO Messages disabled: 1
397- INFO Messages unmasked: 0
398 INFO Commit points: 2
399
400
401@@ -251,7 +217,6 @@
402 COMMIT
403 INFO Done.
404 INFO Messages checked: 2
405- INFO Validation errors: 3
406+ INFO Validation errors: 2
407 INFO Messages disabled: 0
408- INFO Messages unmasked: 0
409 INFO Commit points: 3
410
411=== modified file 'lib/lp/translations/doc/pofile.txt'
412--- lib/lp/translations/doc/pofile.txt 2010-08-06 07:40:52 +0000
413+++ lib/lp/translations/doc/pofile.txt 2010-09-03 16:06:39 +0000
414@@ -773,7 +773,7 @@
415 Found %i invalid file.
416 >>> print pofile_sr.language.code
417 sr
418- >>> print translation_message.pofile.language.code
419+ >>> print translation_message.language.code
420 sr
421
422 This entry is in fact one that is not used anymore, that means, its sequence
423
424=== modified file 'lib/lp/translations/doc/potmsgset.txt'
425--- lib/lp/translations/doc/potmsgset.txt 2010-08-06 06:51:37 +0000
426+++ lib/lp/translations/doc/potmsgset.txt 2010-09-03 16:06:39 +0000
427@@ -472,7 +472,7 @@
428
429 A TranslationMessage knows what language it is in.
430
431- >>> print pt_BR_dummy_current.pofile.language.code
432+ >>> print pt_BR_dummy_current.language.code
433 pt_BR
434
435 Using another dummy pofile we'll get a POTMsgset that's not a singular
436@@ -621,7 +621,8 @@
437
438 >>> translationmessage = TranslationMessage.get(2)
439 >>> potmsgset = translationmessage.potmsgset
440- >>> pofile = translationmessage.pofile
441+ >>> from lp.translations.model.pofile import POFile
442+ >>> pofile = POFile.get(1)
443 >>> translationmessage.date_reviewed.isoformat()
444 '2005-04-07T13:19:17.601068+00:00'
445 >>> potmsgset.isTranslationNewerThan(pofile,
446@@ -660,8 +661,9 @@
447 ... usage.append('Upstream')
448 ... if not usage:
449 ... usage.append('None')
450+ ... pofile = suggestion.getOnePOFile()
451 ... lines.append('%s: %s (%s)' % (
452- ... suggestion.pofile.title,
453+ ... pofile.title,
454 ... suggestion.translations[0],
455 ... ' & '.join(usage)))
456 ... for line in sorted(lines):
457
458=== removed file 'lib/lp/translations/doc/remove-upstream-translations-script.txt'
459--- lib/lp/translations/doc/remove-upstream-translations-script.txt 2009-07-01 20:45:39 +0000
460+++ lib/lp/translations/doc/remove-upstream-translations-script.txt 1970-01-01 00:00:00 +0000
461@@ -1,142 +0,0 @@
462-This test checks the admin script 'remove-upstream-translations-script.py'.
463-That script allows us to remove all translations comming from upstream
464-to fix broken data that would pollute our suggestions database.
465-
466-It was developed to fix bug #32610.
467-
468-First we do some needed initializations.
469-
470- >>> from canonical.launchpad.ftests.harness import LaunchpadTestSetup
471- >>> LaunchpadTestSetup(dbuser='rosettaadmin').setUp()
472- >>> import subprocess, sys
473-
474-Now, we must be sure that we don't break if the user doesn't give us the
475-required arguments.
476-
477- >>> process = subprocess.Popen([
478- ... sys.executable,
479- ... 'scripts/rosetta/remove-upstream-translations.py',
480- ... '-p', 'evolution', '-vvv'],
481- ... stdin=subprocess.PIPE, stdout=subprocess.PIPE,
482- ... stderr=subprocess.STDOUT
483- ... )
484- >>> (output, empty) = process.communicate()
485- >>> print output
486- WARNING Nothing to do. Exiting...
487- <BLANKLINE>
488-
489-We can remove all translations from a concrete potemplate.
490-
491- >>> process = subprocess.Popen([
492- ... sys.executable,
493- ... 'scripts/rosetta/remove-upstream-translations.py',
494- ... '-d', 'ubuntu', '-r', 'hoary', '-n', 'evolution', '-vvv'],
495- ... stdin=subprocess.PIPE, stdout=subprocess.PIPE,
496- ... stderr=subprocess.STDOUT
497- ... )
498- >>> (output, empty) = process.communicate()
499- >>> print output
500- DEBUG Processing Spanish (es) translation of evolution-2.2 in Ubuntu Hoary package "evolution"...
501- DEBUG Removed 8 submissions
502- DEBUG Processing Japanese (ja) translation of evolution-2.2 in Ubuntu Hoary package "evolution"...
503- DEBUG Removed 0 submissions
504- DEBUG Processing Xhosa (xh) translation of evolution-2.2 in Ubuntu Hoary package "evolution"...
505- DEBUG Removed 0 submissions
506- DEBUG Processing Spanish (es) translation of man in Ubuntu Hoary package "evolution"...
507- DEBUG Removed 0 submissions
508- DEBUG Processing Spanish (es) translation of disabled-template in Ubuntu Hoary package "evolution"...
509- DEBUG Removed 0 submissions
510- DEBUG Removed 8 submissions in total.
511- <BLANKLINE>
512-
513-We need to force a DB reset because the changes are done from an external
514-script and the test system is not able to detect the database changes.
515-
516- >>> LaunchpadTestSetup().force_dirty_database()
517- >>> LaunchpadTestSetup().tearDown()
518-
519-Or with a concrete language.
520-
521- >>> LaunchpadTestSetup(dbuser='rosettaadmin').setUp()
522-
523- >>> process = subprocess.Popen([
524- ... sys.executable,
525- ... 'scripts/rosetta/remove-upstream-translations.py',
526- ... '-d', 'ubuntu', '-r', 'hoary', '-n', 'evolution', '-l', 'es',
527- ... '-vvv'],
528- ... stdin=subprocess.PIPE, stdout=subprocess.PIPE,
529- ... stderr=subprocess.STDOUT
530- ... )
531- >>> (output, empty) = process.communicate()
532- >>> print output
533- DEBUG Processing Spanish (es) translation of evolution-2.2 in Ubuntu Hoary package "evolution"...
534- DEBUG Removed 8 submissions
535- DEBUG Processing Spanish (es) translation of man in Ubuntu Hoary package "evolution"...
536- DEBUG Removed 0 submissions
537- DEBUG Processing Spanish (es) translation of disabled-template in Ubuntu Hoary package "evolution"...
538- DEBUG Removed 0 submissions
539- DEBUG Removed 8 submissions in total.
540- <BLANKLINE>
541-
542-We need to force a DB reset because the changes are done from an external
543-script and the test system is not able to detect the database changes.
544-
545- >>> LaunchpadTestSetup().force_dirty_database()
546- >>> LaunchpadTestSetup().tearDown()
547-
548-Or even with a concrete template name.
549-
550- >>> LaunchpadTestSetup(dbuser='rosettaadmin').setUp()
551-
552- >>> process = subprocess.Popen([
553- ... sys.executable,
554- ... 'scripts/rosetta/remove-upstream-translations.py',
555- ... '-d', 'ubuntu', '-r', 'hoary', '-n', 'evolution', '-t', 'evolution-2.2',
556- ... '-vvv'],
557- ... stdin=subprocess.PIPE, stdout=subprocess.PIPE,
558- ... stderr=subprocess.STDOUT
559- ... )
560- >>> (output, empty) = process.communicate()
561- >>> print output
562- DEBUG Processing Spanish (es) translation of evolution-2.2 in Ubuntu Hoary package "evolution"...
563- DEBUG Removed 8 submissions
564- DEBUG Processing Japanese (ja) translation of evolution-2.2 in Ubuntu Hoary package "evolution"...
565- DEBUG Removed 0 submissions
566- DEBUG Processing Xhosa (xh) translation of evolution-2.2 in Ubuntu Hoary package "evolution"...
567- DEBUG Removed 0 submissions
568- DEBUG Removed 8 submissions in total.
569- <BLANKLINE>
570-
571-We need to force a DB reset because the changes are done from an external
572-script and the test system is not able to detect the database changes.
573-
574- >>> LaunchpadTestSetup().force_dirty_database()
575- >>> LaunchpadTestSetup().tearDown()
576-
577-And it also works for products!
578-
579- >>> LaunchpadTestSetup(dbuser='rosettaadmin').setUp()
580-
581- >>> import subprocess, sys
582- >>> process = subprocess.Popen([
583- ... sys.executable,
584- ... 'scripts/rosetta/remove-upstream-translations.py',
585- ... '-p', 'evolution', '-s', 'trunk', '-vvv'],
586- ... stdin=subprocess.PIPE, stdout=subprocess.PIPE,
587- ... stderr=subprocess.STDOUT
588- ... )
589- >>> (output, empty) = process.communicate()
590- >>> print output
591- DEBUG Processing Spanish (es) translation of evolution-2.2 in Evolution trunk...
592- DEBUG Removed 9 submissions
593- DEBUG Processing Portuguese (Brazil) (pt_BR) translation of evolution-2.2-test in Evolution trunk...
594- DEBUG Removed 5 submissions
595- DEBUG Removed 14 submissions in total.
596- <BLANKLINE>
597-
598-We need to force a DB reset because the changes are done from an external
599-script and the test system is not able to detect the database changes.
600-
601- >>> LaunchpadTestSetup().force_dirty_database()
602- >>> LaunchpadTestSetup().tearDown()
603-
604
605=== modified file 'lib/lp/translations/interfaces/pofile.py'
606--- lib/lp/translations/interfaces/pofile.py 2010-08-20 20:31:18 +0000
607+++ lib/lp/translations/interfaces/pofile.py 2010-09-03 16:06:39 +0000
608@@ -201,6 +201,13 @@
609 `date_created` with newest first.
610 """
611
612+ def getTranslationMessages(condition=None):
613+ """Get TranslationMessages in this `IPOFile`.
614+
615+ If condition is None, return all messages, else narrow the result
616+ set down using the condition.
617+ """
618+
619 def makeTranslatableMessage(potmsgset):
620 """Factory method for an `ITranslatableMessage` object.
621
622
623=== modified file 'lib/lp/translations/interfaces/translationmessage.py'
624--- lib/lp/translations/interfaces/translationmessage.py 2010-08-20 20:31:18 +0000
625+++ lib/lp/translations/interfaces/translationmessage.py 2010-09-03 16:06:39 +0000
626@@ -229,6 +229,12 @@
627 def getOnePOFile():
628 """Get any POFile containing this translation."""
629
630+ def ensureBrowserPOFile():
631+ """Assign the result of getOnePOFile to browser_pofile.
632+
633+ If browser_pofile is already set, it is left unchanged.
634+ """
635+
636 def isHidden(pofile):
637 """Whether this is an unused, hidden suggestion in `pofile`.
638
639
640=== modified file 'lib/lp/translations/model/pofile.py'
641--- lib/lp/translations/model/pofile.py 2010-08-24 10:45:57 +0000
642+++ lib/lp/translations/model/pofile.py 2010-09-03 16:06:39 +0000
643@@ -21,11 +21,11 @@
644 BoolCol,
645 ForeignKey,
646 IntCol,
647- SQLMultipleJoin,
648 StringCol,
649 )
650 from storm.expr import (
651 And,
652+ Coalesce,
653 Exists,
654 In,
655 Join,
656@@ -36,7 +36,10 @@
657 SQL,
658 )
659 from storm.info import ClassAlias
660-from storm.store import Store
661+from storm.store import (
662+ EmptyResultSet,
663+ Store,
664+ )
665 from zope.component import (
666 getAdapter,
667 getUtility,
668@@ -55,6 +58,7 @@
669 )
670 from canonical.launchpad import helpers
671 from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
672+from canonical.launchpad.interfaces.lpstorm import IStore
673 from canonical.launchpad.readonly import is_read_only
674 from canonical.launchpad.webapp.interfaces import (
675 DEFAULT_FLAVOR,
676@@ -525,9 +529,26 @@
677 from_sourcepackagename = ForeignKey(foreignKey='SourcePackageName',
678 dbName='from_sourcepackagename', notNull=False, default=None)
679
680- # joins
681- translation_messages = SQLMultipleJoin(
682- 'TranslationMessage', joinColumn='pofile', orderBy='id')
683+ @property
684+ def translation_messages(self):
685+ """See `IPOFile`."""
686+ return self.getTranslationMessages()
687+
688+ def getTranslationMessages(self, condition=None):
689+ """See `IPOFile`."""
690+ applicable_template = Coalesce(
691+ TranslationMessage.potemplateID, self.potemplate.id)
692+ clauses = [
693+ TranslationTemplateItem.potmsgsetID == TranslationMessage.potmsgsetID,
694+ TranslationTemplateItem.potemplate == self.potemplate,
695+ TranslationMessage.language == self.language,
696+ applicable_template == self.potemplate.id,
697+ ]
698+ if condition is not None:
699+ clauses.append(condition)
700+
701+ return IStore(self).find(
702+ TranslationMessage, *clauses).order_by(TranslationMessage.id)
703
704 @property
705 def title(self):
706@@ -1086,9 +1107,9 @@
707 entry_to_import.setErrorOutput(None)
708
709 # Prepare the mail notification.
710- msgsets_imported = TranslationMessage.select(
711- 'was_obsolete_in_last_import IS FALSE AND pofile=%s' %
712- (sqlvalues(self.id))).count()
713+ msgsets_imported = self.getTranslationMessages(
714+ TranslationMessage.was_obsolete_in_last_import == False
715+ ).count()
716
717 replacements = collect_import_info(entry_to_import, self, warnings)
718 replacements.update({
719@@ -1354,16 +1375,13 @@
720 """See `IPOFile`."""
721 return self.potemplate.translationpermission
722
723- def emptySelectResults(self):
724- return POFile.select("1=2")
725-
726 def getTranslationsFilteredBy(self, person):
727 """See `IPOFile`."""
728 return None
729
730 def getPOTMsgSetTranslated(self):
731 """See `IPOFile`."""
732- return self.emptySelectResults()
733+ return EmptyResultSet()
734
735 def getPOTMsgSetUntranslated(self):
736 """See `IPOFile`."""
737@@ -1371,15 +1389,19 @@
738
739 def getPOTMsgSetWithNewSuggestions(self):
740 """See `IPOFile`."""
741- return self.emptySelectResults()
742+ return EmptyResultSet()
743
744 def getPOTMsgSetChangedInLaunchpad(self):
745 """See `IPOFile`."""
746- return self.emptySelectResults()
747+ return EmptyResultSet()
748
749 def getPOTMsgSetWithErrors(self):
750 """See `IPOFile`."""
751- return self.emptySelectResults()
752+ return EmptyResultSet()
753+
754+ def getTranslationMessages(self, condition=None):
755+ """See `IPOFile`."""
756+ return EmptyResultSet()
757
758 def hasMessageID(self, msgid):
759 """See `IPOFile`."""
760
761=== modified file 'lib/lp/translations/model/potmsgset.py'
762--- lib/lp/translations/model/potmsgset.py 2010-08-20 20:31:18 +0000
763+++ lib/lp/translations/model/potmsgset.py 2010-09-03 16:06:39 +0000
764@@ -846,7 +846,6 @@
765 matching_message = TranslationMessage(
766 potmsgset=self,
767 potemplate=pofile.potemplate,
768- pofile=pofile,
769 language=pofile.language,
770 origin=origin,
771 submitter=submitter,
772
773=== modified file 'lib/lp/translations/model/translationmessage.py'
774--- lib/lp/translations/model/translationmessage.py 2010-08-24 10:45:57 +0000
775+++ lib/lp/translations/model/translationmessage.py 2010-09-03 16:06:39 +0000
776@@ -123,7 +123,7 @@
777 'This translation message already exists in the database.')
778
779 self.id = None
780- self.pofile = pofile
781+ self.pofile = None
782 self.browser_pofile = pofile
783 self.potemplate = pofile.potemplate
784 self.language = pofile.language
785@@ -160,6 +160,10 @@
786 """See `ITranslationMessage`."""
787 return None
788
789+ def ensureBrowserPOFile(self):
790+ """See `ITranslationMessage`."""
791+ return self.browser_pofile
792+
793 @property
794 def all_msgstrs(self):
795 """See `ITranslationMessage`."""
796@@ -380,6 +384,12 @@
797 else:
798 return None
799
800+ def ensureBrowserPOFile(self):
801+ """See `ITranslationMessage`."""
802+ if self.browser_pofile is None:
803+ self.browser_pofile = self.getOnePOFile()
804+ return self.browser_pofile
805+
806 def _getSharedEquivalent(self):
807 """Get shared message that otherwise exactly matches this one.
808 """
809
810=== modified file 'lib/lp/translations/scripts/gettext_check_messages.py'
811--- lib/lp/translations/scripts/gettext_check_messages.py 2010-08-20 20:31:18 +0000
812+++ lib/lp/translations/scripts/gettext_check_messages.py 2010-09-03 16:06:39 +0000
813@@ -42,7 +42,6 @@
814 _check_count = 0
815 _error_count = 0
816 _disable_count = 0
817- _unmask_count = 0
818 _commit_count = 0
819
820 _commit_interval = timedelta(0, 3)
821@@ -73,10 +72,9 @@
822 self.logger.info("Messages checked: %d" % self._check_count)
823 self.logger.info("Validation errors: %d" % self._error_count)
824 self.logger.info("Messages disabled: %d" % self._disable_count)
825- self.logger.info("Messages unmasked: %d" % self._unmask_count)
826 self.logger.info("Commit points: %d" % self._commit_count)
827
828- def _log_bad_message(self, bad_message, unmasked_message, error):
829+ def _log_bad_message(self, bad_message, error):
830 """Report gettext validation error for active message."""
831 currency_markers = []
832 if bad_message.is_current:
833@@ -87,9 +85,6 @@
834 currency_markers.append('unused')
835 currency = ', '.join(currency_markers)
836 self.logger.info("%d (%s): %s" % (bad_message.id, currency, error))
837- if unmasked_message is not None:
838- self.logger.info(
839- "%s: unmasked %s." % (bad_message.id, unmasked_message.id))
840
841 def _check_message_for_error(self, translationmessage):
842 """Return error message for `translationmessage`, if any.
843@@ -108,39 +103,19 @@
844
845 return None
846
847- def _get_imported_alternative(self, translationmessage):
848- """Look for a valid, imported alternative for this message."""
849- if translationmessage.is_imported:
850- return None
851-
852- potmsgset = translationmessage.potmsgset
853- pofile = translationmessage.pofile
854- return potmsgset.getImportedTranslationMessage(
855- pofile.potemplate, pofile.language)
856-
857 def _check_and_fix(self, translationmessage):
858 """Check message against gettext, and fix it if necessary."""
859 error = self._check_message_for_error(translationmessage)
860 if error is None:
861 return
862
863- imported = self._get_imported_alternative(translationmessage)
864- if imported is not None:
865- # There is also an imported message that the current message
866- # was previously masking. If that one passes checks, we can
867- # activate it instead. Disabling the current message
868- # "unmasks" the imported one.
869- imported_error = self._check_message_for_error(imported)
870- if imported_error is not None:
871- imported = None
872+ # Here would be the place to check if another message can be used
873+ # instead of the bad one.
874
875- self._log_bad_message(translationmessage, imported, error)
876+ self._log_bad_message(translationmessage, error)
877 if translationmessage.is_current:
878 translationmessage.is_current = False
879 self._disable_count += 1
880- if imported is not None:
881- imported.is_current = True
882- self._unmask_count += 1
883
884 def _do_commit(self):
885 """Commit ongoing transaction, start a new one."""
886
887=== modified file 'lib/lp/translations/templates/currenttranslationmessage-translate-one.pt'
888--- lib/lp/translations/templates/currenttranslationmessage-translate-one.pt 2010-04-23 00:38:35 +0000
889+++ lib/lp/translations/templates/currenttranslationmessage-translate-one.pt 2010-09-03 16:06:39 +0000
890@@ -5,7 +5,7 @@
891 omit-tag="">
892
893 <tal:language-code
894- define="language_code context/pofile/language/code">
895+ define="language_code context/language/code">
896 <tr class="translation">
897 <td class="icon left right">
898 <a tal:attributes="
899@@ -176,7 +176,7 @@
900 <tr tal:attributes="class string:secondary translation ${view/html_id}">
901 <th colspan="3">
902 <label class="language-code">Current
903- <span tal:replace="context/pofile/language/englishname">
904+ <span tal:replace="context/language/englishname">
905 Welsh
906 </span><span tal:replace="string:[${plural_index}]"
907 tal:condition="view/is_plural">[0]</span>:
908@@ -219,8 +219,8 @@
909 <div tal:content="structure translation_dictionary/current_translation"
910 tal:condition="not: view/user_is_official_translator"
911 tal:attributes="
912- lang context/pofile/language/dashedcode;
913- dir context/pofile/language/abbreviated_text_dir;
914+ lang context/language/dashedcode;
915+ dir context/language/abbreviated_text_dir;
916 id string:${translation_dictionary/html_id_translation}">
917 current translation
918 </div>
919@@ -229,8 +229,8 @@
920 tal:content="structure translation_dictionary/current_translation"
921 tal:condition="view/user_is_official_translator"
922 tal:attributes="
923- lang context/pofile/language/dashedcode;
924- dir context/pofile/language/abbreviated_text_dir;
925+ lang context/language/dashedcode;
926+ dir context/language/abbreviated_text_dir;
927 id string:${translation_dictionary/html_id_translation};
928 for string:${translation_dictionary/html_id_translation}_radiobutton">
929 current translation
930@@ -503,8 +503,8 @@
931 tal:attributes="
932 name string:${translation_dictionary/html_id_translation}_new;
933 id string:${translation_dictionary/html_id_translation}_new;
934- lang context/pofile/language/dashedcode;
935- dir context/pofile/language/abbreviated_text_dir;
936+ lang context/language/dashedcode;
937+ dir context/language/abbreviated_text_dir;
938 value translation_dictionary/submitted_translation;
939 "
940 class="translate expandable"
941@@ -526,8 +526,8 @@
942 tal:attributes="
943 id string:${translation_dictionary/html_id_translation}_new;
944 name string:${translation_dictionary/html_id_translation}_new;
945- lang context/pofile/language/dashedcode;
946- dir context/pofile/language/abbreviated_text_dir;
947+ lang context/language/dashedcode;
948+ dir context/language/abbreviated_text_dir;
949 ">
950 <tal:content replace="translation_dictionary/submitted_translation" /></textarea>
951 </tal:with-content>
952@@ -544,8 +544,8 @@
953 tal:attributes="
954 id string:${translation_dictionary/html_id_translation}_new;
955 name string:${translation_dictionary/html_id_translation}_new;
956- lang context/pofile/language/dashedcode;
957- dir context/pofile/language/abbreviated_text_dir;
958+ lang context/language/dashedcode;
959+ dir context/language/abbreviated_text_dir;
960 "></textarea>
961 </tal:without-content>
962 </tal:multi-line>
963
964=== modified file 'lib/lp/translations/templates/translationmessage-translate.pt'
965--- lib/lp/translations/templates/translationmessage-translate.pt 2010-05-18 18:04:00 +0000
966+++ lib/lp/translations/templates/translationmessage-translate.pt 2010-09-03 16:06:39 +0000
967@@ -84,7 +84,7 @@
968 <!-- Paging doodads. -->
969 <tal:navigation
970 replace="structure view/batchnav/@@+navigation-links-lower" />
971- <tal:status replace="structure context/pofile/@@+access" />
972+ <tal:status replace="structure context/browser_pofile/@@+access" />
973 </tal:havepluralforms>
974 <metal:pofile-js-footer
975 use-macro="context/@@+translations-macros/pofile-js-footer" />
976
977=== modified file 'lib/lp/translations/tests/pofiletranslator.txt'
978--- lib/lp/translations/tests/pofiletranslator.txt 2009-08-13 15:12:16 +0000
979+++ lib/lp/translations/tests/pofiletranslator.txt 2010-09-03 16:06:39 +0000
980@@ -24,24 +24,13 @@
981 >>> language_id = 387
982 >>> potmsgset_id = 1
983
984-
985-Note that our oldest TranslationMessage references pofile #1
986-
987- >>> cur.execute("""
988- ... SELECT pofile
989- ... FROM TranslationMessage
990- ... ORDER BY date_created
991- ... LIMIT 1""")
992- >>> cur.fetchone()[0]
993- 1
994-
995+Note that our oldest TranslationMessage belongs to pofile #1.
996
997 Stub has so far not translated anything in this pofile
998
999 >>> pofiletranslator(stub_id, pofile_id) is None
1000 True
1001
1002-
1003 If we add a message, the cache is updated
1004
1005 >>> cur.execute("""
1006
1007=== modified file 'lib/lp/translations/tests/test_doc.py'
1008--- lib/lp/translations/tests/test_doc.py 2010-08-20 20:31:18 +0000
1009+++ lib/lp/translations/tests/test_doc.py 2010-09-03 16:06:39 +0000
1010@@ -27,12 +27,8 @@
1011
1012 special = {
1013 'pofile-views.txt': LayeredDocFileSuite(
1014- '../browser/tests/pofile-views.txt',
1015- setUp=setUp, tearDown=tearDown, layer=LaunchpadFunctionalLayer
1016- ),
1017- 'remove-upstream-translations-script.txt': LayeredDocFileSuite(
1018- '../doc/remove-upstream-translations-script.txt',
1019- setUp=setGlobs, stdout_logging=False, layer=None
1020+ '../browser/tests/pofile-views.txt',
1021+ setUp=setUp, tearDown=tearDown, layer=LaunchpadFunctionalLayer
1022 ),
1023 'poexport-queue.txt': LayeredDocFileSuite(
1024 '../doc/poexport-queue.txt',
1025
1026=== modified file 'lib/lp/translations/tests/test_pofile.py'
1027--- lib/lp/translations/tests/test_pofile.py 2010-08-20 20:31:18 +0000
1028+++ lib/lp/translations/tests/test_pofile.py 2010-09-03 16:06:39 +0000
1029@@ -1729,8 +1729,7 @@
1030
1031 def test_makeTranslatableMessage(self):
1032 # TranslatableMessages can be created from the PO file
1033- potmsgset = self.factory.makePOTMsgSet(self.potemplate,
1034- sequence=1)
1035+ potmsgset = self.factory.makePOTMsgSet(self.potemplate, sequence=1)
1036 message = self.pofile.makeTranslatableMessage(potmsgset)
1037 verifyObject(ITranslatableMessage, message)
1038
1039@@ -1754,6 +1753,113 @@
1040 "(sequence=0) to the end of the file.")
1041
1042
1043+class TestPOFileTranslationMessages(TestCaseWithFactory):
1044+ """Test PO file getTranslationMessages method."""
1045+
1046+ layer = ZopelessDatabaseLayer
1047+
1048+ def setUp(self):
1049+ super(TestPOFileTranslationMessages, self).setUp()
1050+ self.pofile = self.factory.makePOFile('eo')
1051+ self.potemplate = self.pofile.potemplate
1052+ self.potmsgset = self.factory.makePOTMsgSet(
1053+ self.potemplate, sequence=1)
1054+
1055+ def test_getTranslationMessages_current_shared(self):
1056+ # A shared message is included in this POFile's messages.
1057+ message = self.factory.makeTranslationMessage(
1058+ potmsgset=self.potmsgset, pofile=self.pofile, force_shared=True)
1059+
1060+ self.assertEqual(
1061+ [message], list(self.pofile.getTranslationMessages()))
1062+
1063+ def test_getTranslationMessages_current_diverged(self):
1064+ # A diverged message is included in this POFile's messages.
1065+ message = self.factory.makeTranslationMessage(
1066+ potmsgset=self.potmsgset, pofile=self.pofile, force_diverged=True)
1067+
1068+ self.assertEqual(
1069+ [message], list(self.pofile.getTranslationMessages()))
1070+
1071+ def test_getTranslationMessages_suggestion(self):
1072+ # A suggestion is included in this POFile's messages.
1073+ message = self.factory.makeTranslationMessage(
1074+ potmsgset=self.potmsgset, pofile=self.pofile)
1075+
1076+ self.assertEqual(
1077+ [message], list(self.pofile.getTranslationMessages()))
1078+
1079+ def test_getTranslationMessages_obsolete(self):
1080+ # A message on an obsolete POTMsgSEt is included in this
1081+ # POFile's messages.
1082+ potmsgset = self.factory.makePOTMsgSet(self.potemplate, sequence=0)
1083+ message = self.factory.makeTranslationMessage(
1084+ potmsgset=potmsgset, pofile=self.pofile, force_shared=True)
1085+
1086+ self.assertEqual(
1087+ [message], list(self.pofile.getTranslationMessages()))
1088+
1089+ def test_getTranslationMessages_other_pofile(self):
1090+ # A message from another POFiles is not included.
1091+ other_pofile = self.factory.makePOFile('de')
1092+ self.factory.makeTranslationMessage(
1093+ potmsgset=self.potmsgset, pofile=other_pofile)
1094+
1095+ self.assertEqual([], list(self.pofile.getTranslationMessages()))
1096+
1097+ def test_getTranslationMessages_condition_matches(self):
1098+ # A message matching the given condition is included.
1099+ # Diverged messages are linked to a specific POTemplate.
1100+ message = self.factory.makeTranslationMessage(
1101+ potmsgset=self.potmsgset, pofile=self.pofile, force_diverged=True)
1102+
1103+ self.assertContentEqual(
1104+ [message],
1105+ self.pofile.getTranslationMessages(
1106+ "TranslationMessage.potemplate IS NOT NULL"))
1107+
1108+ def test_getTranslationMessages_condition_matches_not(self):
1109+ # A message not matching the given condition is excluded.
1110+ # Shared messages are not linked to a POTemplate.
1111+ self.factory.makeTranslationMessage(
1112+ potmsgset=self.potmsgset, pofile=self.pofile, force_shared=True)
1113+
1114+ self.assertContentEqual(
1115+ [],
1116+ self.pofile.getTranslationMessages(
1117+ "TranslationMessage.potemplate IS NOT NULL"))
1118+
1119+ def test_getTranslationMessages_condition_matches_in_other_pofile(self):
1120+ # A message matching given condition but located in another POFile
1121+ # is not included.
1122+ other_pofile = self.factory.makePOFile('de')
1123+ self.factory.makeTranslationMessage(
1124+ potmsgset=self.potmsgset, pofile=other_pofile,
1125+ force_diverged=True)
1126+
1127+ self.assertContentEqual(
1128+ [],
1129+ self.pofile.getTranslationMessages(
1130+ "TranslationMessage.potemplate IS NOT NULL"))
1131+
1132+ def test_getTranslationMessages_diverged_elsewhere(self):
1133+ # Diverged messages from sharing POTemplates are not included.
1134+ # Create a sharing potemplate in another product series and share
1135+ # potmsgset in both templates.
1136+ other_series = self.factory.makeProductSeries(
1137+ product=self.potemplate.productseries.product)
1138+ other_template = self.factory.makePOTemplate(
1139+ productseries=other_series, name=self.potemplate.name)
1140+ other_pofile = other_template.getPOFileByLang(
1141+ self.pofile.language.code)
1142+ self.potmsgset.setSequence(other_template, 1)
1143+ self.factory.makeTranslationMessage(
1144+ potmsgset=self.potmsgset, pofile=other_pofile,
1145+ force_diverged=True)
1146+
1147+ self.assertEqual([], list(self.pofile.getTranslationMessages()))
1148+
1149+
1150 class TestPOFileToTranslationFileDataAdapter(TestCaseWithFactory):
1151 """Test POFile being adapted to IPOFileToTranslationFileData."""
1152
1153
1154=== removed file 'scripts/rosetta/remove-upstream-translations.py'
1155--- scripts/rosetta/remove-upstream-translations.py 2010-08-06 07:02:32 +0000
1156+++ scripts/rosetta/remove-upstream-translations.py 1970-01-01 00:00:00 +0000
1157@@ -1,227 +0,0 @@
1158-#!/usr/bin/python -S
1159-#
1160-# Copyright 2009 Canonical Ltd. This software is licensed under the
1161-# GNU Affero General Public License version 3 (see the file LICENSE).
1162-
1163-# pylint: disable-msg=W0403
1164-"""Remove all translations from upstream.
1165-
1166-This script is useful to recover from breakages after importing bad
1167-.po files like the one reported at #32610.
1168-"""
1169-
1170-import _pythonpath
1171-
1172-import sys
1173-import logging
1174-from optparse import OptionParser
1175-
1176-from zope.component import getUtility
1177-
1178-from canonical.config import config
1179-from canonical.database.constants import UTC_NOW
1180-from canonical.lp import initZopeless
1181-from canonical.launchpad.scripts import (
1182- execute_zcml_for_scripts, logger, logger_options)
1183-from canonical.launchpad.interfaces import ILaunchpadCelebrities
1184-from lp.registry.interfaces.product import IProductSet
1185-from lp.registry.interfaces.distribution import IDistributionSet
1186-from lp.registry.interfaces.distroseries import IDistroSeriesSet
1187-from lp.registry.interfaces.sourcepackagename import (
1188- ISourcePackageNameSet)
1189-from lp.translations.interfaces.potemplate import IPOTemplateSet
1190-from lp.translations.interfaces.translationmessage import (
1191- RosettaTranslationOrigin)
1192-
1193-
1194-
1195-logger_name = 'remove-upstream-translations'
1196-
1197-def parse_options(args):
1198- """Parse a set of command line options.
1199-
1200- Return an optparse.Values object.
1201- """
1202- parser = OptionParser()
1203-
1204- parser.add_option("-p", "--product", dest="product",
1205- help="The product where we should look for translations.")
1206- parser.add_option("-s", "--series", dest="series",
1207- help="The product series where we should look for translations.")
1208- parser.add_option("-d", "--distro", dest="distro",
1209- help="The distribution where we should look for translations.")
1210- parser.add_option("-r", "--distroseries", dest="distroseries",
1211- help="The distribution series where we should look for translations."
1212- )
1213- parser.add_option("-n", "--sourcepackagename", dest="sourcepackagename",
1214- help="The distribution where we should look for translations.")
1215- parser.add_option("-t", "--potemplatename", dest="potemplatename",
1216- help="The PO Template name where we should look for translations.")
1217- parser.add_option("-l", "--language-code", dest="languagecode",
1218- help="The language code where we should look for translations.")
1219-
1220- # Add the verbose/quiet options.
1221- logger_options(parser)
1222-
1223- (options, args) = parser.parse_args(args)
1224-
1225- return options
1226-
1227-def remove_upstream_entries(ztm, potemplates, lang_code=None):
1228- """Remove all translations that came from upstream.
1229-
1230- :arg ztm: Zope transaction manager.
1231- :arg potemplates: A set of potemplates that we should process.
1232- :arg lang_code: A string with a language code where we should do the
1233- removal.
1234-
1235- If lang_code is None, we process all available languages.
1236- """
1237-
1238- logger_object = logging.getLogger(logger_name)
1239-
1240- items_deleted = 0
1241- # All changes should be logged as done by Rosetta Expert team.
1242- rosetta_experts = getUtility(ILaunchpadCelebrities).rosetta_experts
1243-
1244- for potemplate in potemplates:
1245- if lang_code is None:
1246- pofiles = sorted(
1247- list(potemplate.pofiles),
1248- key=lambda p: p.language.code)
1249- else:
1250- pofile = potemplate.getPOFileByLang(lang_code)
1251- if pofile is None:
1252- pofiles = []
1253- else:
1254- pofiles = [pofile]
1255-
1256- for pofile in pofiles:
1257- logger_object.debug('Processing %s...' % pofile.title)
1258- pofile_items_deleted = 0
1259- for message in pofile.translation_messages:
1260- active_changed = False
1261- if message.origin == RosettaTranslationOrigin.SCM:
1262- if message.is_current:
1263- active_changed = True
1264- message.destroySelf()
1265- pofile_items_deleted += 1
1266- if active_changed:
1267- message.pofile.date_changed = UTC_NOW
1268- message.pofile.lasttranslator = rosetta_experts
1269- message.reviewer = rosetta_experts
1270- message.date_reviewed = UTC_NOW
1271-
1272- items_deleted += pofile_items_deleted
1273- logger_object.debug(
1274- 'Removed %d submissions' % pofile_items_deleted)
1275- pofile.updateStatistics()
1276- ztm.commit()
1277-
1278- # We finished the removal process, is time to notify the amount of entries
1279- # that we removed.
1280- logger_object.debug(
1281- 'Removed %d submissions in total.' % items_deleted)
1282-
1283-
1284-def main(argv):
1285- options = parse_options(argv[1:])
1286- logger_object = logger(options, logger_name)
1287-
1288- execute_zcml_for_scripts()
1289- ztm = initZopeless(dbuser=config.rosetta.admin_dbuser)
1290-
1291- product = None
1292- series = None
1293- distro = None
1294- distroseries = None
1295- sourcepackagename = None
1296- potemplatename = None
1297- language_code = None
1298- if options.product is not None:
1299- productset = getUtility(IProductSet)
1300- product = productset.getByName(options.product)
1301- if product is None:
1302- logger_object.error(
1303- 'The %s product does not exist.' % options.product)
1304- return 1
1305-
1306- if options.series is not None:
1307- if product is None:
1308- logger_object.error(
1309- 'You need to specify a product if you want to select a'
1310- ' productseries.')
1311- return 1
1312-
1313- series = product.getSeries(options.series)
1314- if series is None:
1315- logger_object.error(
1316- 'The %s series does not exist inside %s product.' % (
1317- options.series, options.product))
1318- return 1
1319-
1320- if options.distro is not None:
1321- if product is not None:
1322- logger_object.error(
1323- 'You cannot mix distributions and products.')
1324- return 1
1325- distroset = getUtility(IDistributionSet)
1326- distro = distroset.getByName(options.distro)
1327- if distro is None:
1328- logger_object.error(
1329- 'The %s distribution does not exist.' % options.distro)
1330- return 1
1331-
1332- if options.distroseries is not None:
1333- if distro is None:
1334- logger_object.error(
1335- 'You need to specify a distribution if you want to select a'
1336- ' sourcepackagename.')
1337- distroseriesset = getUtility(IDistroSeriesSet)
1338- distroseries = distroseriesset.queryByName(
1339- distro, options.distroseries)
1340- if distroseries is None:
1341- logger_object.error(
1342- 'The %s distribution does not exist.' % options.distroseries)
1343- return 1
1344-
1345- if options.sourcepackagename is not None:
1346- if distroseries is None:
1347- logger_object.error(
1348- 'You need to specify a distribution series if you want to'
1349- ' select a sourcepackagename.')
1350- return 1
1351- sourcepackagenameset = getUtility(ISourcePackageNameSet)
1352- sourcepackagename = sourcepackagenameset.queryByName(
1353- options.sourcepackagename)
1354- if sourcepackagename is None:
1355- logger_object.error(
1356- 'The %s sourcepackagename does not exist.' % (
1357- options.sourcepackagename))
1358- return 1
1359-
1360- potemplateset = getUtility(IPOTemplateSet)
1361- if series is None and distroseries is None:
1362- if options.potemplatename is None:
1363- logger_object.warning('Nothing to do. Exiting...')
1364- return 0
1365- else:
1366- potemplates = potemplateset.getAllByName(
1367- options.potemplatename)
1368- else:
1369- potemplate_subset = potemplateset.getSubset(
1370- distroseries=distroseries, sourcepackagename=sourcepackagename,
1371- productseries=series)
1372- if options.potemplatename is not None:
1373- potemplate = potemplate_subset.getPOTemplateByName(
1374- options.potemplatename)
1375- potemplates = [potemplate]
1376- else:
1377- # Get a list from the subset of potemplates to be able to do
1378- # transaction commits.
1379- potemplates = list(potemplate_subset)
1380-
1381- remove_upstream_entries(ztm, potemplates, options.languagecode)
1382-
1383-if __name__ == '__main__':
1384- sys.exit(main(sys.argv))