Merge lp:~jtv/launchpad/recife-kill-updatetranslation-1 into lp:~launchpad/launchpad/recife

Proposed by Jeroen T. Vermeulen on 2010-11-16
Status: Merged
Approved by: Graham Binns on 2010-11-16
Approved revision: no longer in the source branch.
Merged at revision: 9194
Proposed branch: lp:~jtv/launchpad/recife-kill-updatetranslation-1
Merge into: lp:~launchpad/launchpad/recife
Diff against target: 436 lines (+152/-128)
7 files modified
lib/lp/translations/browser/tests/test_translationmessage_view.py (+100/-1)
lib/lp/translations/browser/tests/translationmessage-views.txt (+0/-97)
lib/lp/translations/interfaces/potmsgset.py (+6/-0)
lib/lp/translations/model/potmsgset.py (+18/-8)
lib/lp/translations/model/translatablemessage.py (+2/-3)
lib/lp/translations/tests/test_potmsgset.py (+2/-4)
lib/lp/translations/tests/test_translatablemessage.py (+24/-15)
To merge this branch: bzr merge lp:~jtv/launchpad/recife-kill-updatetranslation-1
Reviewer Review Type Date Requested Status
Graham Binns (community) code 2010-11-16 Approve on 2010-11-16
Review via email: mp+40948@code.launchpad.net

Commit Message

Replace some updateTranslation calls.

Description of the Change

= Kill updateTranslation, Part 1 =

In our Recife feature branch we're finally getting rid of the convoluted monstrosity that is POTMsgSet.updateTranslation. In a way it's tragic: this monster is a helpful one, and that is why we can't afford its upkeep. It just does too much.

In this branch I remove a few uses of updateTranslation from non-test code, though not yet all. Here's a quick walk-through of the diff:

TestResetTranslations replaces a stretch of doctest. It's more setup than test, but even so I think it's an improvement. Going from "story" style to "unit" style also takes away the need to produce a sequence of controlled timestamps: all we need to do is make sure we have a translation older than "now(UTC)" to start out with.

Then, you'll notice my stub implementation of getCurrentTranslation. Danilo currently has another branch in review that implements this in a more permanent way, and mine is marked as temporary. It's just good enough to pass tests in the current state, until we move more methods over to the new model. (This is a feature branch, so there's no production environment to worry about).

What follows is mostly replacing calls to old-style methods with ones to new-style methods.

The tests at the end may confuse you. We renamed some old TranslationMessage attributes to is_current_ubuntu and is_current_upstream respectively, but the automated replacements didn't always leave us with the right one of the two—especially for upstream-side translations, which is what the LaunchpadObjectFactory tends to give us by default. For those we often end up working with is_current_ubuntu when is_current_upstream is really the appropriate flag.

To test this, it's really not good enough to run anything less than:
{{{
./bin/test -vvc lp.translations.tests
}}}

The Q/A will happen along with the rest of the feature branch. I did some lint checks, but at the moment (after commit & push) "make lint" says it doesn't detect any changes so I can't produce a final listing.

Jeroen

To post a comment you must log in.
Graham Binns (gmb) wrote :

From IRC:

<gmb> jtv: Please add some comments or docstrings to the start of your unit test functions. Other than that, r=me.

review: Approve (code)

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/test_translationmessage_view.py'
2--- lib/lp/translations/browser/tests/test_translationmessage_view.py 2010-11-04 09:32:28 +0000
3+++ lib/lp/translations/browser/tests/test_translationmessage_view.py 2010-11-16 14:27:09 +0000
4@@ -11,20 +11,28 @@
5 )
6
7 import pytz
8+from zope.security.proxy import removeSecurityProxy
9
10+from canonical.launchpad.webapp.publisher import canonical_url
11 from canonical.launchpad.webapp.servers import LaunchpadTestRequest
12-from canonical.testing.layers import ZopelessDatabaseLayer
13+from canonical.testing.layers import (
14+ DatabaseFunctionalLayer,
15+ ZopelessDatabaseLayer,
16+ )
17 from lp.app.errors import UnexpectedFormData
18 from lp.testing import (
19 anonymous_logged_in,
20 person_logged_in,
21 TestCaseWithFactory,
22 )
23+from lp.testing.views import create_view
24+from lp.translations.enums import TranslationPermission
25 from lp.translations.browser.translationmessage import (
26 CurrentTranslationMessagePageView,
27 CurrentTranslationMessageView,
28 )
29 from lp.translations.interfaces.translationsperson import ITranslationsPerson
30+from lp.translations.publisher import TranslationsLayer
31
32
33 class TestCurrentTranslationMessage_can_dismiss(TestCaseWithFactory):
34@@ -175,6 +183,97 @@
35 self._assertConfirmEmptyPluralPackaged(True, False, False, False)
36
37
38+class TestResetTranslations(TestCaseWithFactory):
39+ """Test resetting of the current translation.
40+
41+ A reviewer can reset a current translation by submitting an empty
42+ translation and forcing it to be a suggestion.
43+
44+ :ivar pofile: A `POFile` for an Ubuntu source package.
45+ :ivar current_translation: A current `TranslationMessage` in `POFile`,
46+ submitted and reviewed sometime in the past.
47+ """
48+ layer = DatabaseFunctionalLayer
49+
50+ def setUp(self):
51+ super(TestResetTranslations, self).setUp()
52+ package = self.factory.makeSourcePackage()
53+ template = self.factory.makePOTemplate(
54+ distroseries=package.distroseries,
55+ sourcepackagename=package.sourcepackagename)
56+ self.pofile = self.factory.makePOFile(potemplate=template)
57+ self.current_translation = self.factory.makeCurrentTranslationMessage(
58+ pofile=self.pofile)
59+ self.current_translation.setPOFile(self.pofile)
60+
61+ naked_tm = removeSecurityProxy(self.current_translation)
62+ naked_tm.date_created -= timedelta(1)
63+ naked_tm.date_reviewed = naked_tm.date_created
64+
65+ def closeTranslations(self):
66+ """Disallow editing of `self.pofile` translations by regular users."""
67+ policy = removeSecurityProxy(
68+ self.pofile.potemplate.getTranslationPolicy())
69+ policy.translationpermission = TranslationPermission.CLOSED
70+
71+ def getLocalSuggestions(self):
72+ """Get local suggestions for `self.current_translation`."""
73+ return list(
74+ self.current_translation.potmsgset.getLocalTranslationMessages(
75+ self.pofile.potemplate, self.pofile.language))
76+
77+ def submitForcedEmptySuggestion(self):
78+ """Submit an empty suggestion for `self.current_translation`."""
79+ empty_translation = u''
80+
81+ msgset_id = 'msgset_' + str(self.current_translation.potmsgset.id)
82+ msgset_id_lang = msgset_id + '_' + self.pofile.language.code
83+ widget_id_base = msgset_id_lang + '_translation_0_'
84+
85+ form = {
86+ 'lock_timestamp': datetime.now(pytz.utc).isoformat(),
87+ 'alt': None,
88+ msgset_id: None,
89+ widget_id_base + 'radiobutton': widget_id_base + 'new',
90+ widget_id_base + 'new': empty_translation,
91+ 'submit_translations': 'Save &amp; Continue',
92+ msgset_id_lang + '_needsreview': 'force_suggestion',
93+ }
94+
95+ url = canonical_url(self.current_translation) + '/+translate'
96+ view = create_view(
97+ self.current_translation, '+translate', form=form,
98+ layer=TranslationsLayer, server_url=url)
99+ view.request.method = 'POST'
100+ view.initialize()
101+
102+ def test_disables_current_translation(self):
103+ # Resetting the current translation clears its "current" flag.
104+ self.assertTrue(self.current_translation.is_current_ubuntu)
105+ with person_logged_in(self.factory.makePerson()):
106+ self.submitForcedEmptySuggestion()
107+ self.assertFalse(self.current_translation.is_current_ubuntu)
108+
109+ def test_turns_current_translation_into_suggestion(self):
110+ # Resetting the current translation demotes it back to the
111+ # status of a suggestion.
112+ self.assertEqual([], self.getLocalSuggestions())
113+ with person_logged_in(self.factory.makePerson()):
114+ self.submitForcedEmptySuggestion()
115+ self.assertEqual(
116+ [self.current_translation], self.getLocalSuggestions())
117+
118+ def test_unprivileged_user_cannot_reset(self):
119+ # When a user without editing privileges on the translation
120+ # submits an empty suggestion, that does not clear the
121+ # translation.
122+ self.closeTranslations()
123+ self.assertTrue(self.current_translation.is_current_ubuntu)
124+ with person_logged_in(self.factory.makePerson()):
125+ self.submitForcedEmptySuggestion()
126+ self.assertTrue(self.current_translation.is_current_ubuntu)
127+
128+
129 class TestCurrentTranslationMessagePageView(TestCaseWithFactory):
130 """Test `CurrentTranslationMessagePageView` and its base class."""
131
132
133=== modified file 'lib/lp/translations/browser/tests/translationmessage-views.txt'
134--- lib/lp/translations/browser/tests/translationmessage-views.txt 2010-11-09 06:55:07 +0000
135+++ lib/lp/translations/browser/tests/translationmessage-views.txt 2010-11-16 14:27:09 +0000
136@@ -648,100 +648,3 @@
137 False
138 >>> subview.shared_translationmessage == translationmessage
139 True
140-
141-
142-Resetting current translation
143------------------------------
144-
145-The current translation can be reset by submitting an empty translation
146-or a translation similar to the current one while forcing suggestions.
147-
148-We need to force timestamp update, since suggestions are submitted too fast.
149-
150- >>> from lp.translations.enums import TranslationPermission
151- >>> pofile = factory.makePOFile('es')
152- >>> potemplate = pofile.potemplate
153- >>> potmsgset = factory.makePOTMsgSet(potemplate, sequence=1)
154- >>> translationmessage = factory.makeTranslationMessage(
155- ... potmsgset=potmsgset, pofile=pofile,
156- ... translations = [u"A shared translation"])
157- >>> translationmessage.setPOFile(pofile)
158- >>> product = potemplate.productseries.product
159- >>> product.translationpermission = TranslationPermission.CLOSED
160- >>> year_tick = 2100
161- >>> def submit_translation(
162- ... translationmessage, translation, force_suggestion=False):
163- ... global year_tick
164- ... potmsgset = translationmessage.potmsgset
165- ... server_url = '/'.join(
166- ... [canonical_url(translationmessage), '+translate'])
167- ... now = (str(year_tick) +
168- ... datetime.now(UTC).strftime('-%m-%dT%H:%M:%S+00:00'))
169- ... year_tick += 1
170- ... msgset_id = 'msgset_' + str(potmsgset.id)
171- ... language_code = translationmessage.language.code
172- ... msgset_id_lang = msgset_id + '_' + language_code
173- ... form = {
174- ... 'lock_timestamp': now,
175- ... 'alt': None,
176- ... msgset_id : None,
177- ... msgset_id_lang + '_translation_0_radiobutton':
178- ... msgset_id_lang + '_translation_0_new',
179- ... msgset_id_lang + '_translation_0_new': translation,
180- ... 'submit_translations': 'Save &amp; Continue'}
181- ... if force_suggestion:
182- ... form[msgset_id_lang + '_needsreview'] = 'force_suggestion'
183- ... tm_view = create_view(
184- ... translationmessage, "+translate", form=form,
185- ... layer=TranslationsLayer, server_url=server_url)
186- ... tm_view.request.method = 'POST'
187- ... tm_view.initialize()
188-
189- >>> def print_current_translation(potmsgset, pofile):
190- ... current_translation = potmsgset.getCurrentTranslationMessage(
191- ... pofile.potemplate, pofile.language)
192- ... if current_translation is not None:
193- ... print current_translation.translations
194-
195- >>> def print_local_suggestions(potmsgset, pofile):
196- ... import operator
197- ... local = sorted(
198- ... potmsgset.getLocalTranslationMessages(
199- ... pofile.potemplate, pofile.language),
200- ... key=operator.attrgetter("date_created"),
201- ... reverse=True)
202- ... for suggestion in local:
203- ... print suggestion.translations
204-
205-We will make an initial translation.
206-
207- >>> submit_translation(
208- ... translationmessage, u'Foo')
209- >>> print_local_suggestions(potmsgset, pofile)
210- >>> print_current_translation(potmsgset, pofile)
211- [u'Foo']
212-
213-Forcing suggestions while submitting an empty translation will reset the
214-translation for this message and all previously entered translations will be
215-listed as suggestions.
216-
217- >>> submit_translation(translationmessage, u'', force_suggestion=True)
218- >>> print_local_suggestions(potmsgset, pofile)
219- [u'Foo']
220- [u'A shared translation']
221- >>> print_current_translation(potmsgset, pofile)
222-
223-Only users with edit rights are allowed to reset a translation.
224-
225- >>> submit_translation(translationmessage, u'New current translation')
226- >>> print_current_translation(potmsgset, pofile)
227- [u'New current translation']
228- >>> print_local_suggestions(potmsgset, pofile)
229- >>> login('no-priv@canonical.com')
230- >>> submit_translation(
231- ... translationmessage, u'New current translation',
232- ... force_suggestion=True)
233- >>> login('carlos@canonical.com')
234- >>> print_local_suggestions(potmsgset, pofile)
235- >>> print_current_translation(potmsgset, pofile)
236- [u'New current translation']
237
238=== modified file 'lib/lp/translations/interfaces/potmsgset.py'
239--- lib/lp/translations/interfaces/potmsgset.py 2010-11-04 09:32:28 +0000
240+++ lib/lp/translations/interfaces/potmsgset.py 2010-11-16 14:27:09 +0000
241@@ -156,6 +156,12 @@
242 Diverged messages are preferred.
243 """
244
245+ def getCurrentTranslation(potemplate, language):
246+ # XXX JeroenVermeulen 2010-11-16: Stub. To be replaced with
247+ # Danilo's simultaneous work. If you see both definitions, this
248+ # is the one you should drop.
249+ """Retrieve a current `TranslationMessage`."""
250+
251 def getSharedTranslationMessage(language):
252 """Returns a shared TranslationMessage."""
253
254
255=== modified file 'lib/lp/translations/model/potmsgset.py'
256--- lib/lp/translations/model/potmsgset.py 2010-11-15 16:25:05 +0000
257+++ lib/lp/translations/model/potmsgset.py 2010-11-16 14:27:09 +0000
258@@ -322,6 +322,15 @@
259 return self._getUsedTranslationMessage(
260 potemplate, language, current=False)
261
262+ def getCurrentTranslation(self, potemplate, language):
263+ # XXX JeroenVermeulen 2010-11-16: Stub. To be replaced with
264+ # Danilo's simultaneous work. If you see both definitions, this
265+ # is the one you should drop.
266+ """See `IPOTMsgSet`."""
267+ traits = getUtility(ITranslationSideTraitsSet).getTraits(
268+ potemplate.translation_side)
269+ return traits.getCurrentMessage(self, potemplate, language)
270+
271 def getSharedTranslationMessage(self, language):
272 """See `IPOTMsgSet`."""
273 return self._getUsedTranslationMessage(
274@@ -341,7 +350,7 @@
275 "msgstr%(form)d IS NOT NULL", "OR")
276 query += " AND (%s)" % msgstr_clause
277 if include_dismissed != include_unreviewed:
278- current = self.getCurrentTranslationMessage(potemplate, language)
279+ current = self.getCurrentTranslation(potemplate, language)
280 if current is not None:
281 if current.date_reviewed is None:
282 comparing_date = current.date_created
283@@ -1024,17 +1033,18 @@
284
285 def dismissAllSuggestions(self, pofile, reviewer, lock_timestamp):
286 """See `IPOTMsgSet`."""
287- assert(lock_timestamp is not None)
288- current = self.getCurrentTranslationMessage(
289- self.potemplate, pofile.language)
290+ current = self.getCurrentTranslation(
291+ pofile.potemplate, pofile.language)
292 if current is None:
293- # Create an empty translation message.
294- current = self.updateTranslation(
295- pofile, reviewer, [], False, lock_timestamp)
296+ # Create or activate an empty translation message.
297+ current = self.setCurrentTranslation(
298+ pofile, reviewer, {}, RosettaTranslationOrigin.ROSETTAWEB,
299+ lock_timestamp=lock_timestamp)
300 else:
301 # Check for translation conflicts and update review fields.
302 self._maybeRaiseTranslationConflict(current, lock_timestamp)
303- current.markReviewed(reviewer, lock_timestamp)
304+ current.markReviewed(reviewer, lock_timestamp)
305+ assert self.getCurrentTranslation(pofile.potemplate, pofile.language)
306
307 def _nameMessageStatus(self, message, translation_side_traits):
308 """Figure out the decision-matrix status of a message.
309
310=== modified file 'lib/lp/translations/model/translatablemessage.py'
311--- lib/lp/translations/model/translatablemessage.py 2010-08-23 08:35:29 +0000
312+++ lib/lp/translations/model/translatablemessage.py 2010-11-16 14:27:09 +0000
313@@ -44,9 +44,8 @@
314 self.potemplate = pofile.potemplate
315 self.language = pofile.language
316
317- self._current_translation = (
318- self.potmsgset.getCurrentTranslationMessage(
319- self.potemplate, self.language))
320+ self._current_translation = self.potmsgset.getCurrentTranslation(
321+ self.potemplate, self.language)
322
323 @property
324 def is_obsolete(self):
325
326=== modified file 'lib/lp/translations/tests/test_potmsgset.py'
327--- lib/lp/translations/tests/test_potmsgset.py 2010-11-04 09:32:28 +0000
328+++ lib/lp/translations/tests/test_potmsgset.py 2010-11-16 14:27:09 +0000
329@@ -24,7 +24,6 @@
330 from lp.registry.interfaces.person import IPersonSet
331 from lp.registry.interfaces.product import IProductSet
332 from lp.testing import TestCaseWithFactory
333-from lp.services.worlddata.interfaces.language import ILanguageSet
334 from lp.translations.interfaces.potemplate import IPOTemplateSet
335 from lp.translations.interfaces.potmsgset import (
336 POTMsgSetInIncompatibleTemplatesError,
337@@ -305,8 +304,7 @@
338
339 # Setting one of the suggestions as current will leave
340 # them both 'reviewed' and thus hidden.
341- current_translation = self.factory.makeSharedTranslationMessage(
342- pofile=sr_pofile, potmsgset=self.potmsgset)
343+ shared_suggestion.approve(sr_pofile, self.factory.makePerson())
344 self.assertContentEqual(
345 [],
346 self.potmsgset.getLocalTranslationMessages(
347@@ -870,7 +868,7 @@
348 self.potmsgset.dismissAllSuggestions(
349 self.pofile, self.factory.makePerson(), self.now())
350 transaction.commit()
351- current = self.potmsgset.getCurrentTranslationMessage(
352+ current = self.potmsgset.getCurrentTranslation(
353 self.potemplate, self.pofile.language)
354 self.assertNotEqual(None, current)
355 self.assertEqual([None], current.translations)
356
357=== modified file 'lib/lp/translations/tests/test_translatablemessage.py'
358--- lib/lp/translations/tests/test_translatablemessage.py 2010-11-16 06:15:13 +0000
359+++ lib/lp/translations/tests/test_translatablemessage.py 2010-11-16 14:27:09 +0000
360@@ -13,6 +13,7 @@
361 import pytz
362 import transaction
363 from zope.component import getUtility
364+from zope.security.proxy import removeSecurityProxy
365
366 from canonical.testing.layers import ZopelessDatabaseLayer
367 from lp.app.enums import ServiceUsage
368@@ -46,21 +47,29 @@
369 def _createTranslation(self, translation=None, is_current_ubuntu=False,
370 is_current_upstream=False, is_diverged=False,
371 date_updated=None):
372- is_suggestion = not (
373- is_current_ubuntu or is_current_upstream or is_diverged)
374 if translation is not None:
375 translation = [translation]
376- if is_suggestion:
377- return self.factory.makeSuggestion(
378+
379+ if is_current_upstream:
380+ message = self.factory.makeCurrentTranslationMessage(
381+ pofile=self.pofile, potmsgset=self.potmsgset,
382+ translations=translation,
383+ current_other=is_current_ubuntu,
384+ diverged=is_diverged)
385+ if date_updated is not None:
386+ naked_message = removeSecurityProxy(message)
387+ naked_message.date_created = date_updated
388+ naked_message.date_reviewed = date_updated
389+ else:
390+ message = self.factory.makeSuggestion(
391 pofile=self.pofile, potmsgset=self.potmsgset,
392 translations=translation, date_created=date_updated)
393- else:
394- return self.factory.makeTranslationMessage(
395- pofile=self.pofile, potmsgset=self.potmsgset,
396- translations=translation,
397- is_current_upstream=is_current_upstream,
398- force_diverged=is_diverged,
399- date_updated=date_updated)
400+ message.is_current_ubuntu = is_current_ubuntu
401+ self.assertFalse(
402+ is_diverged,
403+ "Diverging message to a template it's not current in.")
404+
405+ return message
406
407
408 class TestTranslatableMessage(TestTranslatableMessageBase,
409@@ -88,8 +97,8 @@
410 self.assertTrue(message.is_untranslated)
411
412 def test_is_current_diverged(self):
413- translation = self._createTranslation(is_current_ubuntu=True,
414- is_diverged=True)
415+ translation = self._createTranslation(
416+ is_current_upstream=True, is_diverged=True)
417 message = TranslatableMessage(self.potmsgset, self.pofile)
418 self.assertTrue(message.is_current_diverged)
419
420@@ -118,7 +127,7 @@
421 self.assertEqual(3, message.number_of_plural_forms)
422
423 def test_getCurrentTranslation(self):
424- translation = self._createTranslation(is_current_ubuntu=True)
425+ translation = self._createTranslation(is_current_upstream=True)
426 message = TranslatableMessage(self.potmsgset, self.pofile)
427 current = message.getCurrentTranslation()
428 self.assertEqual(translation, current)
429@@ -193,7 +202,7 @@
430 self.now = self.gen_now().next
431 self.suggestion1 = self._createTranslation(date_updated=self.now())
432 self.current = self._createTranslation(
433- is_current_ubuntu=True, date_updated=self.now())
434+ is_current_upstream=True, date_updated=self.now())
435 self.suggestion2 = self._createTranslation(date_updated=self.now())
436 self.message = TranslatableMessage(self.potmsgset, self.pofile)
437

Subscribers

People subscribed via source and target branches