Merge lp:~henninge/launchpad/recife-poimport into lp:~launchpad/launchpad/recife
- recife-poimport
- Merge into recife
| Status: | Merged |
|---|---|
| Merged at revision: | 9169 |
| Proposed branch: | lp:~henninge/launchpad/recife-poimport |
| Merge into: | lp:~launchpad/launchpad/recife |
| Diff against target: |
1034 lines (+425/-383) 3 files modified
lib/lp/testing/factory.py (+10/-0) lib/lp/translations/doc/poimport-script.txt (+342/-0) lib/lp/translations/doc/poimport.txt (+73/-383) |
| To merge this branch: | bzr merge lp:~henninge/launchpad/recife-poimport |
| Related bugs: |
| Reviewer | Review Type | Date Requested | Status |
|---|---|---|---|
| Māris Fogels (community) | 2010-09-21 | Approve on 2010-09-21 | |
|
Review via email:
|
|||
Commit Message
Description of the Change
== Details ==
This branch was split off the work for bug 611674. There were two goals:
- Make the test use a source package because a lot of code still hardwired to "is_current_
- Update the test to not use (or at least not as much) sample data.
Because the file was quite big, I split off the tests that run the import script into poimport-
Since the new model is explicitly referring to Ubuntu in many places, I found it useful to have a "makeUbuntuDist
== Test ==
bin/test -vvcm lp.translations -t poimport.txt -t poimport-script.txt
Preview Diff
| 1 | === modified file 'lib/lp/testing/factory.py' |
| 2 | --- lib/lp/testing/factory.py 2010-09-08 02:19:20 +0000 |
| 3 | +++ lib/lp/testing/factory.py 2010-09-21 16:23:42 +0000 |
| 4 | @@ -1848,8 +1848,18 @@ |
| 5 | series.status = status |
| 6 | return ProxyFactory(series) |
| 7 | |
| 8 | + def makeUbuntuDistroRelease(self, version=None, |
| 9 | + status=SeriesStatus.DEVELOPMENT, |
| 10 | + parent_series=None, name=None, |
| 11 | + displayname=None): |
| 12 | + """Short cut to use the celebrity 'ubuntu' as the distribution.""" |
| 13 | + ubuntu = getUtility(ILaunchpadCelebrities).ubuntu |
| 14 | + return self.makeDistroRelease( |
| 15 | + ubuntu, version, status, parent_series, name, displayname) |
| 16 | + |
| 17 | # Most people think of distro releases as distro series. |
| 18 | makeDistroSeries = makeDistroRelease |
| 19 | + makeUbuntuDistroSeries = makeUbuntuDistroRelease |
| 20 | |
| 21 | def makeDistroSeriesDifference( |
| 22 | self, derived_series=None, source_package_name_str=None, |
| 23 | |
| 24 | === added file 'lib/lp/translations/doc/poimport-script.txt' |
| 25 | --- lib/lp/translations/doc/poimport-script.txt 1970-01-01 00:00:00 +0000 |
| 26 | +++ lib/lp/translations/doc/poimport-script.txt 2010-09-21 16:23:42 +0000 |
| 27 | @@ -0,0 +1,342 @@ |
| 28 | +Import Script |
| 29 | +============= |
| 30 | + |
| 31 | +The imports are performed by a dedicated cron script. |
| 32 | + |
| 33 | +A template and two pofile will be imported. |
| 34 | + |
| 35 | + >>> potemplate_header = r""" |
| 36 | + ... msgid "" |
| 37 | + ... msgstr "" |
| 38 | + ... "POT-Creation-Date: 2004-07-11 16:16+0900\n" |
| 39 | + ... "Content-Type: text/plain; charset=CHARSET\n" |
| 40 | + ... "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" |
| 41 | + ... |
| 42 | + ... """ |
| 43 | + |
| 44 | + >>> pofile_header = r""" |
| 45 | + ... msgid "" |
| 46 | + ... msgstr "" |
| 47 | + ... "PO-Revision-Date: 2005-06-03 20:41+0100\n" |
| 48 | + ... "Last-Translator: Foo <no-priv@canonical.com>\n" |
| 49 | + ... "Content-Type: text/plain; charset=UTF-8\n" |
| 50 | + ... "Plural-Forms: nplurals=2; plural=(n!=1);\n" |
| 51 | + ... |
| 52 | + ... """ |
| 53 | + |
| 54 | + >>> po_content = r""" |
| 55 | + ... #: test.c:13 |
| 56 | + ... msgid "baz" |
| 57 | + ... msgstr "%s" |
| 58 | + ... |
| 59 | + ... #, c-format |
| 60 | + ... msgid "Foo %%s" |
| 61 | + ... msgstr "%s" |
| 62 | + ... |
| 63 | + ... #, c-format |
| 64 | + ... msgid "Singular %%d" |
| 65 | + ... msgid_plural "Plural %%d" |
| 66 | + ... msgstr[0] "%s" |
| 67 | + ... msgstr[1] "%s" |
| 68 | + ... |
| 69 | + ... msgid "translator-credits" |
| 70 | + ... msgstr "%s" |
| 71 | + ... """ |
| 72 | + |
| 73 | + >>> potemplate_content = potemplate_header + po_content % (('',) * 5) |
| 74 | + >>> pofile_eo_content = pofile_header + po_content % ( |
| 75 | + ... "baz eo", "Foo eo %s", "Singular eo %s", "Plural eo %s", |
| 76 | + ... "helpful-eo@example.com") |
| 77 | + >>> pofile_nl_content = pofile_header + po_content % ( |
| 78 | + ... "baz nl", "Foo nl %s", "Singular nl %s", "Plural nl %s", |
| 79 | + ... "helpful-nl@example.com") |
| 80 | + |
| 81 | +There is annoying sample data in the queue that needs to be removed. |
| 82 | + |
| 83 | + >>> from lp.translations.interfaces.translationimportqueue import ( |
| 84 | + ... ITranslationImportQueue, RosettaImportStatus) |
| 85 | + >>> queue = getUtility(ITranslationImportQueue) |
| 86 | + >>> for entry in queue: |
| 87 | + ... queue.remove(entry) |
| 88 | + |
| 89 | +The files have been uploaded to the queue for a source package and have |
| 90 | +already been approved. |
| 91 | + |
| 92 | + >>> from zope.security.proxy import removeSecurityProxy |
| 93 | + >>> distroseries = factory.makeUbuntuDistroSeries() |
| 94 | + >>> naked_distroseries = removeSecurityProxy(distroseries) |
| 95 | + >>> naked_distroseries.distribution.official_rosetta = True |
| 96 | + >>> sourcepackagename = factory.makeSourcePackageName() |
| 97 | + >>> potemplate = factory.makePOTemplate( |
| 98 | + ... distroseries=distroseries, sourcepackagename=sourcepackagename) |
| 99 | + >>> pofile_eo = potemplate.newPOFile('eo') |
| 100 | + >>> pofile_nl = potemplate.newPOFile('nl') |
| 101 | + |
| 102 | + >>> from canonical.launchpad.interfaces.launchpad import ( |
| 103 | + ... ILaunchpadCelebrities) |
| 104 | + >>> rosetta_experts = getUtility(ILaunchpadCelebrities).rosetta_experts |
| 105 | + |
| 106 | + >>> template_entry = queue.addOrUpdateEntry( |
| 107 | + ... potemplate.path, potemplate_content, True, potemplate.owner, |
| 108 | + ... distroseries=distroseries, sourcepackagename=sourcepackagename, |
| 109 | + ... potemplate=potemplate) |
| 110 | + >>> pofile_eo_entry = queue.addOrUpdateEntry( |
| 111 | + ... 'eo.po', pofile_eo_content, True, potemplate.owner, |
| 112 | + ... distroseries=distroseries, sourcepackagename=sourcepackagename, |
| 113 | + ... potemplate=potemplate, pofile=pofile_eo) |
| 114 | + >>> pofile_nl_entry = queue.addOrUpdateEntry( |
| 115 | + ... 'nl.po', pofile_nl_content, True, potemplate.owner, |
| 116 | + ... distroseries=distroseries, sourcepackagename=sourcepackagename, |
| 117 | + ... potemplate=potemplate, pofile=pofile_nl) |
| 118 | + >>> transaction.commit() |
| 119 | + |
| 120 | + >>> for entry in queue: |
| 121 | + ... entry.setStatus(RosettaImportStatus.APPROVED, rosetta_experts) |
| 122 | + >>> transaction.commit() |
| 123 | + |
| 124 | +As it happens, the administrator has blocked imports to the distroseries, e.g. |
| 125 | +because an in-database update of its translations has been scheduled |
| 126 | +and we don't want interference from queued imports while that happens. |
| 127 | +It doesn't really matter whether entries still get auto-approved, but |
| 128 | +we can't accept new translation imports just now. |
| 129 | + |
| 130 | + >>> distroseries.defer_translation_imports |
| 131 | + True |
| 132 | + |
| 133 | + >>> from canonical.launchpad.scripts import FakeLogger |
| 134 | + >>> from lp.translations.scripts.po_import import TranslationsImport |
| 135 | + >>> import email |
| 136 | + >>> from lp.services.mail import stub |
| 137 | + >>> process = TranslationsImport('poimport', test_args=[]) |
| 138 | + >>> process.logger = FakeLogger() |
| 139 | + >>> process.main() |
| 140 | + DEBUG Starting the import process. |
| 141 | + INFO No requests pending. |
| 142 | + |
| 143 | +When imports are allowed, the import script can do its work. |
| 144 | + |
| 145 | + >>> naked_distroseries.defer_translation_imports = False |
| 146 | + |
| 147 | + >>> process = TranslationsImport('poimport', test_args=[]) |
| 148 | + >>> process.logger = FakeLogger() |
| 149 | + >>> process.main() |
| 150 | + DEBUG Starting the import process. |
| 151 | + INFO Importing: Template ... |
| 152 | + INFO Importing: Esperanto (eo) ... of ... |
| 153 | + INFO Importing: Dutch (nl) ... of ... |
| 154 | + INFO Import requests completed. |
| 155 | + DEBUG Finished the import process. |
| 156 | + |
| 157 | +The import script also generates an email similar to the ones we saw |
| 158 | +composed before, but also sends it. |
| 159 | + |
| 160 | + >>> len(stub.test_emails) |
| 161 | + 1 |
| 162 | + |
| 163 | + >>> from_addr, to_addrs, raw_message = stub.test_emails.pop() |
| 164 | + >>> msg = email.message_from_string(raw_message) |
| 165 | + >>> print msg["Subject"] |
| 166 | + Translation template import - ... |
| 167 | + |
| 168 | + >>> print msg.get_payload(decode=True) |
| 169 | + Hello ..., |
| 170 | + <BLANKLINE> |
| 171 | + On ..., you uploaded a translation |
| 172 | + template for ... in Launchpad. |
| 173 | + <BLANKLINE> |
| 174 | + The template has now been imported successfully. |
| 175 | + <BLANKLINE> |
| 176 | + Thank you, |
| 177 | + <BLANKLINE> |
| 178 | + The Launchpad team |
| 179 | + |
| 180 | +The entries that remain in the queue as "imported" age over time. |
| 181 | + |
| 182 | + >>> import datetime |
| 183 | + >>> for entry in queue: |
| 184 | + ... removeSecurityProxy(entry).date_status_changed -= ( |
| 185 | + ... datetime.timedelta(days=30)) |
| 186 | + |
| 187 | + |
| 188 | +Now the queue gardener runs. This can happen anytime, since it's |
| 189 | +asynchronous to the po-import script. The script tries to approve any |
| 190 | +entries that have not been approved, but look like they could be, |
| 191 | +without human intervention. This involves a bit of guesswork about what |
| 192 | +the imported file is and where it belongs. It similarly blocks entries |
| 193 | +that it thinks should be blocked, and also purges deleted or completed |
| 194 | +entries from the queue. Running at this point, all it does is purge the |
| 195 | +two hand-approved Welsh translations that have just been imported. |
| 196 | + |
| 197 | + >>> import logging |
| 198 | + >>> from lp.testing.logger import MockLogger |
| 199 | + >>> from lp.translations.scripts.import_queue_gardener import ( |
| 200 | + ... ImportQueueGardener) |
| 201 | + >>> process = ImportQueueGardener('approver', test_args=[]) |
| 202 | + >>> process.logger = MockLogger() |
| 203 | + >>> process.logger.setLevel(logging.INFO) |
| 204 | + >>> process.main() |
| 205 | + log> Removed 3 entries from the queue. |
| 206 | + >>> transaction.commit() |
| 207 | + |
| 208 | +If users upload two versions of the same file, they are imported in the |
| 209 | +order in which they were uploaded. |
| 210 | + |
| 211 | + >>> import pytz |
| 212 | + >>> UTC = pytz.timezone('UTC') |
| 213 | + >>> first_pofile_content = r''' |
| 214 | + ... msgid "" |
| 215 | + ... msgstr "" |
| 216 | + ... "PO-Revision-Date: 2005-06-04 20:41+0100\n" |
| 217 | + ... "Last-Translator: Foo <no-priv@canonical.com>\n" |
| 218 | + ... "Content-Type: text/plain; charset=UTF-8\n" |
| 219 | + ... "X-Rosetta-Export-Date: %s\n" |
| 220 | + ... |
| 221 | + ... msgid "Foo %%s" |
| 222 | + ... msgstr "Bar" |
| 223 | + ... |
| 224 | + ... msgid "translator-credits" |
| 225 | + ... msgstr "The world will never know." |
| 226 | + ... ''' % datetime.datetime.now(UTC).isoformat() |
| 227 | + |
| 228 | + >>> second_pofile_content = r''' |
| 229 | + ... msgid "" |
| 230 | + ... msgstr "" |
| 231 | + ... "PO-Revision-Date: 2005-06-04 21:41+0100\n" |
| 232 | + ... "Last-Translator: Jordi Mallach <jordi@canonical.com>\n" |
| 233 | + ... "Content-Type: text/plain; charset=UTF-8\n" |
| 234 | + ... "X-Rosetta-Export-Date: %s\n" |
| 235 | + ... |
| 236 | + ... msgid "Foo %%s" |
| 237 | + ... msgstr "Bars" |
| 238 | + ... |
| 239 | + ... msgid "translator-credits" |
| 240 | + ... msgstr "I'd like to thank John, Kathy, my pot plants, and all the..." |
| 241 | + ... ''' % datetime.datetime.now(UTC).isoformat() |
| 242 | + |
| 243 | +Attach the first version of the file. |
| 244 | + |
| 245 | + >>> entry = queue.addOrUpdateEntry( |
| 246 | + ... pofile_eo.path, first_pofile_content, False, rosetta_experts, |
| 247 | + ... sourcepackagename=sourcepackagename, distroseries=distroseries) |
| 248 | + >>> transaction.commit() |
| 249 | + |
| 250 | +It's in the queue now. |
| 251 | + |
| 252 | + >>> queue.countEntries() |
| 253 | + 1 |
| 254 | + |
| 255 | +For the second version, we need a new importer. |
| 256 | + |
| 257 | + >>> importer_person = factory.makePerson() |
| 258 | + |
| 259 | +Attach the second version of the file. |
| 260 | + |
| 261 | + >>> entry = queue.addOrUpdateEntry( |
| 262 | + ... pofile_eo.path, second_pofile_content, False, importer_person, |
| 263 | + ... sourcepackagename=sourcepackagename, distroseries=distroseries) |
| 264 | + >>> transaction.commit() |
| 265 | + |
| 266 | +It's in the queue now. |
| 267 | + |
| 268 | + >>> queue.countEntries() |
| 269 | + 2 |
| 270 | + >>> print entry.status.name |
| 271 | + NEEDS_REVIEW |
| 272 | + |
| 273 | +The queue gardener runs again. This time it sees the two submitted |
| 274 | +translations and approves them for import based on some heuristic |
| 275 | +intelligence. |
| 276 | + |
| 277 | + >>> process = ImportQueueGardener('approver', test_args=[]) |
| 278 | + >>> process.logger = MockLogger() |
| 279 | + >>> process.logger.setLevel(logging.INFO) |
| 280 | + >>> process.main() |
| 281 | + log> The automatic approval system approved some entries. |
| 282 | + >>> print entry.status.name |
| 283 | + APPROVED |
| 284 | + >>> from canonical.launchpad.ftests import syncUpdate |
| 285 | + >>> syncUpdate(entry) |
| 286 | + |
| 287 | +Now that these submissions have been approved, the next run of the |
| 288 | +import script picks them up and processes them. |
| 289 | + |
| 290 | + >>> process = TranslationsImport('poimport', test_args=[]) |
| 291 | + >>> process.logger = FakeLogger() |
| 292 | + >>> process.main() |
| 293 | + DEBUG Starting the import process. |
| 294 | + INFO Importing: Esperanto (eo) ... of ... |
| 295 | + INFO Importing: Esperanto (eo) ... of ... |
| 296 | + INFO Import requests completed. |
| 297 | + DEBUG Finished the import process. |
| 298 | + |
| 299 | + >>> print entry.status.name |
| 300 | + IMPORTED |
| 301 | + >>> syncUpdate(entry) |
| 302 | + |
| 303 | +And there are no more entries to import |
| 304 | + |
| 305 | + >>> queue.getFirstEntryToImport() is None |
| 306 | + True |
| 307 | + |
| 308 | +We've imported a new translation for "Foo %s." |
| 309 | + |
| 310 | + >>> from lp.services.worlddata.interfaces.language import ILanguageSet |
| 311 | + >>> esperanto = getUtility(ILanguageSet).getLanguageByCode('eo') |
| 312 | + >>> foos = potemplate['Foo %s'].getLocalTranslationMessages( |
| 313 | + ... potemplate, esperanto) |
| 314 | + >>> sorted([foo.msgstr0.translation for foo in foos]) |
| 315 | + [u'Bar', u'Bars'] |
| 316 | + |
| 317 | +Since this last upload was not the upstream one, however, its credits |
| 318 | +message translations were ignored. |
| 319 | + |
| 320 | + >>> potmsgset = pofile_eo.potemplate.getPOTMsgSetByMsgIDText( |
| 321 | + ... u'translator-credits') |
| 322 | + >>> message = potmsgset.getCurrentTranslationMessage( |
| 323 | + ... pofile_eo.potemplate, pofile_eo.language) |
| 324 | + >>> message.msgstr0.translation |
| 325 | + u'helpful-eo@example.com' |
| 326 | + >>> list(potemplate['translator-credits'].getLocalTranslationMessages( |
| 327 | + ... potemplate, esperanto)) |
| 328 | + [] |
| 329 | + |
| 330 | + |
| 331 | +No Contact Address |
| 332 | +------------------ |
| 333 | + |
| 334 | +Not every user has a valid email address. For instance, Kermit the |
| 335 | +Hermit has none at the moment. |
| 336 | + |
| 337 | + >>> from canonical.launchpad.interfaces.emailaddress import ( |
| 338 | + ... EmailAddressStatus) |
| 339 | + >>> from canonical.launchpad.helpers import get_contact_email_addresses |
| 340 | + >>> hermit = factory.makePerson( |
| 341 | + ... name='hermit', email_address_status=EmailAddressStatus.OLD) |
| 342 | + |
| 343 | + >>> len(get_contact_email_addresses(hermit)) |
| 344 | + 0 |
| 345 | + |
| 346 | +Kermit uploads a translation, which gets approved. |
| 347 | + |
| 348 | + >>> pofile = factory.makePOFile('lo', potemplate) |
| 349 | + >>> entry = queue.addOrUpdateEntry( |
| 350 | + ... 'lo.po', 'Invalid content', True, hermit, |
| 351 | + ... pofile=pofile, potemplate=potemplate, |
| 352 | + ... distroseries=distroseries, sourcepackagename=sourcepackagename) |
| 353 | + >>> entry.setStatus(RosettaImportStatus.APPROVED, rosetta_experts) |
| 354 | + >>> transaction.commit() |
| 355 | + |
| 356 | +The import fails. The importer would like to send Kermit an email about |
| 357 | +this, but is unable to. This is unfortunate, but does not faze the |
| 358 | +importer. It completes normally. |
| 359 | + |
| 360 | + >>> process = TranslationsImport('poimport', test_args=[]) |
| 361 | + >>> process.logger = FakeLogger() |
| 362 | + >>> process.main() |
| 363 | + DEBUG Starting the import process. |
| 364 | + INFO Importing: Lao ... |
| 365 | + INFO Import requests completed. |
| 366 | + DEBUG Finished the import process. |
| 367 | + |
| 368 | + >>> print entry.status.name |
| 369 | + FAILED |
| 370 | |
| 371 | === modified file 'lib/lp/translations/doc/poimport.txt' |
| 372 | --- lib/lp/translations/doc/poimport.txt 2010-08-10 14:39:46 +0000 |
| 373 | +++ lib/lp/translations/doc/poimport.txt 2010-09-21 16:23:42 +0000 |
| 374 | @@ -1,23 +1,20 @@ |
| 375 | -= PO Imports = |
| 376 | +========== |
| 377 | +PO Imports |
| 378 | +========== |
| 379 | |
| 380 | The tale of a PO template and a PO file and how they get imported into |
| 381 | Rosetta. |
| 382 | |
| 383 | - |
| 384 | -== Test Setup == |
| 385 | +Test Setup |
| 386 | +========== |
| 387 | |
| 388 | Here are some imports we need to get this test running. |
| 389 | |
| 390 | - >>> from canonical.launchpad.ftests import syncUpdate |
| 391 | - >>> from canonical.launchpad.interfaces import ( |
| 392 | - ... ILanguageSet, ILaunchpadCelebrities, IPersonSet, IProductSet) |
| 393 | + >>> from canonical.launchpad.interfaces.launchpad import ( |
| 394 | + ... ILaunchpadCelebrities) |
| 395 | + >>> from lp.registry.interfaces.person import IPersonSet |
| 396 | >>> from lp.translations.interfaces.translationimportqueue import ( |
| 397 | ... ITranslationImportQueue, RosettaImportStatus) |
| 398 | - >>> from lp.registry.model.sourcepackagename import SourcePackageName |
| 399 | - >>> from lp.translations.model.potemplate import POTemplateSubset |
| 400 | - >>> from lp.translations.scripts.po_import import TranslationsImport |
| 401 | - >>> from lp.translations.scripts.import_queue_gardener import ( |
| 402 | - ... ImportQueueGardener) |
| 403 | >>> import datetime |
| 404 | >>> import pytz |
| 405 | >>> UTC = pytz.timezone('UTC') |
| 406 | @@ -36,18 +33,15 @@ |
| 407 | >>> login('carlos@canonical.com') |
| 408 | |
| 409 | |
| 410 | -== Importing a Template == |
| 411 | +Importing a Template |
| 412 | +==================== |
| 413 | |
| 414 | Normal procedure is to import a template, followed by translations. |
| 415 | A template is created first. After that, imports are done using the |
| 416 | POFile.importFromQueue and POTemplate.importFromQueue methods. |
| 417 | |
| 418 | - >>> from lp.registry.model.productrelease import ProductRelease |
| 419 | - >>> release = ProductRelease.get(3) |
| 420 | - >>> release.productseries.product.name |
| 421 | - u'firefox' |
| 422 | - >>> series = release.productseries |
| 423 | - >>> subset = POTemplateSubset(productseries=series) |
| 424 | + >>> distroseries = factory.makeUbuntuDistroSeries() |
| 425 | + >>> sourcepackagename = factory.makeSourcePackageName() |
| 426 | |
| 427 | Here's the person who'll be doing the import. |
| 428 | |
| 429 | @@ -56,10 +50,8 @@ |
| 430 | |
| 431 | And this is the POTemplate where the import will be done. |
| 432 | |
| 433 | - >>> potemplate = subset.new( |
| 434 | - ... name='firefox', |
| 435 | - ... translation_domain='firefox', |
| 436 | - ... path='po/firefox.pot', |
| 437 | + >>> potemplate = factory.makePOTemplate( |
| 438 | + ... distroseries=distroseries, sourcepackagename=sourcepackagename, |
| 439 | ... owner=person) |
| 440 | >>> potemplate_id = potemplate.id |
| 441 | |
| 442 | @@ -110,7 +102,8 @@ |
| 443 | >>> translation_import_queue = getUtility(ITranslationImportQueue) |
| 444 | >>> entry = translation_import_queue.addOrUpdateEntry( |
| 445 | ... potemplate.path, potemplate_contents, True, potemplate.owner, |
| 446 | - ... productseries=series, potemplate=potemplate) |
| 447 | + ... distroseries=distroseries, sourcepackagename=sourcepackagename, |
| 448 | + ... potemplate=potemplate) |
| 449 | |
| 450 | The file data is stored in the Librarian, so we have to commit the |
| 451 | transaction to make sure it's stored properly. |
| 452 | @@ -143,13 +136,13 @@ |
| 453 | |
| 454 | A successful import is confirmed by email. |
| 455 | |
| 456 | - >>> subject |
| 457 | - u'Translation template import - firefox in Mozilla Firefox trunk' |
| 458 | + >>> print subject |
| 459 | + Translation template import - ... |
| 460 | >>> print body |
| 461 | Hello Mark Shuttleworth, |
| 462 | <BLANKLINE> |
| 463 | On ..., you uploaded a translation |
| 464 | - template for firefox in Mozilla Firefox trunk in Launchpad. |
| 465 | + template for ... in Launchpad. |
| 466 | <BLANKLINE> |
| 467 | The template has now been imported successfully. |
| 468 | <BLANKLINE> |
| 469 | @@ -174,7 +167,8 @@ |
| 470 | u'test.c:13' |
| 471 | |
| 472 | |
| 473 | -=== Import Preconditions === |
| 474 | +Import Preconditions |
| 475 | +==================== |
| 476 | |
| 477 | The API for POTemplate.importFromQueue demands a translation import |
| 478 | queue entry to import. |
| 479 | @@ -195,12 +189,7 @@ |
| 480 | any other file would be an error. |
| 481 | |
| 482 | >>> entry.setStatus(RosettaImportStatus.APPROVED, rosetta_experts) |
| 483 | - >>> from lp.translations.interfaces.potemplate import IPOTemplateSet |
| 484 | - >>> other_product = getUtility(IProductSet).getByName('netapplet') |
| 485 | - >>> other_productseries = other_product.getSeries('trunk') |
| 486 | - >>> template_set = getUtility(IPOTemplateSet) |
| 487 | - >>> other_template = template_set.getPOTemplateByPathAndOrigin( |
| 488 | - ... 'po/netapplet.pot', productseries=other_productseries) |
| 489 | + >>> other_template = factory.makePOTemplate() |
| 490 | >>> other_template.importFromQueue(entry) |
| 491 | Traceback (most recent call last): |
| 492 | ... |
| 493 | @@ -208,7 +197,8 @@ |
| 494 | to. |
| 495 | |
| 496 | |
| 497 | -== Importing a Translation == |
| 498 | +Importing a Translation |
| 499 | +======================= |
| 500 | |
| 501 | Now let's get a PO file to import. |
| 502 | |
| 503 | @@ -217,8 +207,8 @@ |
| 504 | |
| 505 | By default, we got a safe path to prevent collisions with other IPOFile. |
| 506 | |
| 507 | - >>> pofile.path |
| 508 | - u'po/firefox-cy.po' |
| 509 | + >>> print pofile.path |
| 510 | + generic-string...-cy.po |
| 511 | |
| 512 | Let's override the default good path with one we know is the right one. |
| 513 | |
| 514 | @@ -236,7 +226,8 @@ |
| 515 | 1 |
| 516 | |
| 517 | |
| 518 | -=== Import With Errors === |
| 519 | +Import With Errors |
| 520 | +------------------ |
| 521 | |
| 522 | Here are the contents of the file we'll be importing. It has some |
| 523 | validation errors. |
| 524 | @@ -285,7 +276,8 @@ |
| 525 | |
| 526 | >>> entry = translation_import_queue.addOrUpdateEntry( |
| 527 | ... pofile.path, pofile_with_errors, True, person, |
| 528 | - ... productseries=series, potemplate=potemplate) |
| 529 | + ... distroseries=distroseries, sourcepackagename=sourcepackagename, |
| 530 | + ... potemplate=potemplate) |
| 531 | >>> transaction.commit() |
| 532 | |
| 533 | The guess IPOFile should be the same we already had. |
| 534 | @@ -381,7 +373,7 @@ |
| 535 | Hello Mark Shuttleworth, |
| 536 | <BLANKLINE> |
| 537 | On ..., you uploaded 5 |
| 538 | - Welsh (cy) translations for firefox in Mozilla Firefox trunk in Launchpad. |
| 539 | + Welsh (cy) translations for ... in Launchpad. |
| 540 | <BLANKLINE> |
| 541 | There were problems with 1 of these translations. |
| 542 | <BLANKLINE> |
| 543 | @@ -410,7 +402,8 @@ |
| 544 | msgstr "blah %i" |
| 545 | |
| 546 | |
| 547 | -=== Import With Warnings === |
| 548 | +Import With Warnings |
| 549 | +-------------------- |
| 550 | |
| 551 | The import may also succeed but produce syntax warnings. These need not |
| 552 | be tied to particular messages (they could be in the header, for |
| 553 | @@ -435,14 +428,14 @@ |
| 554 | ... msgid "a" |
| 555 | ... msgstr "b" |
| 556 | ... ''' % datetime.datetime.now(UTC).isoformat() |
| 557 | - >>> sumerian_pofile = potemplate.newPOFile('sux') |
| 558 | + >>> eo_pofile = potemplate.newPOFile('eo') |
| 559 | >>> warning_entry = translation_import_queue.addOrUpdateEntry( |
| 560 | - ... 'sux.po', pofile_with_warning, False, potemplate.owner, |
| 561 | - ... productseries=series, potemplate=potemplate, |
| 562 | - ... pofile=sumerian_pofile) |
| 563 | + ... 'eo.po', pofile_with_warning, False, potemplate.owner, |
| 564 | + ... distroseries=distroseries, sourcepackagename=sourcepackagename, |
| 565 | + ... potemplate=potemplate, pofile=eo_pofile) |
| 566 | >>> transaction.commit() |
| 567 | >>> warning_entry.setStatus(RosettaImportStatus.APPROVED, rosetta_experts) |
| 568 | - >>> (subject, message) = sumerian_pofile.importFromQueue(warning_entry) |
| 569 | + >>> (subject, message) = eo_pofile.importFromQueue(warning_entry) |
| 570 | |
| 571 | The warning is noted in the confirmation email. Note that this |
| 572 | particular warning condition is recognized fairly late, so the line |
| 573 | @@ -473,7 +466,8 @@ |
| 574 | >>> warning_entry.setStatus(RosettaImportStatus.DELETED, rosetta_experts) |
| 575 | |
| 576 | |
| 577 | -=== Import Without Errors === |
| 578 | +Import Without Errors |
| 579 | +--------------------- |
| 580 | |
| 581 | Now, let's import one without errors. |
| 582 | |
| 583 | @@ -495,7 +489,8 @@ |
| 584 | ... ''' % datetime.datetime.now(UTC).isoformat() |
| 585 | >>> entry = translation_import_queue.addOrUpdateEntry( |
| 586 | ... pofile.path, pofile_without_errors, True, rosetta_experts, |
| 587 | - ... productseries=series, potemplate=potemplate) |
| 588 | + ... distroseries=distroseries, sourcepackagename=sourcepackagename, |
| 589 | + ... potemplate=potemplate) |
| 590 | >>> transaction.commit() |
| 591 | |
| 592 | The new upload clears the entry's error_output. |
| 593 | @@ -580,7 +575,8 @@ |
| 594 | u'helpful@example.com' |
| 595 | |
| 596 | |
| 597 | -=== Import Preconditions === |
| 598 | +Import Preconditions |
| 599 | +==================== |
| 600 | |
| 601 | The API for POFile.importFromQueue demands a translation import queue |
| 602 | entry to import. |
| 603 | @@ -617,296 +613,28 @@ |
| 604 | to. |
| 605 | |
| 606 | |
| 607 | -== Cron Scripts == |
| 608 | - |
| 609 | -We tested already that the functionality works. Now it's time to know |
| 610 | -if the cronscript has any problem. |
| 611 | - |
| 612 | -First, we are going to reactivate the entries that were already |
| 613 | -imported or failed. Note that we'll only reactivate the entries we use |
| 614 | -in this test; We don't touch entries that were in the queue previously. |
| 615 | - |
| 616 | - >>> for entry in translation_import_queue: |
| 617 | - ... if (entry.status == RosettaImportStatus.IMPORTED or |
| 618 | - ... entry.status == RosettaImportStatus.FAILED) and ( |
| 619 | - ... entry.productseries == series): |
| 620 | - ... entry.setStatus(RosettaImportStatus.APPROVED, rosetta_experts) |
| 621 | - ... syncUpdate(entry) |
| 622 | - >>> transaction.commit() |
| 623 | - |
| 624 | -And run the import script. |
| 625 | - |
| 626 | - >>> import email |
| 627 | - >>> from lp.services.mail import stub |
| 628 | - >>> process = TranslationsImport('poimport', test_args=[]) |
| 629 | - >>> process.logger = FakeLogger() |
| 630 | - >>> process.main() |
| 631 | - DEBUG Starting the import process. |
| 632 | - INFO Importing: Template "firefox" in Mozilla Firefox trunk |
| 633 | - INFO Importing: Welsh (cy) ... of firefox in Mozilla Firefox trunk |
| 634 | - INFO Importing: Welsh (cy) ... of firefox in Mozilla Firefox trunk |
| 635 | - INFO Import requests completed. |
| 636 | - DEBUG Finished the import process. |
| 637 | - |
| 638 | -The import script also generates an email similar to the ones we saw |
| 639 | -composed before, but also sends it. |
| 640 | - |
| 641 | - >>> len(stub.test_emails) |
| 642 | - 1 |
| 643 | - |
| 644 | - >>> from_addr, to_addrs, raw_message = stub.test_emails.pop() |
| 645 | - >>> msg = email.message_from_string(raw_message) |
| 646 | - >>> msg["Subject"] |
| 647 | - 'Translation template import - firefox in Mozilla Firefox trunk' |
| 648 | - |
| 649 | - >>> print msg.get_payload(decode=True) |
| 650 | - Hello Mark Shuttleworth, |
| 651 | - <BLANKLINE> |
| 652 | - On ..., you uploaded a translation |
| 653 | - template for firefox in Mozilla Firefox trunk in Launchpad. |
| 654 | - <BLANKLINE> |
| 655 | - The template has now been imported successfully. |
| 656 | - <BLANKLINE> |
| 657 | - Thank you, |
| 658 | - <BLANKLINE> |
| 659 | - The Launchpad team |
| 660 | - |
| 661 | -Now the queue gardener runs. This can happen anytime, since it's |
| 662 | -asynchronous to the po-import script. The script tries to approve any |
| 663 | -entries that have not been approved, but look like they could be, |
| 664 | -without human intervention. This involves a bit of guesswork about what |
| 665 | -the imported file is and where it belongs. It similarly blocks entries |
| 666 | -that it thinks should be blocked, and also purges deleted or completed |
| 667 | -entries from the queue. Running at this point, all it does is purge the |
| 668 | -two hand-approved Welsh translations that have just been imported. |
| 669 | - |
| 670 | - >>> import logging |
| 671 | - >>> from lp.testing.logger import MockLogger |
| 672 | - >>> process = ImportQueueGardener('approver', test_args=[]) |
| 673 | - >>> process.logger = MockLogger() |
| 674 | - >>> process.logger.setLevel(logging.INFO) |
| 675 | - >>> process.main() |
| 676 | - log> Removed 2 entries from the queue. |
| 677 | - >>> transaction.commit() |
| 678 | - |
| 679 | -If users upload two versions of the same file, they are imported in the |
| 680 | -order in which they were uploaded. |
| 681 | - |
| 682 | - >>> first_pofile_content = r''' |
| 683 | - ... msgid "" |
| 684 | - ... msgstr "" |
| 685 | - ... "PO-Revision-Date: 2005-06-04 20:41+0100\n" |
| 686 | - ... "Last-Translator: Foo <no-priv@canonical.com>\n" |
| 687 | - ... "Content-Type: text/plain; charset=UTF-8\n" |
| 688 | - ... "X-Rosetta-Export-Date: %s\n" |
| 689 | - ... |
| 690 | - ... msgid "Foo %%s" |
| 691 | - ... msgstr "Bar" |
| 692 | - ... |
| 693 | - ... msgid "translator-credits" |
| 694 | - ... msgstr "The world will never know." |
| 695 | - ... ''' % datetime.datetime.now(UTC).isoformat() |
| 696 | - |
| 697 | - >>> second_pofile_content = r''' |
| 698 | - ... msgid "" |
| 699 | - ... msgstr "" |
| 700 | - ... "PO-Revision-Date: 2005-06-04 21:41+0100\n" |
| 701 | - ... "Last-Translator: Jordi Mallach <jordi@canonical.com>\n" |
| 702 | - ... "Content-Type: text/plain; charset=UTF-8\n" |
| 703 | - ... "X-Rosetta-Export-Date: %s\n" |
| 704 | - ... |
| 705 | - ... msgid "Foo %%s" |
| 706 | - ... msgstr "Bars" |
| 707 | - ... |
| 708 | - ... msgid "translator-credits" |
| 709 | - ... msgstr "I'd like to thank John, Kathy, my pot plants, and all the..." |
| 710 | - ... ''' % datetime.datetime.now(UTC).isoformat() |
| 711 | - |
| 712 | -We flush the entry contents. |
| 713 | - |
| 714 | - >>> for entry in translation_import_queue: |
| 715 | - ... translation_import_queue.remove(entry) |
| 716 | - >>> translation_import_queue.countEntries() |
| 717 | - 0 |
| 718 | - |
| 719 | -Attach the first version of the file. |
| 720 | - |
| 721 | - >>> entry = translation_import_queue.addOrUpdateEntry( |
| 722 | - ... pofile.path, first_pofile_content, False, rosetta_experts, |
| 723 | - ... sourcepackagename=pofile.potemplate.sourcepackagename, |
| 724 | - ... distroseries=pofile.potemplate.distroseries, |
| 725 | - ... productseries=pofile.potemplate.productseries) |
| 726 | - >>> transaction.commit() |
| 727 | - |
| 728 | -It's in the queue now. |
| 729 | - |
| 730 | - >>> translation_import_queue.countEntries() |
| 731 | - 1 |
| 732 | - |
| 733 | -For the second version, we need a new importer, in this case, Jordi. |
| 734 | - |
| 735 | - >>> jordi = person_set.getByName('jordi') |
| 736 | - |
| 737 | -Attach the second version of the file. |
| 738 | - |
| 739 | - >>> entry = translation_import_queue.addOrUpdateEntry( |
| 740 | - ... pofile.path, second_pofile_content, False, jordi, |
| 741 | - ... sourcepackagename=pofile.potemplate.sourcepackagename, |
| 742 | - ... distroseries=pofile.potemplate.distroseries, |
| 743 | - ... productseries=pofile.potemplate.productseries) |
| 744 | - >>> transaction.commit() |
| 745 | - |
| 746 | -It's in the queue now. |
| 747 | - |
| 748 | - >>> translation_import_queue.countEntries() |
| 749 | - 2 |
| 750 | - >>> print entry.status.name |
| 751 | - NEEDS_REVIEW |
| 752 | - |
| 753 | -The queue gardener runs again. This time it sees the two submitted |
| 754 | -translations and approves them for import based on some heuristic |
| 755 | -intelligence. |
| 756 | - |
| 757 | - >>> process = ImportQueueGardener('approver', test_args=[]) |
| 758 | - >>> process.logger = MockLogger() |
| 759 | - >>> process.logger.setLevel(logging.INFO) |
| 760 | - >>> process.main() |
| 761 | - log> The automatic approval system approved some entries. |
| 762 | - >>> print entry.status.name |
| 763 | - APPROVED |
| 764 | - >>> syncUpdate(entry) |
| 765 | - |
| 766 | -Now that these submissions have been approved, the next run of the |
| 767 | -import script picks them up and processes them. |
| 768 | - |
| 769 | - >>> process = TranslationsImport('poimport', test_args=[]) |
| 770 | - >>> process.logger = FakeLogger() |
| 771 | - >>> process.main() |
| 772 | - DEBUG Starting the import process. |
| 773 | - INFO Importing: Welsh (cy) ... of firefox in Mozilla Firefox trunk |
| 774 | - INFO Importing: Welsh (cy) ... of firefox in Mozilla Firefox trunk |
| 775 | - INFO Import requests completed. |
| 776 | - DEBUG Finished the import process. |
| 777 | - |
| 778 | - >>> print entry.status.name |
| 779 | - IMPORTED |
| 780 | - >>> syncUpdate(entry) |
| 781 | - |
| 782 | -And there are no more entries to import |
| 783 | - |
| 784 | - >>> translation_import_queue.getFirstEntryToImport() is None |
| 785 | - True |
| 786 | - |
| 787 | -We've imported a new translation for "Foo %s." |
| 788 | - |
| 789 | - >>> welsh = getUtility(ILanguageSet).getLanguageByCode('cy') |
| 790 | - >>> foos = potemplate['Foo %s'].getLocalTranslationMessages( |
| 791 | - ... potemplate, welsh) |
| 792 | - >>> sorted([foo.msgstr0.translation for foo in foos]) |
| 793 | - [u'Bar', u'Bars', u'blah %i'] |
| 794 | - |
| 795 | -Since this last upload was not the upstream one, however, its credits |
| 796 | -message translations were ignored. |
| 797 | - |
| 798 | - >>> message = get_pofile_translation_message( |
| 799 | - ... pofile, u'translator-credits') |
| 800 | - >>> message.msgstr0.translation |
| 801 | - u'helpful@example.com' |
| 802 | - >>> list(potemplate['translator-credits'].getLocalTranslationMessages( |
| 803 | - ... potemplate, welsh)) |
| 804 | - [] |
| 805 | - |
| 806 | -Imports so far have been associated with a product series. We can also |
| 807 | -submit translations for a distroseries. |
| 808 | - |
| 809 | - >>> from lp.registry.interfaces.distribution import ( |
| 810 | - ... IDistributionSet) |
| 811 | - >>> ubuntu = getUtility(IDistributionSet).getByName('ubuntu') |
| 812 | - >>> warty = ubuntu.getSeries('warty') |
| 813 | - >>> print warty.name |
| 814 | - warty |
| 815 | - >>> firefox_name = SourcePackageName.byName('mozilla-firefox') |
| 816 | - >>> subset = POTemplateSubset(sourcepackagename=firefox_name, |
| 817 | - ... distroseries=warty) |
| 818 | - >>> potemplate = subset.new( |
| 819 | - ... name='firefox-warty', |
| 820 | - ... translation_domain='firefox-warty', |
| 821 | - ... path='po/firefox.pot', |
| 822 | - ... owner=person) |
| 823 | - |
| 824 | -As it happens, the administrator has blocked imports to warty, e.g. |
| 825 | -because an in-database update of its translations has been scheduled |
| 826 | -and we don't want interference from queued imports while that happens. |
| 827 | -It doesn't really matter whether entries still get auto-approved, but |
| 828 | -we can't accept new translation imports just now. |
| 829 | - |
| 830 | - >>> warty.defer_translation_imports = True |
| 831 | - >>> syncUpdate(warty) |
| 832 | - |
| 833 | -Nevertheless, someone submits an import request for warty, not knowing |
| 834 | -or caring that imports are deferred. The entry still gets approved as |
| 835 | -normal: |
| 836 | - |
| 837 | - >>> entry = translation_import_queue.addOrUpdateEntry( |
| 838 | - ... potemplate.path, potemplate_contents, True, potemplate.owner, |
| 839 | - ... sourcepackagename=firefox_name, distroseries=warty, |
| 840 | - ... potemplate=potemplate) |
| 841 | - >>> entry.setStatus(RosettaImportStatus.APPROVED, rosetta_experts) |
| 842 | - >>> syncUpdate(entry) |
| 843 | - >>> transaction.commit() |
| 844 | - |
| 845 | -Since imports for warty are suspended, and the only entry we happen to |
| 846 | -have waiting right now is for warty, the queue has no importable |
| 847 | -entries for us. |
| 848 | - |
| 849 | - >>> warty.getFirstEntryToImport() is None |
| 850 | - True |
| 851 | - |
| 852 | -So if we try to import now, nothing happens. Our request remains on the |
| 853 | -queue, but doesn't become a candidate for processing until warty |
| 854 | -imports are resumed. |
| 855 | - |
| 856 | - >>> process = TranslationsImport('poimport', test_args=[]) |
| 857 | - >>> process.logger = FakeLogger() |
| 858 | - >>> process.main() |
| 859 | - DEBUG Starting the import process. |
| 860 | - INFO No requests pending. |
| 861 | - |
| 862 | - >>> print entry.status.name |
| 863 | - APPROVED |
| 864 | - |
| 865 | -Once imports are allowed again, the import is done after all. |
| 866 | - |
| 867 | - >>> warty.defer_translation_imports = False |
| 868 | - >>> syncUpdate(warty) |
| 869 | - >>> (subject, body) = potemplate.importFromQueue(entry, FakeLogger()) |
| 870 | - |
| 871 | - >>> print entry.status.name |
| 872 | - IMPORTED |
| 873 | - |
| 874 | - |
| 875 | -== Plural forms handling == |
| 876 | +Plural forms handling |
| 877 | +===================== |
| 878 | |
| 879 | Apart from the basic plural form handling, which is documented above as |
| 880 | part of the import process, there are some peculiarities with importing |
| 881 | plural forms we want documented as well. |
| 882 | |
| 883 | -For a language such as Divehi, which has no plural forms defined, we |
| 884 | +For a language that has no plural forms defined, we |
| 885 | default to two plural forms (the most common value for the number of |
| 886 | plural forms). |
| 887 | |
| 888 | - >>> divehi = getUtility(ILanguageSet)['dv'] |
| 889 | - >>> print divehi.pluralforms |
| 890 | + >>> language = factory.makeLanguage() |
| 891 | + >>> print language.pluralforms |
| 892 | None |
| 893 | |
| 894 | - >>> firefox = getUtility(IProductSet).getByName('firefox') |
| 895 | - >>> firefox_trunk = firefox.getSeries('trunk') |
| 896 | - >>> firefox_potemplate = firefox_trunk.getPOTemplate('firefox') |
| 897 | - >>> firefox_dv = firefox_potemplate.newPOFile(divehi.code) |
| 898 | - >>> firefox_dv.plural_forms |
| 899 | + >>> potemplate = factory.makePOTemplate( |
| 900 | + ... distroseries=distroseries, sourcepackagename=sourcepackagename) |
| 901 | + >>> pofile = potemplate.newPOFile(language.code) |
| 902 | + >>> pofile.plural_forms |
| 903 | 2 |
| 904 | |
| 905 | -We'll import a POFile with 3 plural forms into Divehi POFile: |
| 906 | +We'll import a POFile with 3 plural forms into this POFile: |
| 907 | |
| 908 | >>> pofile_with_plurals = r''' |
| 909 | ... msgid "" |
| 910 | @@ -925,16 +653,18 @@ |
| 911 | ... msgstr[2] "Third form %%d" |
| 912 | ... ''' % datetime.datetime.now(UTC).isoformat() |
| 913 | |
| 914 | -We now import this POFile as Divehi translation of Firefox trunk: |
| 915 | +We now import this POFile as this language's translation for the soure |
| 916 | +package: |
| 917 | |
| 918 | >>> entry = translation_import_queue.addOrUpdateEntry( |
| 919 | - ... firefox_dv.path, pofile_with_plurals, True, person, |
| 920 | - ... productseries=firefox_trunk, potemplate=firefox_potemplate) |
| 921 | + ... pofile.path, pofile_with_plurals, True, person, |
| 922 | + ... distroseries=distroseries, sourcepackagename=sourcepackagename, |
| 923 | + ... potemplate=potemplate) |
| 924 | >>> # Allow Librarian to see the change. |
| 925 | >>> transaction.commit() |
| 926 | - >>> entry.pofile = firefox_dv |
| 927 | + >>> entry.pofile = pofile |
| 928 | >>> entry.setStatus(RosettaImportStatus.APPROVED, rosetta_experts) |
| 929 | - >>> (subject, body) = firefox_dv.importFromQueue(entry, FakeLogger()) |
| 930 | + >>> (subject, body) = pofile.importFromQueue(entry, FakeLogger()) |
| 931 | >>> flush_database_updates() |
| 932 | >>> print entry.status.name |
| 933 | IMPORTED |
| 934 | @@ -943,21 +673,22 @@ |
| 935 | translations (which is a default when the language has no plural forms |
| 936 | specified): |
| 937 | |
| 938 | - >>> potmsgset_plural = firefox_potemplate.getPOTMsgSetByMsgIDText( |
| 939 | + >>> potmsgset_plural = potemplate.getPOTMsgSetByMsgIDText( |
| 940 | ... u'Singular %d', u'Plural %d') |
| 941 | - >>> current_dv = potmsgset_plural.getCurrentTranslationMessage( |
| 942 | - ... firefox_potemplate, divehi) |
| 943 | - >>> current_dv.translations |
| 944 | + >>> current = potmsgset_plural.getCurrentTranslationMessage( |
| 945 | + ... potemplate, language) |
| 946 | + >>> current.translations |
| 947 | [u'First form %d', u'Second form %d'] |
| 948 | |
| 949 | However, even the third form will be imported into database (this is |
| 950 | useful for when we finally define the number of plural forms for the |
| 951 | language, we should not have to reimport all translations): |
| 952 | |
| 953 | - >>> current_dv.msgstr2.translation |
| 954 | + >>> current.msgstr2.translation |
| 955 | u'Third form %d' |
| 956 | |
| 957 | -== Upstream import notifications == |
| 958 | +Upstream import notifications |
| 959 | +============================= |
| 960 | |
| 961 | Add an upstream POFile import (i.e. from a package or bzr branch), |
| 962 | approve and import it. |
| 963 | @@ -974,12 +705,12 @@ |
| 964 | ... msgid "foo" |
| 965 | ... msgstr "blah" |
| 966 | ... ''' |
| 967 | - >>> pofile = factory.makePOFile('sr') |
| 968 | + >>> pofile = factory.makePOFile('sr', potemplate=potemplate) |
| 969 | >>> from_upstream = True |
| 970 | >>> entry = translation_import_queue.addOrUpdateEntry( |
| 971 | ... pofile.path, pofile_contents, from_upstream, person, |
| 972 | - ... productseries=pofile.potemplate.productseries, |
| 973 | - ... potemplate=pofile.potemplate, pofile=pofile) |
| 974 | + ... distroseries=distroseries, sourcepackagename=sourcepackagename, |
| 975 | + ... potemplate=potemplate, pofile=pofile) |
| 976 | >>> transaction.commit() |
| 977 | >>> entry.setStatus(RosettaImportStatus.APPROVED, rosetta_experts) |
| 978 | >>> (subject, message) = pofile.importFromQueue(entry) |
| 979 | @@ -997,8 +728,8 @@ |
| 980 | >>> pofile_contents = pofile_contents[:-2] |
| 981 | >>> entry = translation_import_queue.addOrUpdateEntry( |
| 982 | ... pofile.path, pofile_contents, from_upstream, person, |
| 983 | - ... productseries=pofile.potemplate.productseries, |
| 984 | - ... potemplate=pofile.potemplate, pofile=pofile) |
| 985 | + ... distroseries=distroseries, sourcepackagename=sourcepackagename, |
| 986 | + ... potemplate=potemplate, pofile=pofile) |
| 987 | >>> transaction.commit() |
| 988 | >>> entry.setStatus(RosettaImportStatus.APPROVED, rosetta_experts) |
| 989 | >>> (subject, message) = pofile.importFromQueue(entry) |
| 990 | @@ -1010,44 +741,3 @@ |
| 991 | >>> subject |
| 992 | u'Import problem - Serbian (sr) - ...' |
| 993 | |
| 994 | - |
| 995 | -No Contact Address |
| 996 | ------------------- |
| 997 | - |
| 998 | -Not every user has a valid email address. For instance, Kermit the |
| 999 | -Hermit has none at the moment. |
| 1000 | - |
| 1001 | - >>> from canonical.launchpad.interfaces.emailaddress import ( |
| 1002 | - ... EmailAddressStatus) |
| 1003 | - >>> from canonical.launchpad.helpers import get_contact_email_addresses |
| 1004 | - >>> hermit = factory.makePerson( |
| 1005 | - ... name='hermit', email_address_status=EmailAddressStatus.OLD) |
| 1006 | - |
| 1007 | - >>> len(get_contact_email_addresses(hermit)) |
| 1008 | - 0 |
| 1009 | - |
| 1010 | -Kermit uploads a translation, which gets approved. |
| 1011 | - |
| 1012 | - >>> pofile = factory.makePOFile('lo') |
| 1013 | - |
| 1014 | - >>> entry = translation_import_queue.addOrUpdateEntry( |
| 1015 | - ... 'lo.po', 'Invalid content', True, hermit, |
| 1016 | - ... pofile=pofile, potemplate=pofile.potemplate, |
| 1017 | - ... productseries=pofile.potemplate.productseries) |
| 1018 | - >>> entry.setStatus(RosettaImportStatus.APPROVED, rosetta_experts) |
| 1019 | - >>> transaction.commit() |
| 1020 | - |
| 1021 | -The import fails. The importer would like to send Kermit an email about |
| 1022 | -this, but is unable to. This is unfortunate, but does not faze the |
| 1023 | -importer. It completes normally. |
| 1024 | - |
| 1025 | - >>> process = TranslationsImport('poimport', test_args=[]) |
| 1026 | - >>> process.logger = FakeLogger() |
| 1027 | - >>> process.main() |
| 1028 | - DEBUG Starting the import process. |
| 1029 | - INFO Importing: Lao ... |
| 1030 | - INFO Import requests completed. |
| 1031 | - DEBUG Finished the import process. |
| 1032 | - |
| 1033 | - >>> print entry.status.name |
| 1034 | - FAILED |

Hi Henning,
This changes looks good. r=mars
Maris