Merge lp://staging/~jtv/launchpad/bug-429811 into lp://staging/launchpad
- bug-429811
- Merge into devel
Proposed by
Jeroen T. Vermeulen
Status: | Merged |
---|---|
Approved by: | Stuart Bishop |
Approved revision: | no longer in the source branch. |
Merged at revision: | not available |
Proposed branch: | lp://staging/~jtv/launchpad/bug-429811 |
Merge into: | lp://staging/launchpad |
Diff against target: | None lines |
To merge this branch: | bzr merge lp://staging/~jtv/launchpad/bug-429811 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Stuart Bishop (community) | Approve | ||
Review via email:
|
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Jeroen T. Vermeulen (jtv) wrote : | # |
Revision history for this message
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Stuart Bishop (stub) wrote : | # |
IMasterStore(
Limiting the shortlist to 2 elements is wrong, as we know we can get larger numbers and the code handles this case. Bump it up.
review:
Approve
Revision history for this message
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Stuart Bishop (stub) wrote : | # |
> IMasterStore(
> IStoreSelector.
Sorry. IStore(POTemplate) for the default store.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'lib/lp/translations/model/potemplate.py' | |||
2 | --- lib/lp/translations/model/potemplate.py 2009-08-05 14:54:11 +0000 | |||
3 | +++ lib/lp/translations/model/potemplate.py 2009-09-15 05:25:13 +0000 | |||
4 | @@ -24,7 +24,7 @@ | |||
5 | 24 | from sqlobject import ( | 24 | from sqlobject import ( |
6 | 25 | BoolCol, ForeignKey, IntCol, SQLMultipleJoin, SQLObjectNotFound, | 25 | BoolCol, ForeignKey, IntCol, SQLMultipleJoin, SQLObjectNotFound, |
7 | 26 | StringCol) | 26 | StringCol) |
9 | 27 | from storm.expr import Alias, And, LeftJoin, SQL | 27 | from storm.expr import Alias, And, LeftJoin, Or, SQL |
10 | 28 | from storm.info import ClassAlias | 28 | from storm.info import ClassAlias |
11 | 29 | from storm.store import Store | 29 | from storm.store import Store |
12 | 30 | from zope.component import getAdapter, getUtility | 30 | from zope.component import getAdapter, getUtility |
13 | @@ -38,6 +38,8 @@ | |||
14 | 38 | from canonical.database.sqlbase import ( | 38 | from canonical.database.sqlbase import ( |
15 | 39 | SQLBase, quote, flush_database_updates, sqlvalues) | 39 | SQLBase, quote, flush_database_updates, sqlvalues) |
16 | 40 | from canonical.launchpad import helpers | 40 | from canonical.launchpad import helpers |
17 | 41 | from canonical.launchpad.webapp.interfaces import ( | ||
18 | 42 | IStoreSelector, MAIN_STORE, DEFAULT_FLAVOR) | ||
19 | 41 | from lp.translations.utilities.rosettastats import RosettaStats | 43 | from lp.translations.utilities.rosettastats import RosettaStats |
20 | 42 | from lp.services.worlddata.model.language import Language | 44 | from lp.services.worlddata.model.language import Language |
21 | 43 | from lp.registry.interfaces.person import validate_public_person | 45 | from lp.registry.interfaces.person import validate_public_person |
22 | @@ -1209,46 +1211,52 @@ | |||
23 | 1209 | def getPOTemplateByPathAndOrigin(self, path, productseries=None, | 1211 | def getPOTemplateByPathAndOrigin(self, path, productseries=None, |
24 | 1210 | distroseries=None, sourcepackagename=None): | 1212 | distroseries=None, sourcepackagename=None): |
25 | 1211 | """See `IPOTemplateSet`.""" | 1213 | """See `IPOTemplateSet`.""" |
62 | 1212 | if productseries is not None: | 1214 | assert (productseries is None) != (sourcepackagename is None), ( |
63 | 1213 | return POTemplate.selectOne(''' | 1215 | "Must specify either productseries or sourcepackagename.") |
64 | 1214 | POTemplate.iscurrent IS TRUE AND | 1216 | |
65 | 1215 | POTemplate.productseries = %s AND | 1217 | conditions = And( |
66 | 1216 | POTemplate.path = %s''' % sqlvalues( | 1218 | POTemplate.iscurrent == True, |
67 | 1217 | productseries.id, | 1219 | POTemplate.path == path, |
68 | 1218 | path) | 1220 | POTemplate.productseries == productseries, |
69 | 1219 | ) | 1221 | Or( |
70 | 1220 | elif sourcepackagename is not None: | 1222 | POTemplate.from_sourcepackagename == sourcepackagename, |
71 | 1221 | # The POTemplate belongs to a distribution and it could come from | 1223 | POTemplate.sourcepackagename == sourcepackagename)) |
72 | 1222 | # another package that the one it's linked at the moment so we | 1224 | |
73 | 1223 | # first check to find it at IPOTemplate.from_sourcepackagename | 1225 | store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR) |
74 | 1224 | potemplate = POTemplate.selectOne(''' | 1226 | matches = helpers.shortlist(store.find(POTemplate, conditions), 2) |
75 | 1225 | POTemplate.iscurrent IS TRUE AND | 1227 | |
76 | 1226 | POTemplate.distroseries = %s AND | 1228 | if len(matches) == 0: |
77 | 1227 | POTemplate.from_sourcepackagename = %s AND | 1229 | # Nope. Sorry. |
78 | 1228 | POTemplate.path = %s''' % sqlvalues( | 1230 | return None |
79 | 1229 | distroseries.id, | 1231 | elif len(matches) == 1: |
80 | 1230 | sourcepackagename.id, | 1232 | # Yup. Great. |
81 | 1231 | path) | 1233 | return matches[0] |
82 | 1232 | ) | 1234 | elif sourcepackagename is None: |
83 | 1233 | if potemplate is not None: | 1235 | # Multiple matches, and for a product not a package. |
84 | 1234 | # There is no potemplate in that 'path' and | 1236 | logging.warn( |
85 | 1235 | # 'from_sourcepackagename' so we do a search using the usual | 1237 | "Found %d templates with path '%s' for productseries %s" % ( |
86 | 1236 | # sourcepackagename. | 1238 | len(matches), path, productseries.title)) |
87 | 1237 | return potemplate | 1239 | return None |
52 | 1238 | |||
53 | 1239 | return POTemplate.selectOne(''' | ||
54 | 1240 | POTemplate.iscurrent IS TRUE AND | ||
55 | 1241 | POTemplate.distroseries = %s AND | ||
56 | 1242 | POTemplate.sourcepackagename = %s AND | ||
57 | 1243 | POTemplate.path = %s''' % sqlvalues( | ||
58 | 1244 | distroseries.id, | ||
59 | 1245 | sourcepackagename.id, | ||
60 | 1246 | path) | ||
61 | 1247 | ) | ||
88 | 1248 | else: | 1240 | else: |
92 | 1249 | raise AssertionError( | 1241 | # Multiple matches, for a distribution package. Prefer a |
93 | 1250 | 'Either productseries or sourcepackagename arguments must be' | 1242 | # match on from_sourcepackagename: the file may have been |
94 | 1251 | ' not None.') | 1243 | # uploaded for another package than the one it is meant to |
95 | 1244 | # be imported into. | ||
96 | 1245 | preferred_matches = [ | ||
97 | 1246 | match | ||
98 | 1247 | for match in matches | ||
99 | 1248 | if match.from_sourcepackagename == sourcepackagename | ||
100 | 1249 | ] | ||
101 | 1250 | |||
102 | 1251 | if len(preferred_matches) == 1: | ||
103 | 1252 | return preferred_matches[0] | ||
104 | 1253 | else: | ||
105 | 1254 | logging.warn( | ||
106 | 1255 | "Found %d templates with path '%s' for package %s " | ||
107 | 1256 | "(%d matched on from_sourcepackagename)." % ( | ||
108 | 1257 | len(matches), path, sourcepackagename.name, | ||
109 | 1258 | len(preferred_matches))) | ||
110 | 1259 | return None | ||
111 | 1252 | 1260 | ||
112 | 1253 | @staticmethod | 1261 | @staticmethod |
113 | 1254 | def compareSharingPrecedence(left, right): | 1262 | def compareSharingPrecedence(left, right): |
114 | 1255 | 1263 | ||
115 | === modified file 'lib/lp/translations/tests/test_autoapproval.py' | |||
116 | --- lib/lp/translations/tests/test_autoapproval.py 2009-08-06 13:56:17 +0000 | |||
117 | +++ lib/lp/translations/tests/test_autoapproval.py 2009-09-15 05:25:13 +0000 | |||
118 | @@ -262,6 +262,8 @@ | |||
119 | 262 | self.factory = LaunchpadObjectFactory() | 262 | self.factory = LaunchpadObjectFactory() |
120 | 263 | self.templateset = POTemplateSet() | 263 | self.templateset = POTemplateSet() |
121 | 264 | 264 | ||
122 | 265 | def _setUpProduct(self): | ||
123 | 266 | """Set up a `Product` with release series and two templates.""" | ||
124 | 265 | self.product = self.factory.makeProduct() | 267 | self.product = self.factory.makeProduct() |
125 | 266 | self.productseries = self.factory.makeSeries(product=self.product) | 268 | self.productseries = self.factory.makeSeries(product=self.product) |
126 | 267 | product_subset = POTemplateSubset(productseries=self.productseries) | 269 | product_subset = POTemplateSubset(productseries=self.productseries) |
127 | @@ -270,6 +272,8 @@ | |||
128 | 270 | self.producttemplate2 = product_subset.new( | 272 | self.producttemplate2 = product_subset.new( |
129 | 271 | 'test2', 'test2', 'test.pot', self.product.owner) | 273 | 'test2', 'test2', 'test.pot', self.product.owner) |
130 | 272 | 274 | ||
131 | 275 | def _setUpDistro(self): | ||
132 | 276 | """Set up a `Distribution` with two templates.""" | ||
133 | 273 | self.distro = self.factory.makeDistribution() | 277 | self.distro = self.factory.makeDistribution() |
134 | 274 | self.distroseries = self.factory.makeDistroRelease( | 278 | self.distroseries = self.factory.makeDistroRelease( |
135 | 275 | distribution=self.distro) | 279 | distribution=self.distro) |
136 | @@ -283,9 +287,46 @@ | |||
137 | 283 | self.distrotemplate2 = distro_subset.new( | 287 | self.distrotemplate2 = distro_subset.new( |
138 | 284 | 'test2', 'test2', 'test.pot', self.distro.owner) | 288 | 'test2', 'test2', 'test.pot', self.distro.owner) |
139 | 285 | 289 | ||
140 | 290 | def test_ByPathAndOrigin_product_duplicate(self): | ||
141 | 291 | # When multiple templates match for a product series, | ||
142 | 292 | # getPOTemplateByPathAndOrigin returns none. | ||
143 | 293 | self._setUpProduct() | ||
144 | 294 | guessed_template = self.templateset.getPOTemplateByPathAndOrigin( | ||
145 | 295 | 'test.pot', productseries=self.productseries) | ||
146 | 296 | self.assertEqual(None, guessed_template) | ||
147 | 297 | |||
148 | 298 | def test_ByPathAndOrigin_package_duplicate(self): | ||
149 | 299 | # When multiple templates match on sourcepackagename, | ||
150 | 300 | # getPOTemplateByPathAndOrigin returns none. | ||
151 | 301 | self._setUpDistro() | ||
152 | 302 | guessed_template = self.templateset.getPOTemplateByPathAndOrigin( | ||
153 | 303 | 'test.pot', sourcepackagename=self.packagename) | ||
154 | 304 | self.assertEqual(None, guessed_template) | ||
155 | 305 | |||
156 | 306 | def test_ByPathAndOrigin_from_package_duplicate(self): | ||
157 | 307 | # When multiple templates match on from_sourcepackagename, | ||
158 | 308 | # getPOTemplateByPathAndOrigin returns none. | ||
159 | 309 | self._setUpDistro() | ||
160 | 310 | guessed_template = self.templateset.getPOTemplateByPathAndOrigin( | ||
161 | 311 | 'test.pot', sourcepackagename=self.from_packagename) | ||
162 | 312 | self.assertEqual(None, guessed_template) | ||
163 | 313 | |||
164 | 314 | def test_ByPathAndOrigin_preferred_match(self): | ||
165 | 315 | # getPOTemplateByPathAndOrigin prefers from_sourcepackagename | ||
166 | 316 | # matches over sourcepackagename matches. | ||
167 | 317 | self._setUpDistro() | ||
168 | 318 | match_package = SourcePackageNameSet().new('match-package') | ||
169 | 319 | self.distrotemplate1.sourcepackagename = match_package | ||
170 | 320 | self.distrotemplate2.from_sourcepackagename = match_package | ||
171 | 321 | |||
172 | 322 | guessed_template = self.templateset.getPOTemplateByPathAndOrigin( | ||
173 | 323 | 'test.pot', sourcepackagename=match_package) | ||
174 | 324 | self.assertEqual(self.distrotemplate2, guessed_template) | ||
175 | 325 | |||
176 | 286 | def test_ByPathAndOriginProductNonCurrentDuplicate(self): | 326 | def test_ByPathAndOriginProductNonCurrentDuplicate(self): |
177 | 287 | # If two templates for the same product series have the same | 327 | # If two templates for the same product series have the same |
178 | 288 | # path, but only one is current, that one is returned. | 328 | # path, but only one is current, that one is returned. |
179 | 329 | self._setUpProduct() | ||
180 | 289 | self.producttemplate1.iscurrent = False | 330 | self.producttemplate1.iscurrent = False |
181 | 290 | self.producttemplate2.iscurrent = True | 331 | self.producttemplate2.iscurrent = True |
182 | 291 | guessed_template = self.templateset.getPOTemplateByPathAndOrigin( | 332 | guessed_template = self.templateset.getPOTemplateByPathAndOrigin( |
183 | @@ -294,6 +335,7 @@ | |||
184 | 294 | 335 | ||
185 | 295 | def test_ByPathAndOriginProductNoCurrentTemplate(self): | 336 | def test_ByPathAndOriginProductNoCurrentTemplate(self): |
186 | 296 | # Non-current templates in product series are ignored. | 337 | # Non-current templates in product series are ignored. |
187 | 338 | self._setUpProduct() | ||
188 | 297 | self.producttemplate1.iscurrent = False | 339 | self.producttemplate1.iscurrent = False |
189 | 298 | self.producttemplate2.iscurrent = False | 340 | self.producttemplate2.iscurrent = False |
190 | 299 | guessed_template = self.templateset.getPOTemplateByPathAndOrigin( | 341 | guessed_template = self.templateset.getPOTemplateByPathAndOrigin( |
191 | @@ -304,6 +346,7 @@ | |||
192 | 304 | # If two templates for the same distroseries and source package | 346 | # If two templates for the same distroseries and source package |
193 | 305 | # have the same path, but only one is current, the current one | 347 | # have the same path, but only one is current, the current one |
194 | 306 | # is returned. | 348 | # is returned. |
195 | 349 | self._setUpDistro() | ||
196 | 307 | self.distrotemplate1.iscurrent = False | 350 | self.distrotemplate1.iscurrent = False |
197 | 308 | self.distrotemplate2.iscurrent = True | 351 | self.distrotemplate2.iscurrent = True |
198 | 309 | self.distrotemplate1.from_sourcepackagename = None | 352 | self.distrotemplate1.from_sourcepackagename = None |
199 | @@ -315,6 +358,7 @@ | |||
200 | 315 | 358 | ||
201 | 316 | def test_ByPathAndOriginDistroNoCurrentTemplate(self): | 359 | def test_ByPathAndOriginDistroNoCurrentTemplate(self): |
202 | 317 | # Non-current templates in distroseries are ignored. | 360 | # Non-current templates in distroseries are ignored. |
203 | 361 | self._setUpDistro() | ||
204 | 318 | self.distrotemplate1.iscurrent = False | 362 | self.distrotemplate1.iscurrent = False |
205 | 319 | self.distrotemplate2.iscurrent = False | 363 | self.distrotemplate2.iscurrent = False |
206 | 320 | self.distrotemplate1.from_sourcepackagename = None | 364 | self.distrotemplate1.from_sourcepackagename = None |
207 | @@ -328,6 +372,7 @@ | |||
208 | 328 | # If two templates for the same distroseries and original source | 372 | # If two templates for the same distroseries and original source |
209 | 329 | # package have the same path, but only one is current, that one is | 373 | # package have the same path, but only one is current, that one is |
210 | 330 | # returned. | 374 | # returned. |
211 | 375 | self._setUpDistro() | ||
212 | 331 | self.distrotemplate1.iscurrent = False | 376 | self.distrotemplate1.iscurrent = False |
213 | 332 | self.distrotemplate2.iscurrent = True | 377 | self.distrotemplate2.iscurrent = True |
214 | 333 | self.distrotemplate1.from_sourcepackagename = self.from_packagename | 378 | self.distrotemplate1.from_sourcepackagename = self.from_packagename |
215 | @@ -340,6 +385,7 @@ | |||
216 | 340 | def test_ByPathAndOriginDistroFromSourcePackageNoCurrentTemplate(self): | 385 | def test_ByPathAndOriginDistroFromSourcePackageNoCurrentTemplate(self): |
217 | 341 | # Non-current templates in distroseries are ignored by the | 386 | # Non-current templates in distroseries are ignored by the |
218 | 342 | # "from_sourcepackagename" match. | 387 | # "from_sourcepackagename" match. |
219 | 388 | self._setUpDistro() | ||
220 | 343 | self.distrotemplate1.iscurrent = False | 389 | self.distrotemplate1.iscurrent = False |
221 | 344 | self.distrotemplate2.iscurrent = False | 390 | self.distrotemplate2.iscurrent = False |
222 | 345 | self.distrotemplate1.from_sourcepackagename = self.from_packagename | 391 | self.distrotemplate1.from_sourcepackagename = self.from_packagename |
223 | @@ -352,12 +398,14 @@ | |||
224 | 352 | def test_ByTranslationDomain(self): | 398 | def test_ByTranslationDomain(self): |
225 | 353 | # getPOTemplateByTranslationDomain looks up a template by | 399 | # getPOTemplateByTranslationDomain looks up a template by |
226 | 354 | # translation domain. | 400 | # translation domain. |
227 | 401 | self._setUpDistro() | ||
228 | 355 | subset = POTemplateSubset(distroseries=self.distroseries) | 402 | subset = POTemplateSubset(distroseries=self.distroseries) |
229 | 356 | potemplate = subset.getPOTemplateByTranslationDomain('test1') | 403 | potemplate = subset.getPOTemplateByTranslationDomain('test1') |
230 | 357 | self.assertEqual(potemplate, self.distrotemplate1) | 404 | self.assertEqual(potemplate, self.distrotemplate1) |
231 | 358 | 405 | ||
232 | 359 | def test_ByTranslationDomain_none(self): | 406 | def test_ByTranslationDomain_none(self): |
233 | 360 | # Test getPOTemplateByTranslationDomain for the zero-match case. | 407 | # Test getPOTemplateByTranslationDomain for the zero-match case. |
234 | 408 | self._setUpDistro() | ||
235 | 361 | subset = POTemplateSubset(distroseries=self.distroseries) | 409 | subset = POTemplateSubset(distroseries=self.distroseries) |
236 | 362 | potemplate = subset.getPOTemplateByTranslationDomain('notesthere') | 410 | potemplate = subset.getPOTemplateByTranslationDomain('notesthere') |
237 | 363 | self.assertEqual(potemplate, None) | 411 | self.assertEqual(potemplate, None) |
238 | @@ -365,6 +413,7 @@ | |||
239 | 365 | def test_ByTranslationDomain_duplicate(self): | 413 | def test_ByTranslationDomain_duplicate(self): |
240 | 366 | # getPOTemplateByTranslationDomain returns no template if there | 414 | # getPOTemplateByTranslationDomain returns no template if there |
241 | 367 | # is more than one match. | 415 | # is more than one match. |
242 | 416 | self._setUpDistro() | ||
243 | 368 | self.distrotemplate1.iscurrent = True | 417 | self.distrotemplate1.iscurrent = True |
244 | 369 | other_package = SourcePackageNameSet().new('other-package') | 418 | other_package = SourcePackageNameSet().new('other-package') |
245 | 370 | other_subset = POTemplateSubset( | 419 | other_subset = POTemplateSubset( |
246 | @@ -385,6 +434,7 @@ | |||
247 | 385 | # To tickle this condition, the user first has to upload a file | 434 | # To tickle this condition, the user first has to upload a file |
248 | 386 | # that's not attached to a template; then upload another one | 435 | # that's not attached to a template; then upload another one |
249 | 387 | # that is, before the first one goes into auto-approval. | 436 | # that is, before the first one goes into auto-approval. |
250 | 437 | self._setUpProduct() | ||
251 | 388 | queue = TranslationImportQueue() | 438 | queue = TranslationImportQueue() |
252 | 389 | template = self.producttemplate1 | 439 | template = self.producttemplate1 |
253 | 390 | 440 |
= Bug 429811 =
There's a case in auto-approval of uploads where the approver *hopes* to find
exactly one match for a template. It's probably a bad thing for there to be
multiple matches, but it's not unthinkable and it definitely shouldn't break the
entire approval script. We've seen and fixed that sort of breakage before.
Today it failed, just once. It's not serious, just a little nuisance that
causes unnecessary alarm on the error-reports list.
This branch replaces a NotOneError for that situation with a log message. This
also allowed me to make the unit tests for that code a bit more comprehensive,
since it didn't previously touch the disaster case.
Tests:
{{{
./bin/test -vv -t test_autoapproval
}}}
No lint. For QA, see that auto-approval still works and note that the
traceback doesn't recur over time. Instead, there may be the occasional log
message.
Jeroen