Merge lp:~cjwatson/launchpad/potemplate-dsp-vocab into lp:launchpad
- potemplate-dsp-vocab
- Merge into devel
Proposed by
Colin Watson
Status: | Merged | ||||||||
---|---|---|---|---|---|---|---|---|---|
Merged at revision: | 18199 | ||||||||
Proposed branch: | lp:~cjwatson/launchpad/potemplate-dsp-vocab | ||||||||
Merge into: | lp:launchpad | ||||||||
Prerequisite: | lp:~cjwatson/launchpad/productseries-ubuntupkg-dsp-vocab | ||||||||
Diff against target: |
583 lines (+347/-28) 8 files modified
lib/lp/app/widgets/popup.py (+8/-0) lib/lp/app/widgets/templates/distributionsourcepackage-picker.pt (+5/-0) lib/lp/bugs/browser/widgets/bugtask.py (+1/-2) lib/lp/bugs/doc/bugtask-package-widget.txt (+18/-21) lib/lp/translations/browser/potemplate.py (+51/-2) lib/lp/translations/browser/tests/test_potemplate_views.py (+68/-3) lib/lp/translations/browser/widgets/potemplate.py (+62/-0) lib/lp/translations/browser/widgets/tests/test_potemplate.py (+134/-0) |
||||||||
To merge this branch: | bzr merge lp:~cjwatson/launchpad/potemplate-dsp-vocab | ||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
William Grant | code | Approve | |
Review via email: mp+305490@code.launchpad.net |
Commit message
Convert POTemplate:+edit and POTemplate:+admin to use the DistributionSou
Description of the change
Convert POTemplate:+edit and POTemplate:+admin to use the DistributionSou
We need another hack in the picker setup to cope with getting the distribution from the distroseries field, and we have to be a bit more careful about how we process empty forms so that GET requests work properly, but the rest is fairly straightforward.
To post a comment you must log in.
Revision history for this message
William Grant (wgrant) : | # |
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/app/widgets/popup.py' | |||
2 | --- lib/lp/app/widgets/popup.py 2016-09-12 15:29:11 +0000 | |||
3 | +++ lib/lp/app/widgets/popup.py 2016-09-12 15:29:12 +0000 | |||
4 | @@ -305,6 +305,7 @@ | |||
5 | 305 | return self._prefix + 'distribution' | 305 | return self._prefix + 'distribution' |
6 | 306 | 306 | ||
7 | 307 | distribution_name = '' | 307 | distribution_name = '' |
8 | 308 | distroseries_id = '' | ||
9 | 308 | 309 | ||
10 | 309 | 310 | ||
11 | 310 | class SourcePackageNameWidgetBase(DistributionSourcePackagePickerWidget): | 311 | class SourcePackageNameWidgetBase(DistributionSourcePackagePickerWidget): |
12 | @@ -325,6 +326,13 @@ | |||
13 | 325 | super(SourcePackageNameWidgetBase, self).__init__( | 326 | super(SourcePackageNameWidgetBase, self).__init__( |
14 | 326 | field, vocabulary, request) | 327 | field, vocabulary, request) |
15 | 327 | self.cached_values = {} | 328 | self.cached_values = {} |
16 | 329 | if bool(getFeatureFlag('disclosure.dsp_picker.enabled')): | ||
17 | 330 | # The distribution may change later when we process form input, | ||
18 | 331 | # but setting it here makes it easier to construct some views, | ||
19 | 332 | # particularly edit views where we need to render the context. | ||
20 | 333 | distribution = self.getDistribution() | ||
21 | 334 | if distribution is not None: | ||
22 | 335 | self.context.vocabulary.setDistribution(distribution) | ||
23 | 328 | 336 | ||
24 | 329 | def getDistribution(self): | 337 | def getDistribution(self): |
25 | 330 | """Get the distribution used for package validation. | 338 | """Get the distribution used for package validation. |
26 | 331 | 339 | ||
27 | === modified file 'lib/lp/app/widgets/templates/distributionsourcepackage-picker.pt' | |||
28 | --- lib/lp/app/widgets/templates/distributionsourcepackage-picker.pt 2016-07-21 20:22:00 +0000 | |||
29 | +++ lib/lp/app/widgets/templates/distributionsourcepackage-picker.pt 2016-09-12 15:29:12 +0000 | |||
30 | @@ -9,6 +9,7 @@ | |||
31 | 9 | var config = ${view/json_config}; | 9 | var config = ${view/json_config}; |
32 | 10 | var distribution_name = '${view/distribution_name}'; | 10 | var distribution_name = '${view/distribution_name}'; |
33 | 11 | var distribution_id = '${view/distribution_id}'; | 11 | var distribution_id = '${view/distribution_id}'; |
34 | 12 | var distroseries_id = '${view/distroseries_id}'; | ||
35 | 12 | if (distribution_name !== '') { | 13 | if (distribution_name !== '') { |
36 | 13 | config.getContextPath = function() { | 14 | config.getContextPath = function() { |
37 | 14 | return '/' + distribution_name; | 15 | return '/' + distribution_name; |
38 | @@ -17,6 +18,10 @@ | |||
39 | 17 | config.getContextPath = function() { | 18 | config.getContextPath = function() { |
40 | 18 | return '/' + Y.DOM.byId(distribution_id).value; | 19 | return '/' + Y.DOM.byId(distribution_id).value; |
41 | 19 | }; | 20 | }; |
42 | 21 | } else if (distroseries_id !== '') { | ||
43 | 22 | config.getContextPath = function() { | ||
44 | 23 | return '/' + Y.DOM.byId(distroseries_id).value.split('/', 2)[0]; | ||
45 | 24 | }; | ||
46 | 20 | } | 25 | } |
47 | 21 | var show_widget_id = '${view/show_widget_id}'; | 26 | var show_widget_id = '${view/show_widget_id}'; |
48 | 22 | Y.on('domready', function(e) { | 27 | Y.on('domready', function(e) { |
49 | 23 | 28 | ||
50 | === modified file 'lib/lp/bugs/browser/widgets/bugtask.py' | |||
51 | --- lib/lp/bugs/browser/widgets/bugtask.py 2016-09-12 15:29:11 +0000 | |||
52 | +++ lib/lp/bugs/browser/widgets/bugtask.py 2016-09-12 15:29:12 +0000 | |||
53 | @@ -505,8 +505,7 @@ | |||
54 | 505 | """See `SourcePackageNameWidgetBase`.""" | 505 | """See `SourcePackageNameWidgetBase`.""" |
55 | 506 | distribution_name = self.request.form.get('field.distribution') | 506 | distribution_name = self.request.form.get('field.distribution') |
56 | 507 | if distribution_name is None: | 507 | if distribution_name is None: |
59 | 508 | raise UnexpectedFormData( | 508 | return None |
58 | 509 | "field.distribution wasn't in the request") | ||
60 | 510 | distribution = getUtility(IDistributionSet).getByName( | 509 | distribution = getUtility(IDistributionSet).getByName( |
61 | 511 | distribution_name) | 510 | distribution_name) |
62 | 512 | if distribution is None: | 511 | if distribution is None: |
63 | 513 | 512 | ||
64 | === modified file 'lib/lp/bugs/doc/bugtask-package-widget.txt' | |||
65 | --- lib/lp/bugs/doc/bugtask-package-widget.txt 2016-09-12 15:29:11 +0000 | |||
66 | +++ lib/lp/bugs/doc/bugtask-package-widget.txt 2016-09-12 15:29:12 +0000 | |||
67 | @@ -118,33 +118,30 @@ | |||
68 | 118 | >>> request = LaunchpadTestRequest( | 118 | >>> request = LaunchpadTestRequest( |
69 | 119 | ... form={'field.distribution': 'debian', | 119 | ... form={'field.distribution': 'debian', |
70 | 120 | ... 'field.sourcepackagename': 'linux-2.6.12'}) | 120 | ... 'field.sourcepackagename': 'linux-2.6.12'}) |
74 | 121 | >>> widget = BugTaskAlsoAffectsSourcePackageNameWidget( | 121 | >>> BugTaskAlsoAffectsSourcePackageNameWidget( |
75 | 122 | ... package_field, package_field.vocabulary, request) | 122 | ... package_field, package_field.vocabulary, |
76 | 123 | >>> widget.getDistribution().name | 123 | ... request).getDistribution().name |
77 | 124 | u'debian' | 124 | u'debian' |
78 | 125 | 125 | ||
82 | 126 | +distrotask always supplies a valid distribution name. If the widget | 126 | +distrotask always supplies a valid distribution name or none at all. If the |
83 | 127 | doesn't get a name, or the name isn't the name of a distro, | 127 | name isn't the name of a distro, UnexpectedFormData is raised. |
81 | 128 | UnexpectedFormData is raised. | ||
84 | 129 | 128 | ||
85 | 130 | >>> request = LaunchpadTestRequest( | 129 | >>> request = LaunchpadTestRequest( |
86 | 131 | ... form={'field.distribution': 'non-existing', | 130 | ... form={'field.distribution': 'non-existing', |
87 | 132 | ... 'field.sourcepackagename': 'linux-2.6.12'}) | 131 | ... 'field.sourcepackagename': 'linux-2.6.12'}) |
103 | 133 | >>> widget = BugTaskAlsoAffectsSourcePackageNameWidget( | 132 | >>> BugTaskAlsoAffectsSourcePackageNameWidget( |
104 | 134 | ... package_field, package_field.vocabulary, request) | 133 | ... package_field, package_field.vocabulary, |
105 | 135 | >>> widget.getDistribution().name | 134 | ... request).getDistribution().name |
106 | 136 | Traceback (most recent call last): | 135 | Traceback (most recent call last): |
107 | 137 | ... | 136 | ... |
108 | 138 | UnexpectedFormData: ... | 137 | UnexpectedFormData: ... |
109 | 139 | 138 | ||
110 | 140 | >>> request = LaunchpadTestRequest( | 139 | A GET request usually won't supply a distribution name at all. |
111 | 141 | ... form={'field.sourcepackagename': 'linux-2.6.12'}) | 140 | |
112 | 142 | >>> widget = BugTaskAlsoAffectsSourcePackageNameWidget( | 141 | >>> request = LaunchpadTestRequest(form={}) |
113 | 143 | ... package_field, package_field.vocabulary, request) | 142 | >>> BugTaskAlsoAffectsSourcePackageNameWidget( |
114 | 144 | >>> widget.getDistribution().name | 143 | ... package_field, package_field.vocabulary, |
115 | 145 | Traceback (most recent call last): | 144 | ... request).getDistribution() |
101 | 146 | ... | ||
102 | 147 | UnexpectedFormData: ... | ||
116 | 148 | 145 | ||
117 | 149 | 146 | ||
118 | 150 | FileBugSourcePackageNameWidget | 147 | FileBugSourcePackageNameWidget |
119 | 151 | 148 | ||
120 | === modified file 'lib/lp/translations/browser/potemplate.py' | |||
121 | --- lib/lp/translations/browser/potemplate.py 2016-06-09 20:13:17 +0000 | |||
122 | +++ lib/lp/translations/browser/potemplate.py 2016-09-12 15:29:12 +0000 | |||
123 | @@ -1,4 +1,4 @@ | |||
125 | 1 | # Copyright 2009-2012 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009-2016 Canonical Ltd. This software is licensed under the |
126 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
127 | 3 | """Browser code for PO templates.""" | 3 | """Browser code for PO templates.""" |
128 | 4 | 4 | ||
129 | @@ -26,6 +26,7 @@ | |||
130 | 26 | import operator | 26 | import operator |
131 | 27 | import os.path | 27 | import os.path |
132 | 28 | 28 | ||
133 | 29 | from lazr.restful.interface import copy_field | ||
134 | 29 | from lazr.restful.utils import smartquote | 30 | from lazr.restful.utils import smartquote |
135 | 30 | import pytz | 31 | import pytz |
136 | 31 | from storm.expr import ( | 32 | from storm.expr import ( |
137 | @@ -41,6 +42,7 @@ | |||
138 | 41 | from lp import _ | 42 | from lp import _ |
139 | 42 | from lp.app.browser.launchpadform import ( | 43 | from lp.app.browser.launchpadform import ( |
140 | 43 | action, | 44 | action, |
141 | 45 | custom_widget, | ||
142 | 44 | LaunchpadEditFormView, | 46 | LaunchpadEditFormView, |
143 | 45 | ReturnToReferrerMixin, | 47 | ReturnToReferrerMixin, |
144 | 46 | ) | 48 | ) |
145 | @@ -51,11 +53,15 @@ | |||
146 | 51 | ) | 53 | ) |
147 | 52 | from lp.app.errors import NotFoundError | 54 | from lp.app.errors import NotFoundError |
148 | 53 | from lp.app.validators.name import valid_name | 55 | from lp.app.validators.name import valid_name |
149 | 56 | from lp.registry.interfaces.distributionsourcepackage import ( | ||
150 | 57 | IDistributionSourcePackage, | ||
151 | 58 | ) | ||
152 | 54 | from lp.registry.interfaces.role import IPersonRoles | 59 | from lp.registry.interfaces.role import IPersonRoles |
153 | 55 | from lp.registry.model.packaging import Packaging | 60 | from lp.registry.model.packaging import Packaging |
154 | 56 | from lp.registry.model.product import Product | 61 | from lp.registry.model.product import Product |
155 | 57 | from lp.registry.model.productseries import ProductSeries | 62 | from lp.registry.model.productseries import ProductSeries |
156 | 58 | from lp.registry.model.sourcepackagename import SourcePackageName | 63 | from lp.registry.model.sourcepackagename import SourcePackageName |
157 | 64 | from lp.services.features import getFeatureFlag | ||
158 | 59 | from lp.services.helpers import is_tar_filename | 65 | from lp.services.helpers import is_tar_filename |
159 | 60 | from lp.services.propertycache import cachedproperty | 66 | from lp.services.propertycache import cachedproperty |
160 | 61 | from lp.services.webapp import ( | 67 | from lp.services.webapp import ( |
161 | @@ -86,6 +92,10 @@ | |||
162 | 86 | from lp.translations.browser.translationsharing import ( | 92 | from lp.translations.browser.translationsharing import ( |
163 | 87 | TranslationSharingDetailsMixin, | 93 | TranslationSharingDetailsMixin, |
164 | 88 | ) | 94 | ) |
165 | 95 | from lp.translations.browser.widgets.potemplate import ( | ||
166 | 96 | POTemplateAdminSourcePackageNameWidget, | ||
167 | 97 | POTemplateEditSourcePackageNameWidget, | ||
168 | 98 | ) | ||
169 | 89 | from lp.translations.interfaces.pofile import IPOFileSet | 99 | from lp.translations.interfaces.pofile import IPOFileSet |
170 | 90 | from lp.translations.interfaces.potemplate import ( | 100 | from lp.translations.interfaces.potemplate import ( |
171 | 91 | IPOTemplate, | 101 | IPOTemplate, |
172 | @@ -492,10 +502,29 @@ | |||
173 | 492 | return POTemplateView.pofiles(self, preferred_only=True) | 502 | return POTemplateView.pofiles(self, preferred_only=True) |
174 | 493 | 503 | ||
175 | 494 | 504 | ||
176 | 505 | class IPOTemplateEditForm(IPOTemplate): | ||
177 | 506 | |||
178 | 507 | sourcepackagename = copy_field( | ||
179 | 508 | IPOTemplate['sourcepackagename'], | ||
180 | 509 | vocabularyName='DistributionSourcePackage') | ||
181 | 510 | |||
182 | 511 | from_sourcepackagename = copy_field( | ||
183 | 512 | IPOTemplate['from_sourcepackagename'], | ||
184 | 513 | vocabularyName='DistributionSourcePackage') | ||
185 | 514 | |||
186 | 515 | |||
187 | 495 | class POTemplateEditView(ReturnToReferrerMixin, LaunchpadEditFormView): | 516 | class POTemplateEditView(ReturnToReferrerMixin, LaunchpadEditFormView): |
188 | 496 | """View class that lets you edit a POTemplate object.""" | 517 | """View class that lets you edit a POTemplate object.""" |
189 | 497 | 518 | ||
191 | 498 | schema = IPOTemplate | 519 | @property |
192 | 520 | def schema(self): | ||
193 | 521 | """See `LaunchpadFormView`.""" | ||
194 | 522 | if bool(getFeatureFlag('disclosure.dsp_picker.enabled')): | ||
195 | 523 | return IPOTemplateEditForm | ||
196 | 524 | else: | ||
197 | 525 | return IPOTemplate | ||
198 | 526 | |||
199 | 527 | custom_widget('sourcepackagename', POTemplateEditSourcePackageNameWidget) | ||
200 | 499 | label = 'Edit translation template details' | 528 | label = 'Edit translation template details' |
201 | 500 | page_title = 'Edit details' | 529 | page_title = 'Edit details' |
202 | 501 | PRIORITY_MIN_VALUE = 0 | 530 | PRIORITY_MIN_VALUE = 0 |
203 | @@ -516,6 +545,14 @@ | |||
204 | 516 | return field_names | 545 | return field_names |
205 | 517 | 546 | ||
206 | 518 | @property | 547 | @property |
207 | 548 | def adapters(self): | ||
208 | 549 | """See `LaunchpadFormView`.""" | ||
209 | 550 | if bool(getFeatureFlag('disclosure.dsp_picker.enabled')): | ||
210 | 551 | return {IPOTemplateEditForm: self.context} | ||
211 | 552 | else: | ||
212 | 553 | return {} | ||
213 | 554 | |||
214 | 555 | @property | ||
215 | 519 | def _return_url(self): | 556 | def _return_url(self): |
216 | 520 | # We override the ReturnToReferrerMixin _return_url because it might | 557 | # We override the ReturnToReferrerMixin _return_url because it might |
217 | 521 | # change when any of the productseries, distroseries, | 558 | # change when any of the productseries, distroseries, |
218 | @@ -548,6 +585,9 @@ | |||
219 | 548 | context.setActive(iscurrent) | 585 | context.setActive(iscurrent) |
220 | 549 | old_description = context.description | 586 | old_description = context.description |
221 | 550 | old_translation_domain = context.translation_domain | 587 | old_translation_domain = context.translation_domain |
222 | 588 | for field in ('sourcepackagename', 'from_sourcepackagename'): | ||
223 | 589 | if IDistributionSourcePackage.providedBy(data.get(field)): | ||
224 | 590 | data[field] = data[field].sourcepackagename | ||
225 | 551 | self.updateContextFromData(data) | 591 | self.updateContextFromData(data) |
226 | 552 | if old_description != context.description: | 592 | if old_description != context.description: |
227 | 553 | self.user.assignKarma( | 593 | self.user.assignKarma( |
228 | @@ -565,6 +605,8 @@ | |||
229 | 565 | """Return a POTemplateSubset corresponding to the chosen target.""" | 605 | """Return a POTemplateSubset corresponding to the chosen target.""" |
230 | 566 | sourcepackagename = data.get('sourcepackagename', | 606 | sourcepackagename = data.get('sourcepackagename', |
231 | 567 | self.context.sourcepackagename) | 607 | self.context.sourcepackagename) |
232 | 608 | if IDistributionSourcePackage.providedBy(sourcepackagename): | ||
233 | 609 | sourcepackagename = sourcepackagename.sourcepackagename | ||
234 | 568 | return getUtility(IPOTemplateSet).getSubset( | 610 | return getUtility(IPOTemplateSet).getSubset( |
235 | 569 | distroseries=self.context.distroseries, | 611 | distroseries=self.context.distroseries, |
236 | 570 | sourcepackagename=sourcepackagename, | 612 | sourcepackagename=sourcepackagename, |
237 | @@ -582,6 +624,8 @@ | |||
238 | 582 | distroseries = data.get('distroseries', self.context.distroseries) | 624 | distroseries = data.get('distroseries', self.context.distroseries) |
239 | 583 | sourcepackagename = data.get( | 625 | sourcepackagename = data.get( |
240 | 584 | 'sourcepackagename', self.context.sourcepackagename) | 626 | 'sourcepackagename', self.context.sourcepackagename) |
241 | 627 | if IDistributionSourcePackage.providedBy(sourcepackagename): | ||
242 | 628 | sourcepackagename = sourcepackagename.sourcepackagename | ||
243 | 585 | productseries = data.get('productseries', None) | 629 | productseries = data.get('productseries', None) |
244 | 586 | sourcepackage_changed = ( | 630 | sourcepackage_changed = ( |
245 | 587 | distroseries is not None and | 631 | distroseries is not None and |
246 | @@ -656,6 +700,9 @@ | |||
247 | 656 | 'from_sourcepackagename', 'sourcepackageversion', | 700 | 'from_sourcepackagename', 'sourcepackageversion', |
248 | 657 | 'languagepack', 'path', 'source_file_format', 'priority', | 701 | 'languagepack', 'path', 'source_file_format', 'priority', |
249 | 658 | 'date_last_updated'] | 702 | 'date_last_updated'] |
250 | 703 | custom_widget('sourcepackagename', POTemplateAdminSourcePackageNameWidget) | ||
251 | 704 | custom_widget( | ||
252 | 705 | 'from_sourcepackagename', POTemplateAdminSourcePackageNameWidget) | ||
253 | 659 | label = 'Administer translation template' | 706 | label = 'Administer translation template' |
254 | 660 | page_title = "Administer" | 707 | page_title = "Administer" |
255 | 661 | 708 | ||
256 | @@ -663,6 +710,8 @@ | |||
257 | 663 | """Return a POTemplateSubset corresponding to the chosen target.""" | 710 | """Return a POTemplateSubset corresponding to the chosen target.""" |
258 | 664 | distroseries = data.get('distroseries') | 711 | distroseries = data.get('distroseries') |
259 | 665 | sourcepackagename = data.get('sourcepackagename') | 712 | sourcepackagename = data.get('sourcepackagename') |
260 | 713 | if IDistributionSourcePackage.providedBy(sourcepackagename): | ||
261 | 714 | sourcepackagename = sourcepackagename.sourcepackagename | ||
262 | 666 | productseries = data.get('productseries') | 715 | productseries = data.get('productseries') |
263 | 667 | 716 | ||
264 | 668 | if distroseries is not None and productseries is not None: | 717 | if distroseries is not None and productseries is not None: |
265 | 669 | 718 | ||
266 | === modified file 'lib/lp/translations/browser/tests/test_potemplate_views.py' | |||
267 | --- lib/lp/translations/browser/tests/test_potemplate_views.py 2012-12-10 13:43:47 +0000 | |||
268 | +++ lib/lp/translations/browser/tests/test_potemplate_views.py 2016-09-12 15:29:12 +0000 | |||
269 | @@ -1,25 +1,46 @@ | |||
271 | 1 | # Copyright 2011 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2011-2016 Canonical Ltd. This software is licensed under the |
272 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
273 | 3 | 3 | ||
274 | 4 | """Module doc.""" | 4 | """Module doc.""" |
275 | 5 | 5 | ||
276 | 6 | __metaclass__ = type | 6 | __metaclass__ = type |
277 | 7 | 7 | ||
278 | 8 | from testscenarios import ( | ||
279 | 9 | load_tests_apply_scenarios, | ||
280 | 10 | WithScenarios, | ||
281 | 11 | ) | ||
282 | 8 | 12 | ||
283 | 13 | from lp.services.features.testing import FeatureFixture | ||
284 | 9 | from lp.services.webapp.escaping import html_escape | 14 | from lp.services.webapp.escaping import html_escape |
285 | 10 | from lp.services.webapp.servers import LaunchpadTestRequest | 15 | from lp.services.webapp.servers import LaunchpadTestRequest |
287 | 11 | from lp.testing import TestCaseWithFactory | 16 | from lp.testing import ( |
288 | 17 | celebrity_logged_in, | ||
289 | 18 | TestCaseWithFactory, | ||
290 | 19 | ) | ||
291 | 12 | from lp.testing.layers import DatabaseFunctionalLayer | 20 | from lp.testing.layers import DatabaseFunctionalLayer |
292 | 21 | from lp.testing.views import create_initialized_view | ||
293 | 13 | from lp.translations.browser.potemplate import ( | 22 | from lp.translations.browser.potemplate import ( |
294 | 14 | POTemplateAdminView, | 23 | POTemplateAdminView, |
295 | 15 | POTemplateEditView, | 24 | POTemplateEditView, |
296 | 16 | ) | 25 | ) |
297 | 17 | 26 | ||
298 | 18 | 27 | ||
300 | 19 | class TestPOTemplateEditViewValidation(TestCaseWithFactory): | 28 | class TestPOTemplateEditViewValidation(WithScenarios, TestCaseWithFactory): |
301 | 20 | 29 | ||
302 | 21 | layer = DatabaseFunctionalLayer | 30 | layer = DatabaseFunctionalLayer |
303 | 22 | 31 | ||
304 | 32 | scenarios = [ | ||
305 | 33 | ("spn_picker", {"features": {}}), | ||
306 | 34 | ("dsp_picker", { | ||
307 | 35 | "features": {u"disclosure.dsp_picker.enabled": u"on"}, | ||
308 | 36 | }), | ||
309 | 37 | ] | ||
310 | 38 | |||
311 | 39 | def setUp(self): | ||
312 | 40 | super(TestPOTemplateEditViewValidation, self).setUp() | ||
313 | 41 | if self.features: | ||
314 | 42 | self.useFixture(FeatureFixture(self.features)) | ||
315 | 43 | |||
316 | 23 | def _makeData(self, potemplate, **kwargs): | 44 | def _makeData(self, potemplate, **kwargs): |
317 | 24 | """Create form data for the given template with some changed values. | 45 | """Create form data for the given template with some changed values. |
318 | 25 | 46 | ||
319 | @@ -131,6 +152,23 @@ | |||
320 | 131 | [u'Source package already has a template with that same domain.'], | 152 | [u'Source package already has a template with that same domain.'], |
321 | 132 | view.errors) | 153 | view.errors) |
322 | 133 | 154 | ||
323 | 155 | def test_change_sourcepackage(self): | ||
324 | 156 | # Changing the source package is honoured. | ||
325 | 157 | distroseries = self.factory.makeDistroSeries() | ||
326 | 158 | potemplate = self.factory.makePOTemplate(distroseries=distroseries) | ||
327 | 159 | dsp = self.factory.makeDSPCache(distroseries=distroseries) | ||
328 | 160 | form = { | ||
329 | 161 | 'field.name': potemplate.name, | ||
330 | 162 | 'field.distroseries': distroseries.name, | ||
331 | 163 | 'field.sourcepackagename': dsp.sourcepackagename.name, | ||
332 | 164 | 'field.actions.change': 'Change', | ||
333 | 165 | } | ||
334 | 166 | with celebrity_logged_in('rosetta_experts'): | ||
335 | 167 | view = create_initialized_view(potemplate, '+edit', form=form) | ||
336 | 168 | self.assertEqual([], view.errors) | ||
337 | 169 | self.assertEqual( | ||
338 | 170 | dsp.sourcepackagename.name, potemplate.sourcepackagename.name) | ||
339 | 171 | |||
340 | 134 | 172 | ||
341 | 135 | class TestPOTemplateAdminViewValidation(TestPOTemplateEditViewValidation): | 173 | class TestPOTemplateAdminViewValidation(TestPOTemplateEditViewValidation): |
342 | 136 | 174 | ||
343 | @@ -194,3 +232,30 @@ | |||
344 | 194 | self.assertEqual( | 232 | self.assertEqual( |
345 | 195 | [u'Choose a distribution release series or a project ' | 233 | [u'Choose a distribution release series or a project ' |
346 | 196 | u'release series, but not both.'], view.errors) | 234 | u'release series, but not both.'], view.errors) |
347 | 235 | |||
348 | 236 | def test_change_from_sourcepackage(self): | ||
349 | 237 | # Changing the source package the template comes from is honoured. | ||
350 | 238 | distroseries = self.factory.makeDistroSeries() | ||
351 | 239 | dsp = self.factory.makeDSPCache(distroseries=distroseries) | ||
352 | 240 | potemplate = self.factory.makePOTemplate( | ||
353 | 241 | distroseries=distroseries, sourcepackagename=dsp.sourcepackagename) | ||
354 | 242 | from_dsp = self.factory.makeDSPCache(distroseries=distroseries) | ||
355 | 243 | form = { | ||
356 | 244 | 'field.name': potemplate.name, | ||
357 | 245 | 'field.distroseries': '%s/%s' % ( | ||
358 | 246 | distroseries.distribution.name, distroseries.name), | ||
359 | 247 | 'field.sourcepackagename': dsp.sourcepackagename.name, | ||
360 | 248 | 'field.from_sourcepackagename': from_dsp.sourcepackagename.name, | ||
361 | 249 | 'field.actions.change': 'Change', | ||
362 | 250 | } | ||
363 | 251 | with celebrity_logged_in('rosetta_experts'): | ||
364 | 252 | view = create_initialized_view(potemplate, '+admin', form=form) | ||
365 | 253 | self.assertEqual([], view.errors) | ||
366 | 254 | self.assertEqual( | ||
367 | 255 | dsp.sourcepackagename.name, potemplate.sourcepackagename.name) | ||
368 | 256 | self.assertEqual( | ||
369 | 257 | from_dsp.sourcepackagename.name, | ||
370 | 258 | potemplate.from_sourcepackagename.name) | ||
371 | 259 | |||
372 | 260 | |||
373 | 261 | load_tests = load_tests_apply_scenarios | ||
374 | 197 | 262 | ||
375 | === added directory 'lib/lp/translations/browser/widgets' | |||
376 | === added file 'lib/lp/translations/browser/widgets/__init__.py' | |||
377 | === added file 'lib/lp/translations/browser/widgets/potemplate.py' | |||
378 | --- lib/lp/translations/browser/widgets/potemplate.py 1970-01-01 00:00:00 +0000 | |||
379 | +++ lib/lp/translations/browser/widgets/potemplate.py 2016-09-12 15:29:12 +0000 | |||
380 | @@ -0,0 +1,62 @@ | |||
381 | 1 | # Copyright 2016 Canonical Ltd. This software is licensed under the | ||
382 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | ||
383 | 3 | |||
384 | 4 | """Widgets related to `IPOTemplate`.""" | ||
385 | 5 | |||
386 | 6 | from __future__ import absolute_import, print_function, unicode_literals | ||
387 | 7 | |||
388 | 8 | __metaclass__ = type | ||
389 | 9 | __all__ = [ | ||
390 | 10 | "POTemplateAdminSourcePackageNameWidget", | ||
391 | 11 | "POTemplateEditSourcePackageNameWidget", | ||
392 | 12 | ] | ||
393 | 13 | |||
394 | 14 | from lp.app.errors import UnexpectedFormData | ||
395 | 15 | from lp.app.widgets.popup import SourcePackageNameWidgetBase | ||
396 | 16 | from lp.registry.vocabularies import DistroSeriesVocabulary | ||
397 | 17 | |||
398 | 18 | |||
399 | 19 | class POTemplateEditSourcePackageNameWidget(SourcePackageNameWidgetBase): | ||
400 | 20 | """A widget for associating a POTemplate with a SourcePackageName. | ||
401 | 21 | |||
402 | 22 | This is suitable for use on POTemplate:+edit, where the distribution is | ||
403 | 23 | fixed. | ||
404 | 24 | """ | ||
405 | 25 | |||
406 | 26 | @property | ||
407 | 27 | def distribution_name(self): | ||
408 | 28 | distribution = self.getDistribution() | ||
409 | 29 | if distribution is not None: | ||
410 | 30 | return distribution.name | ||
411 | 31 | else: | ||
412 | 32 | return '' | ||
413 | 33 | |||
414 | 34 | def getDistribution(self): | ||
415 | 35 | """See `SourcePackageNameWidgetBase`.""" | ||
416 | 36 | return self.context.context.distribution | ||
417 | 37 | |||
418 | 38 | |||
419 | 39 | class POTemplateAdminSourcePackageNameWidget(SourcePackageNameWidgetBase): | ||
420 | 40 | """A widget for associating a POTemplate with a SourcePackageName. | ||
421 | 41 | |||
422 | 42 | This is suitable for use on POTemplate:+admin, where the distribution | ||
423 | 43 | may be changed via the distroseries field. | ||
424 | 44 | """ | ||
425 | 45 | |||
426 | 46 | @property | ||
427 | 47 | def distroseries_id(self): | ||
428 | 48 | return self._prefix + 'distroseries' | ||
429 | 49 | |||
430 | 50 | def getDistribution(self): | ||
431 | 51 | """See `SourcePackageNameWidgetBase`.""" | ||
432 | 52 | distroseries_token = self.request.form.get('field.distroseries') | ||
433 | 53 | if distroseries_token is None: | ||
434 | 54 | # Fall back to the POTemplate's current distribution. | ||
435 | 55 | return self.context.context.distribution | ||
436 | 56 | distroseries_vocab = DistroSeriesVocabulary() | ||
437 | 57 | try: | ||
438 | 58 | term = distroseries_vocab.getTermByToken(distroseries_token) | ||
439 | 59 | except LookupError: | ||
440 | 60 | raise UnexpectedFormData( | ||
441 | 61 | "No such distribution series: %s" % distroseries_token) | ||
442 | 62 | return term.value.distribution | ||
443 | 0 | 63 | ||
444 | === added directory 'lib/lp/translations/browser/widgets/tests' | |||
445 | === added file 'lib/lp/translations/browser/widgets/tests/__init__.py' | |||
446 | === added file 'lib/lp/translations/browser/widgets/tests/test_potemplate.py' | |||
447 | --- lib/lp/translations/browser/widgets/tests/test_potemplate.py 1970-01-01 00:00:00 +0000 | |||
448 | +++ lib/lp/translations/browser/widgets/tests/test_potemplate.py 2016-09-12 15:29:12 +0000 | |||
449 | @@ -0,0 +1,134 @@ | |||
450 | 1 | # Copyright 2016 Canonical Ltd. This software is licensed under the | ||
451 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | ||
452 | 3 | |||
453 | 4 | """Test the POTemplate widgets.""" | ||
454 | 5 | |||
455 | 6 | from __future__ import absolute_import, print_function, unicode_literals | ||
456 | 7 | |||
457 | 8 | __metaclass__ = type | ||
458 | 9 | |||
459 | 10 | from testscenarios import ( | ||
460 | 11 | load_tests_apply_scenarios, | ||
461 | 12 | WithScenarios, | ||
462 | 13 | ) | ||
463 | 14 | |||
464 | 15 | from lp.app.errors import UnexpectedFormData | ||
465 | 16 | from lp.services.features.testing import FeatureFixture | ||
466 | 17 | from lp.services.webapp.servers import LaunchpadTestRequest | ||
467 | 18 | from lp.testing import TestCaseWithFactory | ||
468 | 19 | from lp.testing.layers import DatabaseFunctionalLayer | ||
469 | 20 | from lp.translations.browser.potemplate import IPOTemplateEditForm | ||
470 | 21 | from lp.translations.browser.widgets.potemplate import ( | ||
471 | 22 | POTemplateAdminSourcePackageNameWidget, | ||
472 | 23 | POTemplateEditSourcePackageNameWidget, | ||
473 | 24 | ) | ||
474 | 25 | from lp.translations.interfaces.potemplate import IPOTemplate | ||
475 | 26 | |||
476 | 27 | |||
477 | 28 | class TestPOTemplateEditSourcePackageNameWidget( | ||
478 | 29 | WithScenarios, TestCaseWithFactory): | ||
479 | 30 | |||
480 | 31 | layer = DatabaseFunctionalLayer | ||
481 | 32 | |||
482 | 33 | scenarios = [ | ||
483 | 34 | ("spn_picker", { | ||
484 | 35 | "features": {}, | ||
485 | 36 | "interface": IPOTemplate, | ||
486 | 37 | }), | ||
487 | 38 | ("dsp_picker", { | ||
488 | 39 | "features": {u"disclosure.dsp_picker.enabled": u"on"}, | ||
489 | 40 | "interface": IPOTemplateEditForm, | ||
490 | 41 | }), | ||
491 | 42 | ] | ||
492 | 43 | |||
493 | 44 | def setUp(self): | ||
494 | 45 | super(TestPOTemplateEditSourcePackageNameWidget, self).setUp() | ||
495 | 46 | if self.features: | ||
496 | 47 | self.useFixture(FeatureFixture(self.features)) | ||
497 | 48 | |||
498 | 49 | def makeWidget(self, potemplate, form=None): | ||
499 | 50 | field = self.interface["sourcepackagename"] | ||
500 | 51 | bound_field = field.bind(potemplate) | ||
501 | 52 | request = LaunchpadTestRequest(form=form) | ||
502 | 53 | return POTemplateEditSourcePackageNameWidget( | ||
503 | 54 | bound_field, bound_field.vocabulary, request) | ||
504 | 55 | |||
505 | 56 | def test_productseries(self): | ||
506 | 57 | potemplate = self.factory.makePOTemplate() | ||
507 | 58 | widget = self.makeWidget(potemplate) | ||
508 | 59 | self.assertIsNone(widget.getDistribution()) | ||
509 | 60 | self.assertEqual("", widget.distribution_name) | ||
510 | 61 | |||
511 | 62 | def test_distroseries(self): | ||
512 | 63 | distroseries = self.factory.makeDistroSeries() | ||
513 | 64 | potemplate = self.factory.makePOTemplate(distroseries=distroseries) | ||
514 | 65 | widget = self.makeWidget(potemplate) | ||
515 | 66 | self.assertEqual(distroseries.distribution, widget.getDistribution()) | ||
516 | 67 | self.assertEqual( | ||
517 | 68 | distroseries.distribution.name, widget.distribution_name) | ||
518 | 69 | |||
519 | 70 | |||
520 | 71 | class TestPOTemplateAdminSourcePackageNameWidget( | ||
521 | 72 | WithScenarios, TestCaseWithFactory): | ||
522 | 73 | |||
523 | 74 | layer = DatabaseFunctionalLayer | ||
524 | 75 | |||
525 | 76 | scenarios = [ | ||
526 | 77 | ("spn_picker", { | ||
527 | 78 | "features": {}, | ||
528 | 79 | "interface": IPOTemplate, | ||
529 | 80 | }), | ||
530 | 81 | ("dsp_picker", { | ||
531 | 82 | "features": {u"disclosure.dsp_picker.enabled": u"on"}, | ||
532 | 83 | "interface": IPOTemplateEditForm, | ||
533 | 84 | }), | ||
534 | 85 | ] | ||
535 | 86 | |||
536 | 87 | def setUp(self): | ||
537 | 88 | super(TestPOTemplateAdminSourcePackageNameWidget, self).setUp() | ||
538 | 89 | if self.features: | ||
539 | 90 | self.useFixture(FeatureFixture(self.features)) | ||
540 | 91 | |||
541 | 92 | def makeWidget(self, potemplate, form=None): | ||
542 | 93 | field = self.interface["sourcepackagename"] | ||
543 | 94 | bound_field = field.bind(potemplate) | ||
544 | 95 | request = LaunchpadTestRequest(form=form) | ||
545 | 96 | return POTemplateAdminSourcePackageNameWidget( | ||
546 | 97 | bound_field, bound_field.vocabulary, request) | ||
547 | 98 | |||
548 | 99 | def test_distroseries_id(self): | ||
549 | 100 | potemplate = self.factory.makePOTemplate() | ||
550 | 101 | distroseries = self.factory.makeDistroSeries() | ||
551 | 102 | form = { | ||
552 | 103 | "field.distroseries": "%s/%s" % ( | ||
553 | 104 | distroseries.distribution.name, distroseries.name), | ||
554 | 105 | } | ||
555 | 106 | widget = self.makeWidget(potemplate, form=form) | ||
556 | 107 | self.assertEqual("field.distroseries", widget.distroseries_id) | ||
557 | 108 | |||
558 | 109 | def test_getDistribution(self): | ||
559 | 110 | potemplate = self.factory.makePOTemplate() | ||
560 | 111 | distroseries = self.factory.makeDistroSeries() | ||
561 | 112 | form = { | ||
562 | 113 | "field.distroseries": "%s/%s" % ( | ||
563 | 114 | distroseries.distribution.name, distroseries.name), | ||
564 | 115 | } | ||
565 | 116 | widget = self.makeWidget(potemplate, form=form) | ||
566 | 117 | self.assertEqual(distroseries.distribution, widget.getDistribution()) | ||
567 | 118 | |||
568 | 119 | def test_getDistribution_missing_field(self): | ||
569 | 120 | distroseries = self.factory.makeDistroSeries() | ||
570 | 121 | potemplate = self.factory.makePOTemplate(distroseries=distroseries) | ||
571 | 122 | widget = self.makeWidget(potemplate, form={}) | ||
572 | 123 | self.assertEqual(distroseries.distribution, widget.getDistribution()) | ||
573 | 124 | |||
574 | 125 | def test_getDistribution_non_existent_distroseries(self): | ||
575 | 126 | potemplate = self.factory.makePOTemplate() | ||
576 | 127 | form = {"field.distroseries": "not-a-distribution/not-a-series"} | ||
577 | 128 | self.assertRaises( | ||
578 | 129 | UnexpectedFormData, | ||
579 | 130 | lambda: ( | ||
580 | 131 | self.makeWidget(potemplate, form=form).getDistribution().name)) | ||
581 | 132 | |||
582 | 133 | |||
583 | 134 | load_tests = load_tests_apply_scenarios |