Merge lp:~wgrant/launchpad/tm-suggest-constant into lp:launchpad
- tm-suggest-constant
- Merge into devel
Status: | Merged | ||||
---|---|---|---|---|---|
Merged at revision: | 17086 | ||||
Proposed branch: | lp:~wgrant/launchpad/tm-suggest-constant | ||||
Merge into: | lp:launchpad | ||||
Diff against target: |
374 lines (+211/-30) 6 files modified
lib/lp/translations/browser/tests/test_pofile_view.py (+71/-0) lib/lp/translations/browser/translationmessage.py (+25/-26) lib/lp/translations/interfaces/potemplate.py (+3/-0) lib/lp/translations/interfaces/translationmessage.py (+22/-0) lib/lp/translations/model/potemplate.py (+8/-0) lib/lp/translations/model/translationmessage.py (+82/-4) |
||||
To merge this branch: | bzr merge lp:~wgrant/launchpad/tm-suggest-constant | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Celso Providelo (community) | Approve | ||
Review via email: mp+225260@code.launchpad.net |
Commit message
Preload suggestion details in POFile:+translate to eliminate per-suggestion queries. But there are still lots of per-message queries.
Description of the change
Drop POFile:+translate from O(messages + suggestions) queries to O(messages), by doing lots of pretty boring preloading. The only thing with significant complexity is preloadPOFilesA
We still do the suggestion calculation inside the per-message subviews, so the preloading is done once for each message. But it's a good step in reducing the query count of this page, and we can pull the suggestion calculation and preloading into the upper view later.
I've added a query count test for POFile:+translate, which tests all the preloading I've done so far. It doesn't currently add new messages, just new suggestions, since extra messages still generate extra queries.
Preview Diff
1 | === modified file 'lib/lp/translations/browser/tests/test_pofile_view.py' | |||
2 | --- lib/lp/translations/browser/tests/test_pofile_view.py 2014-06-10 11:25:51 +0000 | |||
3 | +++ lib/lp/translations/browser/tests/test_pofile_view.py 2014-07-02 06:12:24 +0000 | |||
4 | @@ -3,20 +3,91 @@ | |||
5 | 3 | 3 | ||
6 | 4 | __metaclass__ = type | 4 | __metaclass__ = type |
7 | 5 | 5 | ||
8 | 6 | from storm.store import Store | ||
9 | 7 | from testtools.matchers import Equals | ||
10 | 8 | from zope.component import getUtility | ||
11 | 9 | |||
12 | 6 | from lp.app.errors import UnexpectedFormData | 10 | from lp.app.errors import UnexpectedFormData |
13 | 11 | from lp.app.interfaces.launchpad import ILaunchpadCelebrities | ||
14 | 12 | from lp.services.webapp.interfaces import ILaunchBag | ||
15 | 7 | from lp.services.webapp.servers import LaunchpadTestRequest | 13 | from lp.services.webapp.servers import LaunchpadTestRequest |
16 | 8 | from lp.testing import ( | 14 | from lp.testing import ( |
17 | 9 | BrowserTestCase, | 15 | BrowserTestCase, |
18 | 10 | login, | 16 | login, |
19 | 17 | login_person, | ||
20 | 11 | person_logged_in, | 18 | person_logged_in, |
21 | 19 | record_two_runs, | ||
22 | 12 | TestCaseWithFactory, | 20 | TestCaseWithFactory, |
23 | 13 | ) | 21 | ) |
24 | 14 | from lp.testing.layers import ( | 22 | from lp.testing.layers import ( |
25 | 15 | DatabaseFunctionalLayer, | 23 | DatabaseFunctionalLayer, |
26 | 16 | ZopelessDatabaseLayer, | 24 | ZopelessDatabaseLayer, |
27 | 17 | ) | 25 | ) |
28 | 26 | from lp.testing.matchers import HasQueryCount | ||
29 | 27 | from lp.testing.views import create_initialized_view | ||
30 | 18 | from lp.translations.browser.pofile import POFileTranslateView | 28 | from lp.translations.browser.pofile import POFileTranslateView |
31 | 19 | from lp.translations.enums import TranslationPermission | 29 | from lp.translations.enums import TranslationPermission |
32 | 30 | from lp.translations.interfaces.potemplate import IPOTemplateSet | ||
33 | 31 | from lp.translations.interfaces.translationsperson import ITranslationsPerson | ||
34 | 32 | from lp.translations.model.pofiletranslator import POFileTranslator | ||
35 | 33 | |||
36 | 34 | |||
37 | 35 | class TestQueryCount(TestCaseWithFactory): | ||
38 | 36 | |||
39 | 37 | layer = DatabaseFunctionalLayer | ||
40 | 38 | |||
41 | 39 | def test_query_count(self): | ||
42 | 40 | person = self.factory.makePerson() | ||
43 | 41 | login_person(person) | ||
44 | 42 | ITranslationsPerson(person).translations_relicensing_agreement = True | ||
45 | 43 | product = self.factory.makeProduct(owner=person) | ||
46 | 44 | product.translationpermission = TranslationPermission.OPEN | ||
47 | 45 | pofile = self.factory.makePOFile( | ||
48 | 46 | potemplate=self.factory.makePOTemplate( | ||
49 | 47 | productseries=product.series[0])) | ||
50 | 48 | pofile.potemplate.productseries.product | ||
51 | 49 | potmsgsets = [ | ||
52 | 50 | self.factory.makePOTMsgSet(pofile.potemplate) for i in range(10)] | ||
53 | 51 | |||
54 | 52 | # Preload a few transaction-crossing caches that would give | ||
55 | 53 | # extra queries to the first request. | ||
56 | 54 | getUtility(ILaunchBag).time_zone | ||
57 | 55 | getUtility(ILaunchpadCelebrities).ubuntu | ||
58 | 56 | person.inTeam(getUtility(ILaunchpadCelebrities).admin) | ||
59 | 57 | person.inTeam(getUtility(ILaunchpadCelebrities).rosetta_experts) | ||
60 | 58 | |||
61 | 59 | def create_suggestions(): | ||
62 | 60 | for potmsgset in potmsgsets: | ||
63 | 61 | pot = self.factory.makePOTemplate() | ||
64 | 62 | self.factory.makeCurrentTranslationMessage( | ||
65 | 63 | potmsgset=self.factory.makePOTMsgSet( | ||
66 | 64 | singular=potmsgset.msgid_singular.msgid, | ||
67 | 65 | potemplate=pot), | ||
68 | 66 | language=pofile.language, | ||
69 | 67 | translations=[self.factory.getUniqueUnicode()]) | ||
70 | 68 | # A suggestion only shows up if it's actually in a | ||
71 | 69 | # POFile. | ||
72 | 70 | self.factory.makePOFile( | ||
73 | 71 | potemplate=pot, language=pofile.language) | ||
74 | 72 | self.factory.makeSuggestion( | ||
75 | 73 | pofile=pofile, potmsgset=potmsgset) | ||
76 | 74 | |||
77 | 75 | # Ensure that these are valid suggestions. | ||
78 | 76 | templateset = getUtility(IPOTemplateSet) | ||
79 | 77 | templateset.wipeSuggestivePOTemplatesCache() | ||
80 | 78 | templateset.populateSuggestivePOTemplatesCache() | ||
81 | 79 | |||
82 | 80 | # And ensure that the credits string is empty, as that's | ||
83 | 81 | # not currently constant. | ||
84 | 82 | Store.of(pofile).find(POFileTranslator, pofile=pofile).set( | ||
85 | 83 | pofileID=self.factory.makePOFile().id) | ||
86 | 84 | |||
87 | 85 | nb_objects = 2 | ||
88 | 86 | recorder1, recorder2 = record_two_runs( | ||
89 | 87 | lambda: create_initialized_view( | ||
90 | 88 | pofile, '+translate', principal=person)(), | ||
91 | 89 | create_suggestions, nb_objects) | ||
92 | 90 | self.assertThat(recorder2, HasQueryCount(Equals(recorder1.count))) | ||
93 | 20 | 91 | ||
94 | 21 | 92 | ||
95 | 22 | class TestPOFileTranslateViewInvalidFiltering(TestCaseWithFactory): | 93 | class TestPOFileTranslateViewInvalidFiltering(TestCaseWithFactory): |
96 | 23 | 94 | ||
97 | === modified file 'lib/lp/translations/browser/translationmessage.py' | |||
98 | --- lib/lp/translations/browser/translationmessage.py 2014-07-02 04:41:13 +0000 | |||
99 | +++ lib/lp/translations/browser/translationmessage.py 2014-07-02 06:12:24 +0000 | |||
100 | @@ -1212,24 +1212,6 @@ | |||
101 | 1212 | else: | 1212 | else: |
102 | 1213 | self.can_confirm_and_dismiss = True | 1213 | self.can_confirm_and_dismiss = True |
103 | 1214 | 1214 | ||
104 | 1215 | def _setOnePOFile(self, messages): | ||
105 | 1216 | """Return a list of messages that all have a browser_pofile set. | ||
106 | 1217 | |||
107 | 1218 | If a pofile cannot be found for a message, it is not included in | ||
108 | 1219 | the resulting list. | ||
109 | 1220 | """ | ||
110 | 1221 | result = [] | ||
111 | 1222 | for message in messages: | ||
112 | 1223 | if message.browser_pofile is None: | ||
113 | 1224 | pofile = message.getOnePOFile() | ||
114 | 1225 | if pofile is None: | ||
115 | 1226 | # Do not include in result. | ||
116 | 1227 | continue | ||
117 | 1228 | else: | ||
118 | 1229 | message.setPOFile(pofile) | ||
119 | 1230 | result.append(message) | ||
120 | 1231 | return result | ||
121 | 1232 | |||
122 | 1233 | def _buildAllSuggestions(self): | 1215 | def _buildAllSuggestions(self): |
123 | 1234 | """Builds all suggestions and puts them into suggestions_block. | 1216 | """Builds all suggestions and puts them into suggestions_block. |
124 | 1235 | 1217 | ||
125 | @@ -1277,8 +1259,8 @@ | |||
126 | 1277 | 1259 | ||
127 | 1278 | self._set_dismiss_flags(local, other) | 1260 | self._set_dismiss_flags(local, other) |
128 | 1279 | 1261 | ||
131 | 1280 | for suggestion in local: | 1262 | getUtility(ITranslationMessageSet).preloadDetails( |
132 | 1281 | suggestion.setPOFile(self.pofile) | 1263 | local, need_potranslation=True, need_people=True) |
133 | 1282 | 1264 | ||
134 | 1283 | # Get a list of translations which are _used_ as translations | 1265 | # Get a list of translations which are _used_ as translations |
135 | 1284 | # for this same message in a different translation template. | 1266 | # for this same message in a different translation template. |
136 | @@ -1289,19 +1271,36 @@ | |||
137 | 1289 | potmsgset.getExternallySuggestedOrUsedTranslationMessages( | 1271 | potmsgset.getExternallySuggestedOrUsedTranslationMessages( |
138 | 1290 | suggested_languages=[language], | 1272 | suggested_languages=[language], |
139 | 1291 | used_languages=used_languages)) | 1273 | used_languages=used_languages)) |
140 | 1274 | |||
141 | 1275 | # Suggestions from other templates need full preloading, | ||
142 | 1276 | # including picking a POFile. preloadDetails requires that | ||
143 | 1277 | # all messages have the same language, so we invoke it | ||
144 | 1278 | # separately for the alternate suggestion language. | ||
145 | 1279 | preload_groups = [ | ||
146 | 1280 | translations[language].used + translations[language].suggested, | ||
147 | 1281 | translations[self.sec_lang].used, | ||
148 | 1282 | ] | ||
149 | 1283 | for group in preload_groups: | ||
150 | 1284 | getUtility(ITranslationMessageSet).preloadDetails( | ||
151 | 1285 | group, need_pofile=True, need_potemplate=True, | ||
152 | 1286 | need_potemplate_context=True, | ||
153 | 1287 | need_potranslation=True, need_potmsgset=True, | ||
154 | 1288 | need_people=True) | ||
155 | 1289 | |||
156 | 1292 | alt_external = translations[self.sec_lang].used | 1290 | alt_external = translations[self.sec_lang].used |
159 | 1293 | externally_used = self._setOnePOFile(sorted( | 1291 | externally_used = sorted( |
160 | 1294 | translations[language].used, | 1292 | [m for m in translations[language].used if m.browser_pofile], |
161 | 1295 | key=operator.attrgetter("date_created"), | 1293 | key=operator.attrgetter("date_created"), |
163 | 1296 | reverse=True)) | 1294 | reverse=True) |
164 | 1297 | 1295 | ||
165 | 1298 | # Get a list of translations which are suggested as | 1296 | # Get a list of translations which are suggested as |
166 | 1299 | # translations for this same message in a different translation | 1297 | # translations for this same message in a different translation |
167 | 1300 | # template, but are not used. | 1298 | # template, but are not used. |
170 | 1301 | externally_suggested = self._setOnePOFile(sorted( | 1299 | externally_suggested = sorted( |
171 | 1302 | translations[language].suggested, | 1300 | [m for m in translations[language].suggested |
172 | 1301 | if m.browser_pofile], | ||
173 | 1303 | key=operator.attrgetter("date_created"), | 1302 | key=operator.attrgetter("date_created"), |
175 | 1304 | reverse=True)) | 1303 | reverse=True) |
176 | 1305 | else: | 1304 | else: |
177 | 1306 | # Don't show suggestions for anonymous users. | 1305 | # Don't show suggestions for anonymous users. |
178 | 1307 | local = externally_used = externally_suggested = [] | 1306 | local = externally_used = externally_suggested = [] |
179 | 1308 | 1307 | ||
180 | === modified file 'lib/lp/translations/interfaces/potemplate.py' | |||
181 | --- lib/lp/translations/interfaces/potemplate.py 2013-10-21 04:58:19 +0000 | |||
182 | +++ lib/lp/translations/interfaces/potemplate.py 2014-07-02 06:12:24 +0000 | |||
183 | @@ -698,6 +698,9 @@ | |||
184 | 698 | Return None if there is no such `IPOTemplate`. | 698 | Return None if there is no such `IPOTemplate`. |
185 | 699 | """ | 699 | """ |
186 | 700 | 700 | ||
187 | 701 | def preloadPOTemplateContexts(templates): | ||
188 | 702 | """Preload context objects for a sequence of POTemplates.""" | ||
189 | 703 | |||
190 | 701 | def wipeSuggestivePOTemplatesCache(): | 704 | def wipeSuggestivePOTemplatesCache(): |
191 | 702 | """Erase suggestive-templates cache. | 705 | """Erase suggestive-templates cache. |
192 | 703 | 706 | ||
193 | 704 | 707 | ||
194 | === modified file 'lib/lp/translations/interfaces/translationmessage.py' | |||
195 | --- lib/lp/translations/interfaces/translationmessage.py 2013-01-07 02:40:55 +0000 | |||
196 | +++ lib/lp/translations/interfaces/translationmessage.py 2014-07-02 06:12:24 +0000 | |||
197 | @@ -343,3 +343,25 @@ | |||
198 | 343 | return. | 343 | return. |
199 | 344 | :param order_by: An SQL ORDER BY clause. | 344 | :param order_by: An SQL ORDER BY clause. |
200 | 345 | """ | 345 | """ |
201 | 346 | |||
202 | 347 | def preloadDetails(messages, pofile=None, need_pofile=False, | ||
203 | 348 | need_potemplate=False, need_potemplate_context=False, | ||
204 | 349 | need_potranslation=False, need_potmsgset=False, | ||
205 | 350 | need_people=False): | ||
206 | 351 | """Preload lots of details for `TranslationMessage`s. | ||
207 | 352 | |||
208 | 353 | If need_pofile is True, pofile may be left None to indicate that | ||
209 | 354 | an arbitrary `POFile` containing that message may be picked, or | ||
210 | 355 | set to a specific `POFile` in which all the messages must exist. | ||
211 | 356 | In either case, all messages must be for the same language. | ||
212 | 357 | """ | ||
213 | 358 | |||
214 | 359 | def preloadPOFilesAndSequences(messages, pofile=None): | ||
215 | 360 | """Preload browser_pofile and sequence for `TranslationMessage`s. | ||
216 | 361 | |||
217 | 362 | All messages must be for the same language. | ||
218 | 363 | |||
219 | 364 | If pofile is None, an arbitrary POFile containing each message | ||
220 | 365 | will be used. Otherwise, each message must exist in the given | ||
221 | 366 | POFile. | ||
222 | 367 | """ | ||
223 | 346 | 368 | ||
224 | === modified file 'lib/lp/translations/model/potemplate.py' | |||
225 | --- lib/lp/translations/model/potemplate.py 2013-10-21 04:58:19 +0000 | |||
226 | +++ lib/lp/translations/model/potemplate.py 2014-07-02 06:12:24 +0000 | |||
227 | @@ -1347,6 +1347,14 @@ | |||
228 | 1347 | len(preferred_matches)) | 1347 | len(preferred_matches)) |
229 | 1348 | return None | 1348 | return None |
230 | 1349 | 1349 | ||
231 | 1350 | def preloadPOTemplateContexts(self, templates): | ||
232 | 1351 | """See `IPOTemplateSet`.""" | ||
233 | 1352 | from lp.registry.model.product import Product | ||
234 | 1353 | from lp.registry.model.productseries import ProductSeries | ||
235 | 1354 | pses = load_related(ProductSeries, templates, ['productseriesID']) | ||
236 | 1355 | load_related(Product, pses, ['productID']) | ||
237 | 1356 | load_related(SourcePackageName, templates, ['sourcepackagenameID']) | ||
238 | 1357 | |||
239 | 1350 | def wipeSuggestivePOTemplatesCache(self): | 1358 | def wipeSuggestivePOTemplatesCache(self): |
240 | 1351 | """See `IPOTemplateSet`.""" | 1359 | """See `IPOTemplateSet`.""" |
241 | 1352 | return IMasterStore(POTemplate).execute( | 1360 | return IMasterStore(POTemplate).execute( |
242 | 1353 | 1361 | ||
243 | === modified file 'lib/lp/translations/model/translationmessage.py' | |||
244 | --- lib/lp/translations/model/translationmessage.py 2014-06-13 07:43:41 +0000 | |||
245 | +++ lib/lp/translations/model/translationmessage.py 2014-07-02 06:12:24 +0000 | |||
246 | @@ -22,9 +22,18 @@ | |||
247 | 22 | from storm.expr import And | 22 | from storm.expr import And |
248 | 23 | from storm.locals import SQL | 23 | from storm.locals import SQL |
249 | 24 | from storm.store import Store | 24 | from storm.store import Store |
250 | 25 | from zope.component import getUtility | ||
251 | 25 | from zope.interface import implements | 26 | from zope.interface import implements |
252 | 27 | from zope.security.proxy import removeSecurityProxy | ||
253 | 26 | 28 | ||
255 | 27 | from lp.registry.interfaces.person import validate_public_person | 29 | from lp.registry.interfaces.person import ( |
256 | 30 | IPersonSet, | ||
257 | 31 | validate_public_person, | ||
258 | 32 | ) | ||
259 | 33 | from lp.services.database.bulk import ( | ||
260 | 34 | load, | ||
261 | 35 | load_related, | ||
262 | 36 | ) | ||
263 | 28 | from lp.services.database.constants import ( | 37 | from lp.services.database.constants import ( |
264 | 29 | DEFAULT, | 38 | DEFAULT, |
265 | 30 | UTC_NOW, | 39 | UTC_NOW, |
266 | @@ -41,6 +50,7 @@ | |||
267 | 41 | cachedproperty, | 50 | cachedproperty, |
268 | 42 | get_property_cache, | 51 | get_property_cache, |
269 | 43 | ) | 52 | ) |
270 | 53 | from lp.translations.interfaces.potemplate import IPOTemplateSet | ||
271 | 44 | from lp.translations.interfaces.side import TranslationSide | 54 | from lp.translations.interfaces.side import TranslationSide |
272 | 45 | from lp.translations.interfaces.translationmessage import ( | 55 | from lp.translations.interfaces.translationmessage import ( |
273 | 46 | ITranslationMessage, | 56 | ITranslationMessage, |
274 | @@ -49,6 +59,7 @@ | |||
275 | 49 | TranslationValidationStatus, | 59 | TranslationValidationStatus, |
276 | 50 | ) | 60 | ) |
277 | 51 | from lp.translations.interfaces.translations import TranslationConstants | 61 | from lp.translations.interfaces.translations import TranslationConstants |
278 | 62 | from lp.translations.model.potranslation import POTranslation | ||
279 | 52 | 63 | ||
280 | 53 | 64 | ||
281 | 54 | UTC = pytz.timezone('UTC') | 65 | UTC = pytz.timezone('UTC') |
282 | @@ -113,10 +124,13 @@ | |||
283 | 113 | elements.append(suffix) | 124 | elements.append(suffix) |
284 | 114 | return self.potmsgset.makeHTMLID('_'.join(elements)) | 125 | return self.potmsgset.makeHTMLID('_'.join(elements)) |
285 | 115 | 126 | ||
287 | 116 | def setPOFile(self, pofile): | 127 | def setPOFile(self, pofile, sequence=None): |
288 | 117 | """See `ITranslationMessage`.""" | 128 | """See `ITranslationMessage`.""" |
289 | 118 | self.browser_pofile = pofile | 129 | self.browser_pofile = pofile |
291 | 119 | del get_property_cache(self).sequence | 130 | if sequence is not None: |
292 | 131 | get_property_cache(self).sequence = sequence | ||
293 | 132 | else: | ||
294 | 133 | del get_property_cache(self).sequence | ||
295 | 120 | 134 | ||
296 | 121 | @cachedproperty | 135 | @cachedproperty |
297 | 122 | def sequence(self): | 136 | def sequence(self): |
298 | @@ -396,7 +410,7 @@ | |||
299 | 396 | """(SELECT potemplate | 410 | """(SELECT potemplate |
300 | 397 | FROM TranslationTemplateItem | 411 | FROM TranslationTemplateItem |
301 | 398 | WHERE potmsgset = %s AND sequence > 0 | 412 | WHERE potmsgset = %s AND sequence > 0 |
303 | 399 | LIMIT 1)""" % sqlvalues(self.potmsgset)), | 413 | LIMIT 1)""" % sqlvalues(self.potmsgsetID)), |
304 | 400 | POFile.language == self.language).one() | 414 | POFile.language == self.language).one() |
305 | 401 | return pofile | 415 | return pofile |
306 | 402 | 416 | ||
307 | @@ -530,3 +544,67 @@ | |||
308 | 530 | def selectDirect(self, where=None, order_by=None): | 544 | def selectDirect(self, where=None, order_by=None): |
309 | 531 | """See `ITranslationMessageSet`.""" | 545 | """See `ITranslationMessageSet`.""" |
310 | 532 | return TranslationMessage.select(where, orderBy=order_by) | 546 | return TranslationMessage.select(where, orderBy=order_by) |
311 | 547 | |||
312 | 548 | def preloadDetails(self, messages, pofile=None, need_pofile=False, | ||
313 | 549 | need_potemplate=False, need_potemplate_context=False, | ||
314 | 550 | need_potranslation=False, need_potmsgset=False, | ||
315 | 551 | need_people=False): | ||
316 | 552 | """See `ITranslationMessageSet`.""" | ||
317 | 553 | from lp.translations.model.potemplate import POTemplate | ||
318 | 554 | from lp.translations.model.potmsgset import POTMsgSet | ||
319 | 555 | assert need_pofile or not need_potemplate | ||
320 | 556 | assert need_potemplate or not need_potemplate_context | ||
321 | 557 | tms = [removeSecurityProxy(tm) for tm in messages] | ||
322 | 558 | if need_pofile: | ||
323 | 559 | self.preloadPOFilesAndSequences(tms, pofile) | ||
324 | 560 | if need_potemplate: | ||
325 | 561 | pofiles = [tm.browser_pofile for tm in tms] | ||
326 | 562 | pots = load_related( | ||
327 | 563 | POTemplate, | ||
328 | 564 | (removeSecurityProxy(pofile) for pofile in pofiles), | ||
329 | 565 | ['potemplateID']) | ||
330 | 566 | if need_potemplate_context: | ||
331 | 567 | getUtility(IPOTemplateSet).preloadPOTemplateContexts(pots) | ||
332 | 568 | if need_potranslation: | ||
333 | 569 | load_related( | ||
334 | 570 | POTranslation, tms, | ||
335 | 571 | ['msgstr%dID' % form | ||
336 | 572 | for form in xrange(TranslationConstants.MAX_PLURAL_FORMS)]) | ||
337 | 573 | if need_potmsgset: | ||
338 | 574 | load_related(POTMsgSet, tms, ['potmsgsetID']) | ||
339 | 575 | if need_people: | ||
340 | 576 | list(getUtility(IPersonSet).getPrecachedPersonsFromIDs( | ||
341 | 577 | [tm.submitterID for tm in tms] | ||
342 | 578 | + [tm.reviewerID for tm in tms])) | ||
343 | 579 | |||
344 | 580 | def preloadPOFilesAndSequences(self, messages, pofile=None): | ||
345 | 581 | """See `ITranslationMessageSet`.""" | ||
346 | 582 | from lp.translations.model.pofile import POFile | ||
347 | 583 | from lp.translations.model.translationtemplateitem import ( | ||
348 | 584 | TranslationTemplateItem, | ||
349 | 585 | ) | ||
350 | 586 | |||
351 | 587 | if len(messages) == 0: | ||
352 | 588 | return | ||
353 | 589 | language = messages[0].language | ||
354 | 590 | if pofile is not None: | ||
355 | 591 | pofile_constraints = [POFile.id == pofile.id] | ||
356 | 592 | else: | ||
357 | 593 | pofile_constraints = [POFile.language == language] | ||
358 | 594 | results = IStore(POFile).find( | ||
359 | 595 | (TranslationTemplateItem.potmsgsetID, POFile.id, | ||
360 | 596 | TranslationTemplateItem.sequence), | ||
361 | 597 | TranslationTemplateItem.potmsgsetID.is_in( | ||
362 | 598 | message.potmsgsetID for message in messages), | ||
363 | 599 | POFile.potemplateID == TranslationTemplateItem.potemplateID, | ||
364 | 600 | *pofile_constraints | ||
365 | 601 | ).config(distinct=(TranslationTemplateItem.potmsgsetID,)) | ||
366 | 602 | potmsgset_map = dict( | ||
367 | 603 | (potmsgset_id, (pofile_id, sequence)) | ||
368 | 604 | for potmsgset_id, pofile_id, sequence in results) | ||
369 | 605 | load(POFile, (pofile_id for pofile_id, _ in potmsgset_map.values())) | ||
370 | 606 | for message in messages: | ||
371 | 607 | assert message.language == language | ||
372 | 608 | pofile_id, sequence = potmsgset_map.get( | ||
373 | 609 | message.potmsgsetID, (None, None)) | ||
374 | 610 | message.setPOFile(IStore(POFile).get(POFile, pofile_id), sequence) |
Nice change, introducing preloadPOFilesA ndSequence really solves the problem for loading suggestions for a given message.
We can land it ASAP.
(thinking out loud here, feel free to ignore me)
Although, I am not seeing a clear path for going further and pre-loading this for multiple messages. Maybe we won't need it (this step will bring enough benefits) or we could try to investigate something at the model level for making it possible.