Merge lp:~abentley/launchpad/run-via-celery into lp:launchpad
- run-via-celery
- Merge into devel
Status: | Merged |
---|---|
Merged at revision: | 15046 |
Proposed branch: | lp:~abentley/launchpad/run-via-celery |
Merge into: | lp:launchpad |
Prerequisite: | lp:~abentley/launchpad/lp-lazr.jobrunner |
Diff against target: |
4851 lines (+701/-2692) 58 files modified
Makefile (+1/-1) buildout.cfg (+1/-0) lib/lp/app/templates/base-layout.pt (+0/-5) lib/lp/bugs/model/bug.py (+0/-123) lib/lp/bugs/model/bugtask.py (+0/-2) lib/lp/bugs/scripts/bugimport.py (+0/-22) lib/lp/bugs/tests/test_bug_mirror_access_triggers.py (+0/-12) lib/lp/code/interfaces/branch.py (+4/-1) lib/lp/code/model/branch.py (+12/-2) lib/lp/code/model/branchjob.py (+67/-61) lib/lp/code/model/tests/test_branch.py (+3/-2) lib/lp/code/model/tests/test_branchpuller.py (+2/-2) lib/lp/code/model/tests/test_branchtarget.py (+9/-5) lib/lp/code/tests/test_directbranchcommit.py (+3/-2) lib/lp/code/xmlrpc/tests/test_codehosting.py (+2/-1) lib/lp/codehosting/bzrutils.py (+16/-3) lib/lp/registry/browser/pillar.py (+5/-101) lib/lp/registry/browser/product.py (+0/-6) lib/lp/registry/browser/tests/test_pillar_sharing.py (+0/-194) lib/lp/registry/enums.py (+0/-18) lib/lp/registry/interfaces/accesspolicy.py (+0/-24) lib/lp/registry/interfaces/productjob.py (+0/-70) lib/lp/registry/interfaces/sharingservice.py (+0/-15) lib/lp/registry/javascript/sharing/pillarsharingview.js (+0/-6) lib/lp/registry/javascript/sharing/shareepicker.js (+0/-96) lib/lp/registry/javascript/sharing/shareetable.js (+6/-37) lib/lp/registry/javascript/sharing/tests/test_shareelisting_navigator.html (+0/-65) lib/lp/registry/javascript/sharing/tests/test_shareepicker.js (+0/-9) lib/lp/registry/javascript/sharing/tests/test_shareetable.html (+0/-11) lib/lp/registry/javascript/sharing/tests/test_shareetable.js (+0/-111) lib/lp/registry/model/accesspolicy.py (+0/-29) lib/lp/registry/model/person.py (+0/-8) lib/lp/registry/model/productjob.py (+0/-154) lib/lp/registry/services/sharingservice.py (+0/-69) lib/lp/registry/services/tests/test_sharingservice.py (+2/-150) lib/lp/registry/subscribers.py (+137/-278) lib/lp/registry/templates/pillar-sharing-details.pt (+0/-17) lib/lp/registry/templates/pillar-sharing.pt (+0/-15) lib/lp/registry/tests/test_accesspolicy.py (+0/-55) lib/lp/registry/tests/test_pillar.py (+5/-14) lib/lp/registry/tests/test_productjob.py (+0/-189) lib/lp/registry/tests/test_subscribers.py (+260/-524) lib/lp/scripts/garbo.py (+0/-42) lib/lp/scripts/tests/test_garbo.py (+0/-27) lib/lp/services/features/flags.py (+0/-13) lib/lp/services/job/celeryconfig.py (+4/-0) lib/lp/services/job/celeryjob.py (+30/-0) lib/lp/services/job/interfaces/job.py (+0/-8) lib/lp/services/job/model/job.py (+61/-2) lib/lp/services/job/runner.py (+5/-2) lib/lp/services/job/tests/celeryconfig.py (+7/-0) lib/lp/services/job/tests/test_celeryjob.py (+42/-0) lib/lp/services/job/tests/test_job.py (+1/-1) lib/lp/services/job/tests/test_runner.py (+7/-20) lib/lp/testing/factory.py (+3/-3) lib/lp/testing/yuixhr.py (+0/-40) lib/lp/translations/model/translationsharingjob.py (+6/-17) versions.cfg (+0/-8) |
To merge this branch: | bzr merge lp:~abentley/launchpad/run-via-celery |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Abel Deuring (community) | Approve | ||
Review via email: mp+99099@code.launchpad.net |
Commit message
Support running jobs via Celery
Description of the change
= Summary =
Support running Jobs via Celery
== Pre-implementation notes ==
Discussed with Deryck, adeuring, lifeless
LOC can be added because this is part of an arc that will remove most job-running code from Launchpad.
== Implementation details ==
Provide a Celery Task, CeleryRunJob, that will run a Job.
As CeleryRunJob's job source, provide UniversalJobSource, which uses Job.id to retrieve the corresponding RunnableJob. It also sets up an environment similar to the script environment for Launchpad code.
Move most code out of BranchScanJob.
To support UniversalJobSource, rework RegisteredSubclass as EnumeratedSubclass and make it the metaclass for BranchJobDerived.
CeleryRunJob overrides getJobRunner to return a BaseJobRunner, so that it knows how to run Launchpad jobs.
Switch to lazr.jobrunner's LeaseHeld exception, to ensure lease handling works correctly.
Update lp.codehosting.
Provide two different celeryconfigs:
- lib/lp/
- lib/lp/
Update versions.cfg to list the current versions of Celery and its dependencies at this time.
== Tests ==
bin/test job -tCelery
== Demo and Q/A ==
None (this functionality is optional.)
= Launchpad lint =
Checking for conflicts and issues in changed files.
Linting changed files:
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
buildout.cfg
versions.cfg
setup.py
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
Aaron Bentley (abentley) wrote : | # |
I've used Transaction.
I've inverted skip_celery to celery_scan, to make the documentation simpler, and documented it.
Abel Deuring (adeuring) wrote : | # |
Looks good. Just one nitpick:
=== modified file 'lib/lp/
--- lib/lp/
+++ lib/lp/
@@ -1078,7 +1078,7 @@
"""Create an IBranchUpgradeJob to upgrade this branch."""
def branchChanged(
- branch_format, repository_format):
+ branch_format, repository_format, skip_celery=False):
s/skip_
Aaron Bentley (abentley) wrote : | # |
This branch can be QAed by pushing branches up to be scanned. This should behave normally. The celery side does not need to be tested, since it's not mandatory.
Preview Diff
1 | === modified file 'Makefile' |
2 | --- Makefile 2012-03-14 12:01:42 +0000 |
3 | +++ Makefile 2012-03-29 14:36:38 +0000 |
4 | @@ -305,7 +305,7 @@ |
5 | $(PY) scripts/stop-loggerhead.py |
6 | |
7 | run_codehosting: build inplace stop |
8 | - bin/run -r librarian,sftp,forker,codebrowse -i $(LPCONFIG) |
9 | + bin/run -r librarian,sftp,forker,codebrowse,rabbitmq -i $(LPCONFIG) |
10 | |
11 | start_librarian: compile |
12 | bin/start_librarian |
13 | |
14 | === modified file 'buildout.cfg' |
15 | --- buildout.cfg 2012-03-16 11:09:03 +0000 |
16 | +++ buildout.cfg 2012-03-29 14:36:38 +0000 |
17 | @@ -65,6 +65,7 @@ |
18 | [scripts] |
19 | recipe = z3c.recipe.scripts |
20 | eggs = lp |
21 | + celery |
22 | funkload |
23 | zc.zservertracelog |
24 | pyinotify |
25 | |
26 | === modified file 'lib/lp/app/templates/base-layout.pt' |
27 | --- lib/lp/app/templates/base-layout.pt 2012-03-29 14:36:36 +0000 |
28 | +++ lib/lp/app/templates/base-layout.pt 2012-03-23 20:56:11 +0000 |
29 | @@ -79,14 +79,9 @@ |
30 | <tal:login replace="structure context/@@login_status" /> |
31 | </div><!--id="locationbar"--> |
32 | |
33 | -<<<<<<< TREE |
34 | <div id="watermark" class="watermark-apps-portlet" |
35 | tal:condition="view/macro:has-watermark"> |
36 | <div> |
37 | -======= |
38 | - <div id="watermark" class="watermark-apps-portlet"> |
39 | - <div> |
40 | ->>>>>>> MERGE-SOURCE |
41 | <span tal:replace="structure view/watermark:logo"></span> |
42 | </div> |
43 | <div class="wide"> |
44 | |
45 | === modified file 'lib/lp/bugs/model/bug.py' |
46 | --- lib/lp/bugs/model/bug.py 2012-03-29 14:36:36 +0000 |
47 | +++ lib/lp/bugs/model/bug.py 2012-03-29 06:02:46 +0000 |
48 | @@ -394,7 +394,6 @@ |
49 | heat_last_updated = UtcDateTimeCol(default=None) |
50 | latest_patch_uploaded = UtcDateTimeCol(default=None) |
51 | |
52 | -<<<<<<< TREE |
53 | @property |
54 | def private(self): |
55 | return self.information_type in PRIVATE_INFORMATION_TYPES |
56 | @@ -403,22 +402,6 @@ |
57 | def security_related(self): |
58 | return self.information_type in SECURITY_INFORMATION_TYPES |
59 | |
60 | -======= |
61 | - @property |
62 | - def private(self): |
63 | - if self.information_type: |
64 | - return self.information_type in PRIVATE_INFORMATION_TYPES |
65 | - else: |
66 | - return self._private |
67 | - |
68 | - @property |
69 | - def security_related(self): |
70 | - if self.information_type: |
71 | - return self.information_type in SECURITY_INFORMATION_TYPES |
72 | - else: |
73 | - return self._security_related |
74 | - |
75 | ->>>>>>> MERGE-SOURCE |
76 | @cachedproperty |
77 | def _subscriber_cache(self): |
78 | """Caches known subscribers.""" |
79 | @@ -1727,106 +1710,6 @@ |
80 | |
81 | return bugtask |
82 | |
83 | -<<<<<<< TREE |
84 | -======= |
85 | - def _setInformationType(self): |
86 | - if self._private and self._security_related: |
87 | - self.information_type = InformationType.EMBARGOEDSECURITY |
88 | - elif self._private: |
89 | - self.information_type = InformationType.USERDATA |
90 | - elif self._security_related: |
91 | - self.information_type = InformationType.UNEMBARGOEDSECURITY |
92 | - else: |
93 | - self.information_type = InformationType.PUBLIC |
94 | - |
95 | - def setPrivacyAndSecurityRelated(self, private, security_related, who): |
96 | - """ See `IBug`.""" |
97 | - private_changed = False |
98 | - security_related_changed = False |
99 | - bug_before_modification = Snapshot(self, providing=providedBy(self)) |
100 | - |
101 | - f_flag_str = 'disclosure.enhanced_private_bug_subscriptions.enabled' |
102 | - f_flag = bool(getFeatureFlag(f_flag_str)) |
103 | - if f_flag: |
104 | - # Before we update the privacy or security_related status, we |
105 | - # need to reconcile the subscribers to avoid leaking private |
106 | - # information. |
107 | - if (self.private != private |
108 | - or self.security_related != security_related): |
109 | - self.reconcileSubscribers(private, security_related, who) |
110 | - |
111 | - if self.private != private: |
112 | - # We do not allow multi-pillar private bugs except for those teams |
113 | - # who want to shoot themselves in the foot. |
114 | - if private: |
115 | - allow_multi_pillar_private = bool(getFeatureFlag( |
116 | - 'disclosure.allow_multipillar_private_bugs.enabled')) |
117 | - if (not allow_multi_pillar_private |
118 | - and len(self.affected_pillars) > 1): |
119 | - raise BugCannotBePrivate( |
120 | - "Multi-pillar bugs cannot be private.") |
121 | - private_changed = True |
122 | - self._private = private |
123 | - |
124 | - if private: |
125 | - self.who_made_private = who |
126 | - self.date_made_private = UTC_NOW |
127 | - else: |
128 | - self.who_made_private = None |
129 | - self.date_made_private = None |
130 | - |
131 | - # XXX: This should be a bulk update. RBC 20100827 |
132 | - # bug=https://bugs.launchpad.net/storm/+bug/625071 |
133 | - for attachment in self.attachments_unpopulated: |
134 | - attachment.libraryfile.restricted = private |
135 | - |
136 | - if self.security_related != security_related: |
137 | - security_related_changed = True |
138 | - self._security_related = security_related |
139 | - |
140 | - if private_changed or security_related_changed: |
141 | - # Correct the heat for the bug immediately, so that we don't have |
142 | - # to wait for the next calculation job for the adjusted heat. |
143 | - self.updateHeat() |
144 | - |
145 | - self._setInformationType() |
146 | - |
147 | - if private_changed or security_related_changed: |
148 | - changed_fields = [] |
149 | - |
150 | - if private_changed: |
151 | - changed_fields.append('private') |
152 | - if not f_flag and private: |
153 | - # If we didn't call reconcileSubscribers, we may have |
154 | - # bug supervisors who should be on this bug, but aren't. |
155 | - supervisors = set() |
156 | - for bugtask in self.bugtasks: |
157 | - supervisors.add(bugtask.pillar.bug_supervisor) |
158 | - if None in supervisors: |
159 | - supervisors.remove(None) |
160 | - for s in supervisors: |
161 | - subscriptions = get_structural_subscriptions_for_bug( |
162 | - self, s) |
163 | - if subscriptions != []: |
164 | - self.subscribe(s, who) |
165 | - |
166 | - if security_related_changed: |
167 | - changed_fields.append('security_related') |
168 | - if not f_flag and security_related: |
169 | - # The bug turned out to be security-related, subscribe the |
170 | - # security contact. We do it here only if the feature flag |
171 | - # is not set, otherwise it's done in |
172 | - # reconcileSubscribers(). |
173 | - for pillar in self.affected_pillars: |
174 | - if pillar.security_contact is not None: |
175 | - self.subscribe(pillar.security_contact, who) |
176 | - |
177 | - notify(ObjectModifiedEvent( |
178 | - self, bug_before_modification, changed_fields, user=who)) |
179 | - |
180 | - return private_changed, security_related_changed |
181 | - |
182 | ->>>>>>> MERGE-SOURCE |
183 | def setPrivate(self, private, who): |
184 | """See `IBug`. |
185 | |
186 | @@ -2997,15 +2880,9 @@ |
187 | params.information_type in SECURITY_INFORMATION_TYPES) |
188 | bug = Bug( |
189 | title=params.title, description=params.description, |
190 | -<<<<<<< TREE |
191 | owner=params.owner, datecreated=params.datecreated, |
192 | information_type=params.information_type, |
193 | _private=private, _security_related=security_related, |
194 | -======= |
195 | - _private=params.private, owner=params.owner, |
196 | - datecreated=params.datecreated, |
197 | - _security_related=params.security_related, |
198 | ->>>>>>> MERGE-SOURCE |
199 | **extra_params) |
200 | |
201 | if params.subscribe_owner: |
202 | |
203 | === modified file 'lib/lp/bugs/model/bugtask.py' |
204 | --- lib/lp/bugs/model/bugtask.py 2012-03-29 14:36:36 +0000 |
205 | +++ lib/lp/bugs/model/bugtask.py 2012-03-29 00:48:21 +0000 |
206 | @@ -43,10 +43,8 @@ |
207 | And, |
208 | Join, |
209 | Or, |
210 | - Select, |
211 | SQL, |
212 | Sum, |
213 | - Union, |
214 | ) |
215 | from storm.store import ( |
216 | EmptyResultSet, |
217 | |
218 | === modified file 'lib/lp/bugs/scripts/bugimport.py' |
219 | --- lib/lp/bugs/scripts/bugimport.py 2012-03-29 14:36:36 +0000 |
220 | +++ lib/lp/bugs/scripts/bugimport.py 2012-03-23 07:17:15 +0000 |
221 | @@ -293,17 +293,11 @@ |
222 | |
223 | private = get_value(bugnode, 'private') == 'True' |
224 | security_related = get_value(bugnode, 'security_related') == 'True' |
225 | -<<<<<<< TREE |
226 | # If the product has private_bugs, we force private to True. |
227 | if self.product.private_bugs: |
228 | private = True |
229 | information_type = convert_to_information_type( |
230 | private, security_related) |
231 | -======= |
232 | - # If the product has private_bugs, we force private to True. |
233 | - if self.product.private_bugs: |
234 | - private = True |
235 | ->>>>>>> MERGE-SOURCE |
236 | |
237 | if owner is None: |
238 | owner = self.bug_importer |
239 | @@ -311,17 +305,8 @@ |
240 | msg = self.createMessage(commentnode, defaulttitle=title) |
241 | |
242 | bug = self.product.createBug(CreateBugParams( |
243 | -<<<<<<< TREE |
244 | msg=msg, datecreated=datecreated, title=title, |
245 | information_type=information_type, owner=owner)) |
246 | -======= |
247 | - msg=msg, |
248 | - datecreated=datecreated, |
249 | - title=title, |
250 | - private=private or security_related, |
251 | - security_related=security_related, |
252 | - owner=owner)) |
253 | ->>>>>>> MERGE-SOURCE |
254 | bugtask = bug.bugtasks[0] |
255 | self.logger.info('Creating Launchpad bug #%d', bug.id) |
256 | |
257 | @@ -336,18 +321,11 @@ |
258 | bug.linkMessage(msg) |
259 | self.createAttachments(bug, msg, commentnode) |
260 | |
261 | -<<<<<<< TREE |
262 | # Security bugs must be created private, so set it correctly. |
263 | if not self.product.private_bugs: |
264 | information_type = convert_to_information_type( |
265 | private, security_related) |
266 | bug.transitionToInformationType(information_type, owner) |
267 | -======= |
268 | - # Security bugs must be created private, so set it correctly. |
269 | - if not self.product.private_bugs: |
270 | - bug.setPrivacyAndSecurityRelated( |
271 | - private, security_related, owner) |
272 | ->>>>>>> MERGE-SOURCE |
273 | bug.name = get_value(bugnode, 'nickname') |
274 | description = get_value(bugnode, 'description') |
275 | if description: |
276 | |
277 | === modified file 'lib/lp/bugs/tests/test_bug_mirror_access_triggers.py' |
278 | --- lib/lp/bugs/tests/test_bug_mirror_access_triggers.py 2012-03-29 14:36:36 +0000 |
279 | +++ lib/lp/bugs/tests/test_bug_mirror_access_triggers.py 2012-03-26 00:12:50 +0000 |
280 | @@ -136,11 +136,7 @@ |
281 | bug = self.makeBugAndPolicies(private=True) |
282 | self.assertIsNot( |
283 | None, getUtility(IAccessArtifactSource).find([bug]).one()) |
284 | -<<<<<<< TREE |
285 | bug.setPrivate(False, bug.owner) |
286 | -======= |
287 | - removeSecurityProxy(bug).setPrivate(False, bug.owner) |
288 | ->>>>>>> MERGE-SOURCE |
289 | self.assertIs( |
290 | None, getUtility(IAccessArtifactSource).find([bug]).one()) |
291 | |
292 | @@ -148,11 +144,7 @@ |
293 | bug = self.makeBugAndPolicies(private=False) |
294 | self.assertIs( |
295 | None, getUtility(IAccessArtifactSource).find([bug]).one()) |
296 | -<<<<<<< TREE |
297 | bug.setPrivate(True, bug.owner) |
298 | -======= |
299 | - removeSecurityProxy(bug).setPrivate(True, bug.owner) |
300 | ->>>>>>> MERGE-SOURCE |
301 | self.assertIsNot( |
302 | None, getUtility(IAccessArtifactSource).find([bug]).one()) |
303 | self.assertEqual((1, 1), self.assertMirrored(bug)) |
304 | @@ -166,11 +158,7 @@ |
305 | self.assertContentEqual( |
306 | [InformationType.USERDATA], |
307 | self.getPolicyTypesForArtifact(artifact)) |
308 | -<<<<<<< TREE |
309 | bug.setSecurityRelated(True, bug.owner) |
310 | -======= |
311 | - removeSecurityProxy(bug).setSecurityRelated(True, bug.owner) |
312 | ->>>>>>> MERGE-SOURCE |
313 | self.assertEqual((1, 1), self.assertMirrored(bug)) |
314 | self.assertContentEqual( |
315 | [InformationType.EMBARGOEDSECURITY], |
316 | |
317 | === modified file 'lib/lp/code/interfaces/branch.py' |
318 | --- lib/lp/code/interfaces/branch.py 2012-02-20 02:07:55 +0000 |
319 | +++ lib/lp/code/interfaces/branch.py 2012-03-29 14:36:38 +0000 |
320 | @@ -1078,7 +1078,7 @@ |
321 | """Create an IBranchUpgradeJob to upgrade this branch.""" |
322 | |
323 | def branchChanged(stacked_on_url, last_revision_id, control_format, |
324 | - branch_format, repository_format): |
325 | + branch_format, repository_format, celery_scan=True): |
326 | """Record that a branch has been changed. |
327 | |
328 | This method records the stacked on branch tip revision id and format |
329 | @@ -1092,6 +1092,9 @@ |
330 | :param branch_format: The entry from BranchFormat for the branch. |
331 | :param repository_format: The entry from RepositoryFormat for the |
332 | branch. |
333 | + :param celery_scan: If True, request a branch scan via Celery. |
334 | + Otherwise, a BranchScanJob may be created, but not requested to |
335 | + run. Should only be False in certain tests. |
336 | """ |
337 | |
338 | @export_destructor_operation() |
339 | |
340 | === modified file 'lib/lp/code/model/branch.py' |
341 | --- lib/lp/code/model/branch.py 2012-03-21 12:34:12 +0000 |
342 | +++ lib/lp/code/model/branch.py 2012-03-29 14:36:38 +0000 |
343 | @@ -40,6 +40,7 @@ |
344 | Reference, |
345 | ) |
346 | from storm.store import Store |
347 | +import transaction |
348 | from zope.component import getUtility |
349 | from zope.event import notify |
350 | from zope.interface import implements |
351 | @@ -1032,7 +1033,8 @@ |
352 | return getUtility(IBranchLookup).getByUniqueName(location) |
353 | |
354 | def branchChanged(self, stacked_on_url, last_revision_id, |
355 | - control_format, branch_format, repository_format): |
356 | + control_format, branch_format, repository_format, |
357 | + celery_scan=True): |
358 | """See `IBranch`.""" |
359 | self.mirror_status_message = None |
360 | if stacked_on_url == '' or stacked_on_url is None: |
361 | @@ -1057,7 +1059,15 @@ |
362 | self.last_mirrored_id = last_revision_id |
363 | if self.last_scanned_id != last_revision_id: |
364 | from lp.code.model.branchjob import BranchScanJob |
365 | - BranchScanJob.create(self) |
366 | + job_id = BranchScanJob.create(self).job_id |
367 | + if celery_scan: |
368 | + # lp.services.job.celery is imported only where needed. |
369 | + from lp.services.job.celeryjob import CeleryRunJob |
370 | + current = transaction.get() |
371 | + def runHook(succeeded): |
372 | + if succeeded: |
373 | + CeleryRunJob.delay(job_id) |
374 | + current.addAfterCommitHook(runHook) |
375 | self.control_format = control_format |
376 | self.branch_format = branch_format |
377 | self.repository_format = repository_format |
378 | |
379 | === modified file 'lib/lp/code/model/branchjob.py' |
380 | --- lib/lp/code/model/branchjob.py 2012-02-21 19:13:45 +0000 |
381 | +++ lib/lp/code/model/branchjob.py 2012-03-29 14:36:38 +0000 |
382 | @@ -80,6 +80,7 @@ |
383 | from lp.code.model.branch import Branch |
384 | from lp.code.model.branchmergeproposal import BranchMergeProposal |
385 | from lp.code.model.revision import RevisionSet |
386 | +from lp.codehosting.bzrutils import server |
387 | from lp.codehosting.scanner.bzrsync import BzrSync |
388 | from lp.codehosting.vfs import ( |
389 | branch_id_to_path, |
390 | @@ -93,7 +94,10 @@ |
391 | from lp.services.database.lpstorm import IStore |
392 | from lp.services.database.sqlbase import SQLBase |
393 | from lp.services.job.interfaces.job import JobStatus |
394 | -from lp.services.job.model.job import Job |
395 | +from lp.services.job.model.job import ( |
396 | + EnumeratedSubclass, |
397 | + Job, |
398 | + ) |
399 | from lp.services.job.runner import BaseRunnableJob |
400 | from lp.services.mail.sendmail import format_address_for_person |
401 | from lp.services.webapp import ( |
402 | @@ -212,9 +216,14 @@ |
403 | SQLBase.destroySelf(self) |
404 | self.job.destroySelf() |
405 | |
406 | + def makeDerived(self): |
407 | + return BranchJobDerived.makeSubclass(self) |
408 | + |
409 | |
410 | class BranchJobDerived(BaseRunnableJob): |
411 | |
412 | + __metaclass__ = EnumeratedSubclass |
413 | + |
414 | delegates(IBranchJob) |
415 | |
416 | def __init__(self, branch_job): |
417 | @@ -287,29 +296,26 @@ |
418 | classProvides(IBranchScanJobSource) |
419 | class_job_type = BranchJobType.SCAN_BRANCH |
420 | memory_limit = 2 * (1024 ** 3) |
421 | - server = None |
422 | |
423 | @classmethod |
424 | def create(cls, branch): |
425 | """See `IBranchScanJobSource`.""" |
426 | - branch_job = BranchJob(branch, BranchJobType.SCAN_BRANCH, {}) |
427 | + branch_job = BranchJob(branch, cls.class_job_type, {}) |
428 | return cls(branch_job) |
429 | |
430 | def run(self): |
431 | """See `IBranchScanJob`.""" |
432 | from lp.services.scripts import log |
433 | - bzrsync = BzrSync(self.branch, log) |
434 | - bzrsync.syncBranchAndClose() |
435 | + with server(get_ro_server(), no_replace=True): |
436 | + bzrsync = BzrSync(self.branch, log) |
437 | + bzrsync.syncBranchAndClose() |
438 | |
439 | @classmethod |
440 | @contextlib.contextmanager |
441 | def contextManager(cls): |
442 | """See `IBranchScanJobSource`.""" |
443 | errorlog.globalErrorUtility.configure('branchscanner') |
444 | - cls.server = get_ro_server() |
445 | - cls.server.start_server() |
446 | yield |
447 | - cls.server.stop_server() |
448 | |
449 | |
450 | class BranchUpgradeJob(BranchJobDerived): |
451 | @@ -330,7 +336,7 @@ |
452 | """See `IBranchUpgradeJobSource`.""" |
453 | branch.checkUpgrade() |
454 | branch_job = BranchJob( |
455 | - branch, BranchJobType.UPGRADE_BRANCH, {}, requester=requester) |
456 | + branch, cls.class_job_type, {}, requester=requester) |
457 | return cls(branch_job) |
458 | |
459 | @staticmethod |
460 | @@ -338,63 +344,63 @@ |
461 | def contextManager(): |
462 | """See `IBranchUpgradeJobSource`.""" |
463 | errorlog.globalErrorUtility.configure('upgrade_branches') |
464 | - server = get_rw_server() |
465 | - server.start_server() |
466 | yield |
467 | - server.stop_server() |
468 | |
469 | def run(self, _check_transaction=False): |
470 | """See `IBranchUpgradeJob`.""" |
471 | # Set up the new branch structure |
472 | - upgrade_branch_path = tempfile.mkdtemp() |
473 | - try: |
474 | - upgrade_transport = get_transport(upgrade_branch_path) |
475 | - upgrade_transport.mkdir('.bzr') |
476 | - source_branch_transport = get_transport( |
477 | - self.branch.getInternalBzrUrl()) |
478 | - source_branch_transport.clone('.bzr').copy_tree_to_transport( |
479 | - upgrade_transport.clone('.bzr')) |
480 | - transaction.commit() |
481 | - upgrade_branch = BzrBranch.open_from_transport(upgrade_transport) |
482 | - |
483 | - # No transactions are open so the DB connection won't be killed. |
484 | - with TransactionFreeOperation(): |
485 | - # Perform the upgrade. |
486 | - upgrade(upgrade_branch.base) |
487 | - |
488 | - # Re-open the branch, since its format has changed. |
489 | - upgrade_branch = BzrBranch.open_from_transport( |
490 | - upgrade_transport) |
491 | - source_branch = BzrBranch.open_from_transport( |
492 | - source_branch_transport) |
493 | - |
494 | - source_branch.lock_write() |
495 | - upgrade_branch.pull(source_branch) |
496 | - upgrade_branch.fetch(source_branch) |
497 | - source_branch.unlock() |
498 | - |
499 | - # Move the branch in the old format to backup.bzr |
500 | + with server(get_rw_server(), no_replace=True): |
501 | + upgrade_branch_path = tempfile.mkdtemp() |
502 | try: |
503 | - source_branch_transport.delete_tree('backup.bzr') |
504 | - except NoSuchFile: |
505 | - pass |
506 | - source_branch_transport.rename('.bzr', 'backup.bzr') |
507 | - source_branch_transport.mkdir('.bzr') |
508 | - upgrade_transport.clone('.bzr').copy_tree_to_transport( |
509 | - source_branch_transport.clone('.bzr')) |
510 | - |
511 | - # Re-open the source branch again. |
512 | - source_branch = BzrBranch.open_from_transport( |
513 | - source_branch_transport) |
514 | - |
515 | - formats = get_branch_formats(source_branch) |
516 | - |
517 | - self.branch.branchChanged( |
518 | - self.branch.stacked_on, |
519 | - self.branch.last_scanned_id, |
520 | - *formats) |
521 | - finally: |
522 | - shutil.rmtree(upgrade_branch_path) |
523 | + upgrade_transport = get_transport(upgrade_branch_path) |
524 | + upgrade_transport.mkdir('.bzr') |
525 | + source_branch_transport = get_transport( |
526 | + self.branch.getInternalBzrUrl()) |
527 | + source_branch_transport.clone('.bzr').copy_tree_to_transport( |
528 | + upgrade_transport.clone('.bzr')) |
529 | + transaction.commit() |
530 | + upgrade_branch = BzrBranch.open_from_transport( |
531 | + upgrade_transport) |
532 | + |
533 | + # No transactions are open so the DB connection won't be |
534 | + # killed. |
535 | + with TransactionFreeOperation(): |
536 | + # Perform the upgrade. |
537 | + upgrade(upgrade_branch.base) |
538 | + |
539 | + # Re-open the branch, since its format has changed. |
540 | + upgrade_branch = BzrBranch.open_from_transport( |
541 | + upgrade_transport) |
542 | + source_branch = BzrBranch.open_from_transport( |
543 | + source_branch_transport) |
544 | + |
545 | + source_branch.lock_write() |
546 | + upgrade_branch.pull(source_branch) |
547 | + upgrade_branch.fetch(source_branch) |
548 | + source_branch.unlock() |
549 | + |
550 | + # Move the branch in the old format to backup.bzr |
551 | + try: |
552 | + source_branch_transport.delete_tree('backup.bzr') |
553 | + except NoSuchFile: |
554 | + pass |
555 | + source_branch_transport.rename('.bzr', 'backup.bzr') |
556 | + source_branch_transport.mkdir('.bzr') |
557 | + upgrade_transport.clone('.bzr').copy_tree_to_transport( |
558 | + source_branch_transport.clone('.bzr')) |
559 | + |
560 | + # Re-open the source branch again. |
561 | + source_branch = BzrBranch.open_from_transport( |
562 | + source_branch_transport) |
563 | + |
564 | + formats = get_branch_formats(source_branch) |
565 | + |
566 | + self.branch.branchChanged( |
567 | + self.branch.stacked_on, |
568 | + self.branch.last_scanned_id, |
569 | + *formats) |
570 | + finally: |
571 | + shutil.rmtree(upgrade_branch_path) |
572 | |
573 | |
574 | class RevisionMailJob(BranchJobDerived): |
575 | @@ -415,7 +421,7 @@ |
576 | 'body': body, |
577 | 'subject': subject, |
578 | } |
579 | - branch_job = BranchJob(branch, BranchJobType.REVISION_MAIL, metadata) |
580 | + branch_job = BranchJob(branch, cls.class_job_type, metadata) |
581 | return cls(branch_job) |
582 | |
583 | @property |
584 | |
585 | === modified file 'lib/lp/code/model/tests/test_branch.py' |
586 | --- lib/lp/code/model/tests/test_branch.py 2012-02-15 08:13:51 +0000 |
587 | +++ lib/lp/code/model/tests/test_branch.py 2012-03-29 14:36:38 +0000 |
588 | @@ -130,6 +130,7 @@ |
589 | from lp.testing.layers import ( |
590 | AppServerLayer, |
591 | DatabaseFunctionalLayer, |
592 | + LaunchpadFunctionalLayer, |
593 | LaunchpadZopelessLayer, |
594 | ) |
595 | from lp.translations.model.translationtemplatesbuildjob import ( |
596 | @@ -159,7 +160,7 @@ |
597 | class TestBranchChanged(TestCaseWithFactory): |
598 | """Tests for `IBranch.branchChanged`.""" |
599 | |
600 | - layer = DatabaseFunctionalLayer |
601 | + layer = LaunchpadFunctionalLayer |
602 | |
603 | def setUp(self): |
604 | TestCaseWithFactory.setUp(self) |
605 | @@ -2144,7 +2145,7 @@ |
606 | class TestPendingWrites(TestCaseWithFactory): |
607 | """Are there changes to this branch not reflected in the database?""" |
608 | |
609 | - layer = DatabaseFunctionalLayer |
610 | + layer = LaunchpadFunctionalLayer |
611 | |
612 | def test_new_branch_no_writes(self): |
613 | # New branches have no pending writes. |
614 | |
615 | === modified file 'lib/lp/code/model/tests/test_branchpuller.py' |
616 | --- lib/lp/code/model/tests/test_branchpuller.py 2012-01-06 11:08:30 +0000 |
617 | +++ lib/lp/code/model/tests/test_branchpuller.py 2012-03-29 14:36:38 +0000 |
618 | @@ -89,7 +89,7 @@ |
619 | transaction.commit() |
620 | branch.startMirroring() |
621 | removeSecurityProxy(branch).branchChanged( |
622 | - '', 'rev1', None, None, None) |
623 | + '', 'rev1', None, None, None, celery_scan=False) |
624 | self.assertEqual(None, branch.next_mirror_time) |
625 | |
626 | def test_mirrorFailureResetsMirrorRequest(self): |
627 | @@ -158,7 +158,7 @@ |
628 | transaction.commit() |
629 | branch.startMirroring() |
630 | removeSecurityProxy(branch).branchChanged( |
631 | - '', 'rev1', None, None, None) |
632 | + '', 'rev1', None, None, None, celery_scan=False) |
633 | self.assertInFuture(branch.next_mirror_time, self.increment) |
634 | self.assertEqual(0, branch.mirror_failures) |
635 | |
636 | |
637 | === modified file 'lib/lp/code/model/tests/test_branchtarget.py' |
638 | --- lib/lp/code/model/tests/test_branchtarget.py 2012-01-01 02:58:52 +0000 |
639 | +++ lib/lp/code/model/tests/test_branchtarget.py 2012-03-29 14:36:38 +0000 |
640 | @@ -129,7 +129,8 @@ |
641 | default_branch = self.factory.makePackageBranch( |
642 | sourcepackage=development_package) |
643 | removeSecurityProxy(default_branch).branchChanged( |
644 | - '', self.factory.getUniqueString(), None, None, None) |
645 | + '', self.factory.getUniqueString(), None, None, None, |
646 | + celery_scan=False) |
647 | registrant = development_package.distribution.owner |
648 | with person_logged_in(registrant): |
649 | development_package.setBranch( |
650 | @@ -397,7 +398,7 @@ |
651 | branch = self.factory.makeProductBranch(product=self.original) |
652 | self._setDevelopmentFocus(self.original, branch) |
653 | removeSecurityProxy(branch).branchChanged( |
654 | - '', 'rev1', None, None, None) |
655 | + '', 'rev1', None, None, None, celery_scan=False) |
656 | target = IBranchTarget(self.original) |
657 | self.assertEqual(branch, target.default_stacked_on_branch) |
658 | |
659 | @@ -537,7 +538,8 @@ |
660 | branch = self.factory.makeAnyBranch(branch_type=BranchType.MIRRORED) |
661 | branch.startMirroring() |
662 | removeSecurityProxy(branch).branchChanged( |
663 | - '', self.factory.getUniqueString(), None, None, None) |
664 | + '', self.factory.getUniqueString(), None, None, None, |
665 | + celery_scan=False) |
666 | removeSecurityProxy(branch).branch_type = BranchType.REMOTE |
667 | self.assertIs(None, check_default_stacked_on(branch)) |
668 | |
669 | @@ -553,14 +555,16 @@ |
670 | branch = self.factory.makeAnyBranch(private=True) |
671 | naked_branch = removeSecurityProxy(branch) |
672 | naked_branch.branchChanged( |
673 | - '', self.factory.getUniqueString(), None, None, None) |
674 | + '', self.factory.getUniqueString(), None, None, None, |
675 | + celery_scan=False) |
676 | self.assertIs(None, check_default_stacked_on(branch)) |
677 | |
678 | def test_been_mirrored(self): |
679 | # `check_default_stacked_on` returns the branch if it has revisions. |
680 | branch = self.factory.makeAnyBranch() |
681 | removeSecurityProxy(branch).branchChanged( |
682 | - '', self.factory.getUniqueString(), None, None, None) |
683 | + '', self.factory.getUniqueString(), None, None, None, |
684 | + celery_scan=False) |
685 | self.assertEqual(branch, check_default_stacked_on(branch)) |
686 | |
687 | |
688 | |
689 | === modified file 'lib/lp/code/tests/test_directbranchcommit.py' |
690 | --- lib/lp/code/tests/test_directbranchcommit.py 2012-01-01 02:58:52 +0000 |
691 | +++ lib/lp/code/tests/test_directbranchcommit.py 2012-03-29 14:36:38 +0000 |
692 | @@ -1,4 +1,4 @@ |
693 | -# Copyright 2009-2010 Canonical Ltd. This software is licensed under the |
694 | +# Copyright 2009-2012 Canonical Ltd. This software is licensed under the |
695 | # GNU Affero General Public License version 3 (see the file LICENSE). |
696 | |
697 | """Tests for `DirectBranchCommit`.""" |
698 | @@ -20,6 +20,7 @@ |
699 | from lp.testing.fakemethod import FakeMethod |
700 | from lp.testing.layers import ( |
701 | DatabaseFunctionalLayer, |
702 | + LaunchpadZopelessLayer, |
703 | ZopelessDatabaseLayer, |
704 | ) |
705 | |
706 | @@ -63,7 +64,7 @@ |
707 | class TestDirectBranchCommit(DirectBranchCommitTestCase, TestCaseWithFactory): |
708 | """Test `DirectBranchCommit`.""" |
709 | |
710 | - layer = ZopelessDatabaseLayer |
711 | + layer = LaunchpadZopelessLayer |
712 | |
713 | def test_defaults_to_branch_owner(self): |
714 | # If no committer is given, DirectBranchCommits defaults to |
715 | |
716 | === modified file 'lib/lp/code/xmlrpc/tests/test_codehosting.py' |
717 | --- lib/lp/code/xmlrpc/tests/test_codehosting.py 2012-02-21 22:46:28 +0000 |
718 | +++ lib/lp/code/xmlrpc/tests/test_codehosting.py 2012-03-29 14:36:38 +0000 |
719 | @@ -56,6 +56,7 @@ |
720 | from lp.testing.layers import ( |
721 | DatabaseFunctionalLayer, |
722 | FunctionalLayer, |
723 | + LaunchpadFunctionalLayer, |
724 | ) |
725 | from lp.xmlrpc import faults |
726 | |
727 | @@ -1290,7 +1291,7 @@ |
728 | ]) |
729 | scenarios = [ |
730 | ('db', {'frontend': LaunchpadDatabaseFrontend, |
731 | - 'layer': DatabaseFunctionalLayer}), |
732 | + 'layer': LaunchpadFunctionalLayer}), |
733 | ('inmemory', {'frontend': InMemoryFrontend, |
734 | 'layer': FunctionalLayer}), |
735 | ] |
736 | |
737 | === modified file 'lib/lp/codehosting/bzrutils.py' |
738 | --- lib/lp/codehosting/bzrutils.py 2012-02-24 16:51:25 +0000 |
739 | +++ lib/lp/codehosting/bzrutils.py 2012-03-29 14:36:38 +0000 |
740 | @@ -19,6 +19,7 @@ |
741 | 'identical_formats', |
742 | 'install_oops_handler', |
743 | 'is_branch_stackable', |
744 | + 'server', |
745 | 'read_locked', |
746 | 'remove_exception_logging_hook', |
747 | ] |
748 | @@ -33,6 +34,7 @@ |
749 | ) |
750 | from bzrlib.errors import ( |
751 | NotStacked, |
752 | + UnsupportedProtocol, |
753 | UnstackableBranchFormat, |
754 | UnstackableRepositoryFormat, |
755 | ) |
756 | @@ -42,6 +44,7 @@ |
757 | RemoteRepository, |
758 | ) |
759 | from bzrlib.transport import ( |
760 | + get_transport, |
761 | register_transport, |
762 | unregister_transport, |
763 | ) |
764 | @@ -332,9 +335,19 @@ |
765 | |
766 | |
767 | @contextmanager |
768 | -def server(server): |
769 | - server.start_server() |
770 | +def server(server, no_replace=False): |
771 | + run_server = True |
772 | + if no_replace: |
773 | + try: |
774 | + get_transport(server.get_url()) |
775 | + except UnsupportedProtocol: |
776 | + pass |
777 | + else: |
778 | + run_server = False |
779 | + if run_server: |
780 | + server.start_server() |
781 | try: |
782 | yield server |
783 | finally: |
784 | - server.stop_server() |
785 | + if run_server: |
786 | + server.stop_server() |
787 | |
788 | === modified file 'lib/lp/registry/browser/pillar.py' |
789 | --- lib/lp/registry/browser/pillar.py 2012-03-29 14:36:36 +0000 |
790 | +++ lib/lp/registry/browser/pillar.py 2012-03-28 01:51:45 +0000 |
791 | @@ -20,13 +20,6 @@ |
792 | from lazr.restful import ResourceJSONEncoder |
793 | from lazr.restful.interfaces import IJSONRequestCache |
794 | import simplejson |
795 | -<<<<<<< TREE |
796 | -======= |
797 | - |
798 | -from lazr.restful import ResourceJSONEncoder |
799 | -from lazr.restful.interfaces import IJSONRequestCache |
800 | - |
801 | ->>>>>>> MERGE-SOURCE |
802 | from zope.component import getUtility |
803 | from zope.interface import ( |
804 | implements, |
805 | @@ -48,19 +41,12 @@ |
806 | from lp.bugs.browser.structuralsubscription import ( |
807 | StructuralSubscriptionMenuMixin, |
808 | ) |
809 | -<<<<<<< TREE |
810 | from lp.bugs.interfaces.bug import IBug |
811 | from lp.code.interfaces.branch import IBranch |
812 | from lp.registry.interfaces.accesspolicy import ( |
813 | IAccessPolicyGrantFlatSource, |
814 | IAccessPolicySource, |
815 | ) |
816 | -======= |
817 | -from lp.registry.interfaces.accesspolicy import ( |
818 | - IAccessPolicyGrantFlatSource, |
819 | - IAccessPolicySource, |
820 | - ) |
821 | ->>>>>>> MERGE-SOURCE |
822 | from lp.registry.interfaces.distributionsourcepackage import ( |
823 | IDistributionSourcePackage, |
824 | ) |
825 | @@ -68,30 +54,15 @@ |
826 | from lp.registry.interfaces.person import IPersonSet |
827 | from lp.registry.interfaces.pillar import IPillar |
828 | from lp.registry.interfaces.projectgroup import IProjectGroup |
829 | -<<<<<<< TREE |
830 | from lp.registry.model.pillar import PillarPerson |
831 | from lp.services.config import config |
832 | from lp.services.features import getFeatureFlag |
833 | -======= |
834 | -from lp.registry.interfaces.person import IPersonSet |
835 | -from lp.registry.model.pillar import PillarPerson |
836 | -from lp.services.config import config |
837 | ->>>>>>> MERGE-SOURCE |
838 | from lp.services.propertycache import cachedproperty |
839 | -<<<<<<< TREE |
840 | -from lp.services.webapp.authorization import check_permission |
841 | -from lp.services.webapp.batching import ( |
842 | - BatchNavigator, |
843 | - StormRangeFactory, |
844 | - ) |
845 | -======= |
846 | -from lp.services.features import getFeatureFlag |
847 | -from lp.services.webapp.authorization import check_permission |
848 | -from lp.services.webapp.batching import ( |
849 | - BatchNavigator, |
850 | - StormRangeFactory, |
851 | - ) |
852 | ->>>>>>> MERGE-SOURCE |
853 | +from lp.services.webapp.authorization import check_permission |
854 | +from lp.services.webapp.batching import ( |
855 | + BatchNavigator, |
856 | + StormRangeFactory, |
857 | + ) |
858 | from lp.services.webapp.menu import ( |
859 | ApplicationMenu, |
860 | enabled_with_permission, |
861 | @@ -326,7 +297,6 @@ |
862 | return simplejson.dumps( |
863 | self.sharing_picker_config, cls=ResourceJSONEncoder) |
864 | |
865 | -<<<<<<< TREE |
866 | def _getBatchNavigator(self, sharees): |
867 | """Return the batch navigator to be used to batch the sharees.""" |
868 | return BatchNavigator( |
869 | @@ -344,25 +314,6 @@ |
870 | |
871 | def unbatched_sharees(self): |
872 | """All the sharees for a pillar.""" |
873 | -======= |
874 | - def _getBatchNavigator(self, sharees): |
875 | - """Return the batch navigator to be used to batch the sharees.""" |
876 | - return BatchNavigator( |
877 | - sharees, self.request, |
878 | - hide_counts=True, |
879 | - size=config.launchpad.default_batch_size, |
880 | - range_factory=StormRangeFactory(sharees)) |
881 | - |
882 | - def shareeData(self): |
883 | - """Return an `ITableBatchNavigator` for sharees.""" |
884 | - if self._batch_navigator is None: |
885 | - unbatchedSharees = self.unbatchedShareeData() |
886 | - self._batch_navigator = self._getBatchNavigator(unbatchedSharees) |
887 | - return self._batch_navigator |
888 | - |
889 | - def unbatchedShareeData(self): |
890 | - """Return all the sharees for a pillar.""" |
891 | ->>>>>>> MERGE-SOURCE |
892 | return self._getSharingService().getPillarSharees(self.context) |
893 | |
894 | def initialize(self): |
895 | @@ -379,7 +330,6 @@ |
896 | and check_permission('launchpad.Edit', self.context)) |
897 | cache.objects['information_types'] = self.information_types |
898 | cache.objects['sharing_permissions'] = self.sharing_permissions |
899 | -<<<<<<< TREE |
900 | |
901 | view_names = set(reg.name for reg |
902 | in iter_view_registrations(self.__class__)) |
903 | @@ -455,49 +405,3 @@ |
904 | branch_name=branch.unique_name, |
905 | branch_id=branch.id)) |
906 | return branch_data |
907 | -======= |
908 | - |
909 | - view_names = set(reg.name for reg |
910 | - in iter_view_registrations(self.__class__)) |
911 | - if len(view_names) != 1: |
912 | - raise AssertionError("Ambiguous view name.") |
913 | - cache.objects['view_name'] = view_names.pop() |
914 | - batch_navigator = self.shareeData() |
915 | - cache.objects['sharee_data'] = ( |
916 | - self._getSharingService().getPillarShareeData( |
917 | - self.context, batch_navigator.batch)) |
918 | - |
919 | - def _getBatchInfo(batch): |
920 | - if batch is None: |
921 | - return None |
922 | - return {'memo': batch.range_memo, |
923 | - 'start': batch.startNumber() - 1} |
924 | - |
925 | - next_batch = batch_navigator.batch.nextBatch() |
926 | - cache.objects['next'] = _getBatchInfo(next_batch) |
927 | - prev_batch = batch_navigator.batch.prevBatch() |
928 | - cache.objects['prev'] = _getBatchInfo(prev_batch) |
929 | - cache.objects['total'] = batch_navigator.batch.total() |
930 | - cache.objects['forwards'] = batch_navigator.batch.range_forwards |
931 | - last_batch = batch_navigator.batch.lastBatch() |
932 | - cache.objects['last_start'] = last_batch.startNumber() - 1 |
933 | - cache.objects.update(_getBatchInfo(batch_navigator.batch)) |
934 | - |
935 | - |
936 | -class PillarPersonSharingView(LaunchpadView): |
937 | - |
938 | - page_title = "Person or team" |
939 | - label = "Information shared with person or team" |
940 | - |
941 | - def initialize(self): |
942 | - enabled_flag = 'disclosure.enhanced_sharing.enabled' |
943 | - enabled = bool(getFeatureFlag(enabled_flag)) |
944 | - if not enabled: |
945 | - raise Unauthorized("This feature is not yet available.") |
946 | - |
947 | - self.pillar = self.context.pillar |
948 | - self.person = self.context.person |
949 | - |
950 | - self.label = "Information shared with %s" % self.person.displayname |
951 | - self.page_title = "%s" % self.person.displayname |
952 | ->>>>>>> MERGE-SOURCE |
953 | |
954 | === modified file 'lib/lp/registry/browser/product.py' |
955 | --- lib/lp/registry/browser/product.py 2012-03-29 14:36:36 +0000 |
956 | +++ lib/lp/registry/browser/product.py 2012-03-22 23:21:24 +0000 |
957 | @@ -194,13 +194,7 @@ |
958 | from lp.services.webapp.authorization import check_permission |
959 | from lp.services.webapp.batching import BatchNavigator |
960 | from lp.services.webapp.breadcrumb import Breadcrumb |
961 | -<<<<<<< TREE |
962 | from lp.services.webapp.interfaces import UnsafeFormGetSubmissionError |
963 | -======= |
964 | -from lp.services.webapp.interfaces import ( |
965 | - UnsafeFormGetSubmissionError, |
966 | - ) |
967 | ->>>>>>> MERGE-SOURCE |
968 | from lp.services.webapp.menu import NavigationMenu |
969 | from lp.services.worlddata.helpers import browser_languages |
970 | from lp.services.worlddata.interfaces.country import ICountry |
971 | |
972 | === modified file 'lib/lp/registry/browser/tests/test_pillar_sharing.py' |
973 | --- lib/lp/registry/browser/tests/test_pillar_sharing.py 2012-03-29 14:36:36 +0000 |
974 | +++ lib/lp/registry/browser/tests/test_pillar_sharing.py 2012-03-28 01:51:45 +0000 |
975 | @@ -7,7 +7,6 @@ |
976 | |
977 | from BeautifulSoup import BeautifulSoup |
978 | from lazr.restful.interfaces import IJSONRequestCache |
979 | -<<<<<<< TREE |
980 | import simplejson |
981 | from testtools.matchers import ( |
982 | LessThan, |
983 | @@ -15,29 +14,15 @@ |
984 | Not, |
985 | Raises, |
986 | ) |
987 | -======= |
988 | -from testtools.matchers import ( |
989 | - LessThan, |
990 | - MatchesException, |
991 | - Not, |
992 | - Raises, |
993 | - ) |
994 | ->>>>>>> MERGE-SOURCE |
995 | from zope.component import getUtility |
996 | from zope.publisher.interfaces import NotFound |
997 | from zope.security.interfaces import Unauthorized |
998 | |
999 | from lp.app.interfaces.services import IService |
1000 | -<<<<<<< TREE |
1001 | from lp.registry.enums import InformationType |
1002 | from lp.registry.interfaces.accesspolicy import IAccessPolicyGrantFlatSource |
1003 | from lp.registry.model.pillar import PillarPerson |
1004 | from lp.services.config import config |
1005 | -======= |
1006 | -from lp.registry.enums import InformationType |
1007 | -from lp.registry.model.pillar import PillarPerson |
1008 | -from lp.services.config import config |
1009 | ->>>>>>> MERGE-SOURCE |
1010 | from lp.services.features.testing import FeatureFixture |
1011 | from lp.services.webapp.interfaces import StormRangeFactoryError |
1012 | from lp.services.webapp.publisher import canonical_url |
1013 | @@ -55,7 +40,6 @@ |
1014 | ) |
1015 | |
1016 | |
1017 | -<<<<<<< TREE |
1018 | DETAILS_ENABLED_FLAG = {'disclosure.enhanced_sharing_details.enabled': 'true'} |
1019 | ENABLED_FLAG = {'disclosure.enhanced_sharing.enabled': 'true'} |
1020 | WRITE_FLAG = {'disclosure.enhanced_sharing.writable': 'true'} |
1021 | @@ -149,106 +133,12 @@ |
1022 | owner=self.owner, driver=self.driver) |
1023 | login_person(self.driver) |
1024 | |
1025 | -======= |
1026 | -ENABLED_FLAG = {'disclosure.enhanced_sharing.enabled': 'true'} |
1027 | -WRITE_FLAG = {'disclosure.enhanced_sharing.writable': 'true'} |
1028 | - |
1029 | - |
1030 | -class PillarSharingDetailsMixin: |
1031 | - """Test the pillar sharing details view.""" |
1032 | - |
1033 | - layer = DatabaseFunctionalLayer |
1034 | - |
1035 | - def getPillarPerson(self, person=None, with_sharing=True): |
1036 | - if person is None: |
1037 | - person = self.factory.makePerson() |
1038 | - if with_sharing: |
1039 | - if self.pillar_type == 'product': |
1040 | - bug = self.factory.makeBug(product=self.pillar, private=True) |
1041 | - elif self.pillar_type == 'distribution': |
1042 | - bug = self.factory.makeBug( |
1043 | - distribution=self.pillar, private=True) |
1044 | - artifact = self.factory.makeAccessArtifact(concrete=bug) |
1045 | - policy = self.factory.makeAccessPolicy(pillar=self.pillar) |
1046 | - self.factory.makeAccessPolicyArtifact( |
1047 | - artifact=artifact, policy=policy) |
1048 | - self.factory.makeAccessArtifactGrant( |
1049 | - artifact=artifact, grantee=person, grantor=self.pillar.owner) |
1050 | - |
1051 | - return PillarPerson(self.pillar, person) |
1052 | - |
1053 | - def test_view_traverses_plus_sharingdetails(self): |
1054 | - # The traversed url in the app is pillar/+sharingdetails/person |
1055 | - with FeatureFixture(ENABLED_FLAG): |
1056 | - # We have to do some fun url hacking to force the traversal a user |
1057 | - # encounters. |
1058 | - pillarperson = self.getPillarPerson() |
1059 | - expected = pillarperson.person.displayname |
1060 | - url = 'http://launchpad.dev/%s/+sharingdetails/%s' % ( |
1061 | - pillarperson.pillar.name, pillarperson.person.name) |
1062 | - browser = self.getUserBrowser(user=self.driver, url=url) |
1063 | - self.assertEqual(expected, browser.title) |
1064 | - |
1065 | - def test_not_found_without_sharing(self): |
1066 | - # If there is no sharing between pillar and person, NotFound is the |
1067 | - # result. |
1068 | - with FeatureFixture(ENABLED_FLAG): |
1069 | - # We have to do some fun url hacking to force the traversal a user |
1070 | - # encounters. |
1071 | - pillarperson = self.getPillarPerson(with_sharing=False) |
1072 | - url = 'http://launchpad.dev/%s/+sharingdetails/%s' % ( |
1073 | - pillarperson.pillar.name, pillarperson.person.name) |
1074 | - browser = self.getUserBrowser(user=self.driver) |
1075 | - self.assertRaises(NotFound, browser.open, url) |
1076 | - |
1077 | - def test_init_without_feature_flag(self): |
1078 | - # We need a feature flag to enable the view. |
1079 | - pillarperson = self.getPillarPerson() |
1080 | - self.assertRaises( |
1081 | - Unauthorized, create_initialized_view, pillarperson, '+index') |
1082 | - |
1083 | - def test_init_with_feature_flag(self): |
1084 | - # The view works with a feature flag. |
1085 | - with FeatureFixture(ENABLED_FLAG): |
1086 | - pillarperson = self.getPillarPerson() |
1087 | - view = create_initialized_view(pillarperson, '+index') |
1088 | - self.assertEqual(pillarperson.person.displayname, view.page_title) |
1089 | - |
1090 | - |
1091 | -class TestProductSharingDetailsView( |
1092 | - TestCaseWithFactory, PillarSharingDetailsMixin): |
1093 | - |
1094 | - pillar_type = 'product' |
1095 | - |
1096 | - def setUp(self): |
1097 | - super(TestProductSharingDetailsView, self).setUp() |
1098 | - self.driver = self.factory.makePerson() |
1099 | - self.owner = self.factory.makePerson() |
1100 | - self.pillar = self.factory.makeProduct( |
1101 | - owner=self.owner, driver=self.driver) |
1102 | - login_person(self.driver) |
1103 | - |
1104 | - |
1105 | -class TestDistributionSharingDetailsView( |
1106 | - TestCaseWithFactory, PillarSharingDetailsMixin): |
1107 | - |
1108 | - pillar_type = 'distribution' |
1109 | - |
1110 | - def setUp(self): |
1111 | - super(TestDistributionSharingDetailsView, self).setUp() |
1112 | - self.driver = self.factory.makePerson() |
1113 | - self.owner = self.factory.makePerson() |
1114 | - self.pillar = self.factory.makeProduct( |
1115 | - owner=self.owner, driver=self.driver) |
1116 | - login_person(self.driver) |
1117 | ->>>>>>> MERGE-SOURCE |
1118 | |
1119 | class PillarSharingViewTestMixin: |
1120 | """Test the PillarSharingView.""" |
1121 | |
1122 | layer = DatabaseFunctionalLayer |
1123 | |
1124 | -<<<<<<< TREE |
1125 | def createSharees(self): |
1126 | login_person(self.owner) |
1127 | self.access_policy = self.factory.makeAccessPolicy( |
1128 | @@ -271,30 +161,6 @@ |
1129 | for x in range(10): |
1130 | makeGrants('name%s' % x) |
1131 | |
1132 | -======= |
1133 | - def createSharees(self): |
1134 | - login_person(self.owner) |
1135 | - access_policy = self.factory.makeAccessPolicy( |
1136 | - pillar=self.pillar, |
1137 | - type=InformationType.PROPRIETARY) |
1138 | - self.grantees = [] |
1139 | - |
1140 | - def makeGrants(name): |
1141 | - grantee = self.factory.makePerson(name=name) |
1142 | - self.grantees.append(grantee) |
1143 | - # Make access policy grant so that 'All' is returned. |
1144 | - self.factory.makeAccessPolicyGrant(access_policy, grantee) |
1145 | - # Make access artifact grants so that 'Some' is returned. |
1146 | - artifact_grant = self.factory.makeAccessArtifactGrant() |
1147 | - self.factory.makeAccessPolicyArtifact( |
1148 | - artifact=artifact_grant.abstract_artifact, |
1149 | - policy=access_policy) |
1150 | - # Make grants for grantees in ascending order so we can slice off the |
1151 | - # first elements in the pillar observer results to check batching. |
1152 | - for x in range(10): |
1153 | - makeGrants('name%s' % x) |
1154 | - |
1155 | ->>>>>>> MERGE-SOURCE |
1156 | def test_init_without_feature_flag(self): |
1157 | # We need a feature flag to enable the view. |
1158 | self.assertRaises( |
1159 | @@ -343,7 +209,6 @@ |
1160 | cache = IJSONRequestCache(view.request) |
1161 | self.assertIsNotNone(cache.objects.get('information_types')) |
1162 | self.assertIsNotNone(cache.objects.get('sharing_permissions')) |
1163 | -<<<<<<< TREE |
1164 | batch_size = config.launchpad.default_batch_size |
1165 | apgfs = getUtility(IAccessPolicyGrantFlatSource) |
1166 | sharees = apgfs.findGranteePermissionsByPolicy( |
1167 | @@ -403,65 +268,6 @@ |
1168 | view = create_initialized_view(self.pillar, name='+sharing') |
1169 | cache = IJSONRequestCache(view.request) |
1170 | self.assertTrue(cache.objects.get('sharing_write_enabled')) |
1171 | -======= |
1172 | - aps = getUtility(IService, 'sharing') |
1173 | - batch_size = config.launchpad.default_batch_size |
1174 | - observers = aps.getPillarShareeData( |
1175 | - self.pillar, grantees=self.grantees[:batch_size]) |
1176 | - self.assertContentEqual( |
1177 | - observers, cache.objects.get('sharee_data')) |
1178 | - |
1179 | - def test_view_batch_data(self): |
1180 | - # Test the expected batching data is in the json request cache. |
1181 | - with FeatureFixture(ENABLED_FLAG): |
1182 | - view = create_initialized_view(self.pillar, name='+sharing') |
1183 | - cache = IJSONRequestCache(view.request) |
1184 | - # Test one expected data value (there are many). |
1185 | - next_batch = view.shareeData().batch.nextBatch() |
1186 | - self.assertContentEqual( |
1187 | - next_batch.range_memo, cache.objects.get('next')['memo']) |
1188 | - |
1189 | - def test_view_range_factory(self): |
1190 | - # Test the view range factory is properly configured. |
1191 | - with FeatureFixture(ENABLED_FLAG): |
1192 | - view = create_initialized_view(self.pillar, name='+sharing') |
1193 | - range_factory = view.shareeData().batch.range_factory |
1194 | - |
1195 | - def test_range_factory(): |
1196 | - row = range_factory.resultset.get_plain_result_set()[0] |
1197 | - range_factory.getOrderValuesFor(row) |
1198 | - |
1199 | - self.assertThat( |
1200 | - test_range_factory, |
1201 | - Not(Raises(MatchesException(StormRangeFactoryError)))) |
1202 | - |
1203 | - def test_view_query_count(self): |
1204 | - # Test the query count is within expected limit. |
1205 | - with FeatureFixture(ENABLED_FLAG): |
1206 | - view = create_view(self.pillar, name='+sharing') |
1207 | - with StormStatementRecorder() as recorder: |
1208 | - view.initialize() |
1209 | - self.assertThat(recorder, HasQueryCount(LessThan(7))) |
1210 | - |
1211 | - def test_view_write_enabled_without_feature_flag(self): |
1212 | - # Test that sharing_write_enabled is not set without the feature flag. |
1213 | - with FeatureFixture(ENABLED_FLAG): |
1214 | - login_person(self.owner) |
1215 | - view = create_initialized_view(self.pillar, name='+sharing') |
1216 | - cache = IJSONRequestCache(view.request) |
1217 | - self.assertFalse(cache.objects.get('sharing_write_enabled')) |
1218 | - |
1219 | - def test_view_write_enabled_with_feature_flag(self): |
1220 | - # Test that sharing_write_enabled is set when required. |
1221 | - with FeatureFixture(WRITE_FLAG): |
1222 | - view = create_initialized_view(self.pillar, name='+sharing') |
1223 | - cache = IJSONRequestCache(view.request) |
1224 | - self.assertFalse(cache.objects.get('sharing_write_enabled')) |
1225 | - login_person(self.owner) |
1226 | - view = create_initialized_view(self.pillar, name='+sharing') |
1227 | - cache = IJSONRequestCache(view.request) |
1228 | - self.assertTrue(cache.objects.get('sharing_write_enabled')) |
1229 | ->>>>>>> MERGE-SOURCE |
1230 | |
1231 | |
1232 | class TestProductSharingView(PillarSharingViewTestMixin, |
1233 | |
1234 | === modified file 'lib/lp/registry/enums.py' |
1235 | --- lib/lp/registry/enums.py 2012-03-29 14:36:36 +0000 |
1236 | +++ lib/lp/registry/enums.py 2012-03-29 00:48:21 +0000 |
1237 | @@ -9,16 +9,10 @@ |
1238 | 'DistroSeriesDifferenceType', |
1239 | 'InformationType', |
1240 | 'PersonTransferJobType', |
1241 | -<<<<<<< TREE |
1242 | 'PRIVATE_INFORMATION_TYPES', |
1243 | 'PUBLIC_INFORMATION_TYPES', |
1244 | 'ProductJobType', |
1245 | 'SECURITY_INFORMATION_TYPES', |
1246 | -======= |
1247 | - 'PRIVATE_INFORMATION_TYPES', |
1248 | - 'ProductJobType', |
1249 | - 'SECURITY_INFORMATION_TYPES', |
1250 | ->>>>>>> MERGE-SOURCE |
1251 | 'SharingPermission', |
1252 | ] |
1253 | |
1254 | @@ -70,7 +64,6 @@ |
1255 | """) |
1256 | |
1257 | |
1258 | -<<<<<<< TREE |
1259 | PUBLIC_INFORMATION_TYPES = ( |
1260 | InformationType.PUBLIC, InformationType.UNEMBARGOEDSECURITY) |
1261 | |
1262 | @@ -84,17 +77,6 @@ |
1263 | InformationType.UNEMBARGOEDSECURITY, InformationType.EMBARGOEDSECURITY) |
1264 | |
1265 | |
1266 | -======= |
1267 | -PRIVATE_INFORMATION_TYPES = ( |
1268 | - InformationType.EMBARGOEDSECURITY, InformationType.USERDATA, |
1269 | - InformationType.PROPRIETARY) |
1270 | - |
1271 | - |
1272 | -SECURITY_INFORMATION_TYPES = ( |
1273 | - InformationType.UNEMBARGOEDSECURITY, InformationType.EMBARGOEDSECURITY) |
1274 | - |
1275 | - |
1276 | ->>>>>>> MERGE-SOURCE |
1277 | class SharingPermission(DBEnumeratedType): |
1278 | """Sharing permission. |
1279 | |
1280 | |
1281 | === modified file 'lib/lp/registry/interfaces/accesspolicy.py' |
1282 | --- lib/lp/registry/interfaces/accesspolicy.py 2012-03-29 14:36:36 +0000 |
1283 | +++ lib/lp/registry/interfaces/accesspolicy.py 2012-03-22 09:00:36 +0000 |
1284 | @@ -215,7 +215,6 @@ |
1285 | """Experimental query utility to search through the flattened schema.""" |
1286 | |
1287 | def findGranteesByPolicy(policies): |
1288 | -<<<<<<< TREE |
1289 | """Find teams or users with access grants for the policies. |
1290 | |
1291 | This includes grants for artifacts in the policies. |
1292 | @@ -237,29 +236,6 @@ |
1293 | ALL means the person has an access policy grant and can see all |
1294 | artifacts for the associated pillar. |
1295 | SOME means the person only has specified access artifact grants. |
1296 | -======= |
1297 | - """Find teams or users with access grants for the policies. |
1298 | - |
1299 | - This includes grants for artifacts in the policies. |
1300 | - |
1301 | - :param policies: a collection of `IAccesPolicy`s. |
1302 | - :return: a collection of `IPerson`. |
1303 | - """ |
1304 | - |
1305 | - def findGranteePermissionsByPolicy(policies, grantees=None): |
1306 | - """Find teams or users with access grants for the policies. |
1307 | - |
1308 | - This includes grants for artifacts in the policies. |
1309 | - |
1310 | - :param policies: a collection of `IAccesPolicy`s. |
1311 | - :param grantees: if not None, the result only includes people in the |
1312 | - specified list of grantees. |
1313 | - :return: a collection of (`IPerson`, `IAccessPolicy`, permission) |
1314 | - where permission is a SharingPermission value. |
1315 | - 'ALL' means the person has an access policy grant and can see all |
1316 | - artifacts for the associated pillar. |
1317 | - 'SOME' means the person only has specified access artifact grants. |
1318 | ->>>>>>> MERGE-SOURCE |
1319 | """ |
1320 | |
1321 | def findArtifactsByGrantee(grantee, policies): |
1322 | |
1323 | === modified file 'lib/lp/registry/interfaces/productjob.py' |
1324 | --- lib/lp/registry/interfaces/productjob.py 2012-03-29 14:36:36 +0000 |
1325 | +++ lib/lp/registry/interfaces/productjob.py 2012-03-24 12:41:36 +0000 |
1326 | @@ -1,4 +1,3 @@ |
1327 | -<<<<<<< TREE |
1328 | # Copyright 2012 Canonical Ltd. This software is licensed under the |
1329 | # GNU Affero General Public License version 3 (see the file LICENSE). |
1330 | |
1331 | @@ -120,72 +119,3 @@ |
1332 | :param reply_to_commercial: Set the reply_to property to the |
1333 | commercial email address. |
1334 | """ |
1335 | -======= |
1336 | -# Copyright 2012 Canonical Ltd. This software is licensed under the |
1337 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
1338 | - |
1339 | -"""Interfaces for the Jobs system to update products and send notifications.""" |
1340 | - |
1341 | -__metaclass__ = type |
1342 | -__all__ = [ |
1343 | - 'IProductJob', |
1344 | - 'IProductJobSource', |
1345 | - ] |
1346 | - |
1347 | -from zope.interface import Attribute |
1348 | -from zope.schema import ( |
1349 | - Int, |
1350 | - Object, |
1351 | - ) |
1352 | - |
1353 | -from lp import _ |
1354 | -from lp.registry.interfaces.product import IProduct |
1355 | - |
1356 | -from lp.services.job.interfaces.job import ( |
1357 | - IJob, |
1358 | - IJobSource, |
1359 | - IRunnableJob, |
1360 | - ) |
1361 | - |
1362 | - |
1363 | -class IProductJob(IRunnableJob): |
1364 | - """A Job related to an `IProduct`.""" |
1365 | - |
1366 | - id = Int( |
1367 | - title=_('DB ID'), required=True, readonly=True, |
1368 | - description=_("The tracking number of this job.")) |
1369 | - |
1370 | - job = Object( |
1371 | - title=_('The common Job attributes'), |
1372 | - schema=IJob, |
1373 | - required=True) |
1374 | - |
1375 | - product = Object( |
1376 | - title=_('The product the job is for'), |
1377 | - schema=IProduct, |
1378 | - required=True) |
1379 | - |
1380 | - metadata = Attribute('A dict of data for the job') |
1381 | - |
1382 | - |
1383 | -class IProductJobSource(IJobSource): |
1384 | - """An interface for creating and finding `IProductJob`s.""" |
1385 | - |
1386 | - def create(product, metadata): |
1387 | - """Create a new `IProductJob`. |
1388 | - |
1389 | - :param product: An IProduct. |
1390 | - :param metadata: a dict of configuration data for the job. |
1391 | - The data must be JSON compatible keys and values. |
1392 | - """ |
1393 | - |
1394 | - def find(product=None, date_since=None, job_type=None): |
1395 | - """Find `IProductJob`s that match the specified criteria. |
1396 | - |
1397 | - :param product: Match jobs for specific product. |
1398 | - :param date_since: Match jobs since the specified date. |
1399 | - :param job_type: Match jobs of a specific type. Type is expected |
1400 | - to be a class name. |
1401 | - :return: A `ResultSet` yielding `IProductJob`. |
1402 | - """ |
1403 | ->>>>>>> MERGE-SOURCE |
1404 | |
1405 | === modified file 'lib/lp/registry/interfaces/sharingservice.py' |
1406 | --- lib/lp/registry/interfaces/sharingservice.py 2012-03-29 14:36:36 +0000 |
1407 | +++ lib/lp/registry/interfaces/sharingservice.py 2012-03-26 14:10:32 +0000 |
1408 | @@ -59,7 +59,6 @@ |
1409 | def getPillarSharees(pillar): |
1410 | """Return people/teams who can see pillar artifacts.""" |
1411 | |
1412 | -<<<<<<< TREE |
1413 | @export_read_operation() |
1414 | @operation_parameters( |
1415 | pillar=Reference(IPillar, title=_('Pillar'), required=True)) |
1416 | @@ -83,20 +82,6 @@ |
1417 | - permissions they have for each information type. |
1418 | """ |
1419 | |
1420 | -======= |
1421 | - @export_read_operation() |
1422 | - @operation_parameters( |
1423 | - pillar=Reference(IPillar, title=_('Pillar'), required=True)) |
1424 | - @operation_for_version('devel') |
1425 | - def getPillarShareeData(pillar, grantees=None): |
1426 | - """Return people/teams who can see pillar artifacts. |
1427 | - |
1428 | - The result records are json data which includes: |
1429 | - - person name |
1430 | - - permissions they have for each information type. |
1431 | - """ |
1432 | - |
1433 | ->>>>>>> MERGE-SOURCE |
1434 | @export_write_operation() |
1435 | @call_with(user=REQUEST_USER) |
1436 | @operation_parameters( |
1437 | |
1438 | === modified file 'lib/lp/registry/javascript/sharing/pillarsharingview.js' |
1439 | --- lib/lp/registry/javascript/sharing/pillarsharingview.js 2012-03-29 14:36:36 +0000 |
1440 | +++ lib/lp/registry/javascript/sharing/pillarsharingview.js 2012-03-26 01:11:56 +0000 |
1441 | @@ -103,16 +103,10 @@ |
1442 | var otns = Y.lp.registry.sharing.shareetable; |
1443 | var sharee_table = new otns.ShareeTableWidget({ |
1444 | sharees: sharee_data, |
1445 | -<<<<<<< TREE |
1446 | sharing_permissions: |
1447 | this.get('sharing_permissions_by_value'), |
1448 | information_types: this.get('information_types_by_value'), |
1449 | write_enabled: this.get('write_enabled') |
1450 | -======= |
1451 | - sharing_permissions: sharing_permissions, |
1452 | - information_types: this.get('information_types_by_value'), |
1453 | - write_enabled: this.get('write_enabled') |
1454 | ->>>>>>> MERGE-SOURCE |
1455 | }); |
1456 | this.set('sharee_table', sharee_table); |
1457 | sharee_table.render(); |
1458 | |
1459 | === modified file 'lib/lp/registry/javascript/sharing/shareepicker.js' |
1460 | --- lib/lp/registry/javascript/sharing/shareepicker.js 2012-03-29 14:36:36 +0000 |
1461 | +++ lib/lp/registry/javascript/sharing/shareepicker.js 2012-03-28 00:44:36 +0000 |
1462 | @@ -51,12 +51,8 @@ |
1463 | } |
1464 | } |
1465 | this.set('information_types', information_types); |
1466 | -<<<<<<< TREE |
1467 | this.set('sharing_permissions', sharing_permissions); |
1468 | this.step_one_header = this.get('headerContent'); |
1469 | -======= |
1470 | - this.set('sharing_permissions', sharing_permissions); |
1471 | ->>>>>>> MERGE-SOURCE |
1472 | var self = this; |
1473 | this.subscribe('save', function (e) { |
1474 | e.halt(); |
1475 | @@ -173,67 +169,10 @@ |
1476 | var step_one_content = contentBox.one('.yui3-widget-bd'); |
1477 | var step_two_content = contentBox.one('.picker-content-two'); |
1478 | if (step_two_content === null) { |
1479 | -<<<<<<< TREE |
1480 | step_two_content = this._render_step_two( |
1481 | data.back_enabled, data.allowed_permissions); |
1482 | -======= |
1483 | - var step_two_html = [ |
1484 | - '<div class="picker-content-two transparent">', |
1485 | - '<div class="step-links">', |
1486 | - '<a class="prev js-action" href="#">Back</a>', |
1487 | - '<button class="next lazr-pos lazr-btn"></button>', |
1488 | - '<a class="next js-action" href="#">Select</a>', |
1489 | - '</div></div>' |
1490 | - ].join(' '); |
1491 | - step_two_content = Y.Node.create(step_two_html); |
1492 | - var self = this; |
1493 | - // Remove the back link if required. |
1494 | - if (Y.Lang.isBoolean(data.back_enabled) |
1495 | - && !data.back_enabled ) { |
1496 | - step_two_content.one('a.prev').remove(true); |
1497 | - } else { |
1498 | - step_two_content.one('a.prev').on('click', function(e) { |
1499 | - e.halt(); |
1500 | - self._display_step_one(); |
1501 | - }); |
1502 | - } |
1503 | - // Wire up the next (ie submit) links. |
1504 | - step_two_content.all('.next').on('click', function(e) { |
1505 | - e.halt(); |
1506 | - // Only submit if at least one info type is selected. |
1507 | - if (!self._all_info_choices_unticked(step_two_content)) { |
1508 | - self.fire('save', data, 2); |
1509 | - } |
1510 | - }); |
1511 | - // By default, we only show All or Nothing. |
1512 | - var allowed_permissions = ['ALL', 'NOTHING']; |
1513 | - if (Y.Lang.isValue(data.allowed_permissions)) { |
1514 | - allowed_permissions = data.allowed_permissions; |
1515 | - } |
1516 | - var sharing_permissions = []; |
1517 | - Y.Array.each(this.get('sharing_permissions'), |
1518 | - function(permission) { |
1519 | - if (Y.Array.indexOf( |
1520 | - allowed_permissions, permission.value) >=0) { |
1521 | - sharing_permissions.push(permission); |
1522 | - } |
1523 | - }); |
1524 | - var policy_selector = self._make_policy_selector( |
1525 | - sharing_permissions); |
1526 | - step_two_content.one('div.step-links') |
1527 | - .insert(policy_selector, 'before'); |
1528 | ->>>>>>> MERGE-SOURCE |
1529 | step_one_content.insert(step_two_content, 'after'); |
1530 | -<<<<<<< TREE |
1531 | -======= |
1532 | - step_two_content.all('input[name^=field.permission]') |
1533 | - .on('click', function(e) { |
1534 | - self._disable_select_if_all_info_choices_unticked( |
1535 | - step_two_content); |
1536 | - }); |
1537 | ->>>>>>> MERGE-SOURCE |
1538 | } |
1539 | -<<<<<<< TREE |
1540 | // Wire up the next (ie submit) links. |
1541 | step_two_content.detach('click'); |
1542 | step_two_content.delegate('click', function(e) { |
1543 | @@ -252,17 +191,6 @@ |
1544 | // sharee_permissions. |
1545 | if (Y.Lang.isObject(data.sharee_permissions)) { |
1546 | Y.each(data.sharee_permissions, function(perm, type) { |
1547 | -======= |
1548 | - // Initially set all radio buttons to Nothing. |
1549 | - step_two_content.all('input[name^=field.permission][value=NOTHING]') |
1550 | - .each(function(radio_button) { |
1551 | - radio_button.set('checked', true); |
1552 | - }); |
1553 | - // Ensure the correct radio buttons are ticked according to the |
1554 | - // sharee_permissions. |
1555 | - if (Y.Lang.isObject(data.sharee_permissions)) { |
1556 | - Y.each(data.sharee_permissions, function(perm, type) { |
1557 | ->>>>>>> MERGE-SOURCE |
1558 | var cb = step_two_content.one( |
1559 | 'input[name=field.permission.'+type+']' + |
1560 | '[value="' + perm + '"]'); |
1561 | @@ -326,7 +254,6 @@ |
1562 | this.fire('save', data, 0); |
1563 | }, |
1564 | |
1565 | -<<<<<<< TREE |
1566 | _sharing_permission_template: function() { |
1567 | return [ |
1568 | '<table class="radio-button-widget"><tbody>', |
1569 | @@ -348,29 +275,6 @@ |
1570 | }, |
1571 | |
1572 | _make_policy_selector: function(allowed_permissions) { |
1573 | -======= |
1574 | - _sharing_permission_template: function() { |
1575 | - return [ |
1576 | - '<table class="radio-button-widget"><tbody>', |
1577 | - '{{#permissions}}', |
1578 | - '<tr>', |
1579 | - ' <input type="radio"', |
1580 | - ' value="{{value}}"', |
1581 | - ' name="field.permission.{{info_type}}"', |
1582 | - ' id="field.permission.{{info_type}}.{{index}}"', |
1583 | - ' class="radioType">', |
1584 | - ' <label for="field.permission.{{info_type}}"', |
1585 | - ' title="{{description}}">', |
1586 | - ' {{title}}', |
1587 | - ' </label>', |
1588 | - '</tr>', |
1589 | - '{{/permissions}}', |
1590 | - '</tbody></table>' |
1591 | - ].join(''); |
1592 | - }, |
1593 | - |
1594 | - _make_policy_selector: function(allowed_permissions) { |
1595 | ->>>>>>> MERGE-SOURCE |
1596 | // The policy selector is a set of radio buttons. |
1597 | var sharing_permissions_template = this._sharing_permission_template(); |
1598 | var html = Y.lp.mustache.to_html([ |
1599 | |
1600 | === modified file 'lib/lp/registry/javascript/sharing/shareetable.js' |
1601 | --- lib/lp/registry/javascript/sharing/shareetable.js 2012-03-29 14:36:36 +0000 |
1602 | +++ lib/lp/registry/javascript/sharing/shareetable.js 2012-03-28 03:50:33 +0000 |
1603 | @@ -49,7 +49,7 @@ |
1604 | }, |
1605 | // The sharing permission choices: all, some, nothing etc. |
1606 | sharing_permissions: { |
1607 | - value: [] |
1608 | + value: {} |
1609 | }, |
1610 | // The node holding the sharee table. |
1611 | sharee_table: { |
1612 | @@ -273,25 +273,11 @@ |
1613 | }, |
1614 | |
1615 | renderUI: function() { |
1616 | -<<<<<<< TREE |
1617 | - this._render_sharees(this.get('sharees')); |
1618 | - }, |
1619 | - |
1620 | - _render_sharees: function(sharees) { |
1621 | - var sharee_table = this.get('sharee_table'); |
1622 | -======= |
1623 | - this._render_sharees(this.get('sharees')); |
1624 | - }, |
1625 | - |
1626 | - _render_sharees: function(sharees) { |
1627 | - var sharee_table = this.get('sharee_table'); |
1628 | - if (sharees.length === 0) { |
1629 | - sharee_table.one('tr#sharee-table-loading td') |
1630 | - .setContent("This project's private information is " + |
1631 | - "not shared with anyone."); |
1632 | - return; |
1633 | - } |
1634 | ->>>>>>> MERGE-SOURCE |
1635 | + this._render_sharees(this.get('sharees')); |
1636 | + }, |
1637 | + |
1638 | + _render_sharees: function(sharees) { |
1639 | + var sharee_table = this.get('sharee_table'); |
1640 | var partials = { |
1641 | sharee_access_policies: |
1642 | this.get('sharee_policy_template'), |
1643 | @@ -302,7 +288,6 @@ |
1644 | this.get('sharee_table_template'), |
1645 | {sharees: sharees}, partials); |
1646 | var table_node = Y.Node.create(html); |
1647 | -<<<<<<< TREE |
1648 | if (sharees.length === 0) { |
1649 | table_node.one('tbody').appendChild( |
1650 | Y.Node.create(this.get('sharee_table_empty_row'))); |
1651 | @@ -311,12 +296,6 @@ |
1652 | this.render_sharing_info(sharees); |
1653 | this._update_editable_status(); |
1654 | this.set('sharees', sharees); |
1655 | -======= |
1656 | - sharee_table.replace(table_node); |
1657 | - this.render_sharing_info(sharees); |
1658 | - this._update_editable_status(); |
1659 | - this.set('sharees', sharees); |
1660 | ->>>>>>> MERGE-SOURCE |
1661 | }, |
1662 | |
1663 | bindUI: function() { |
1664 | @@ -521,7 +500,6 @@ |
1665 | var rows_to_delete = sharee_table.all(deleted_row_selectors.join(',')); |
1666 | var delete_rows = function() { |
1667 | rows_to_delete.remove(true); |
1668 | -<<<<<<< TREE |
1669 | if (all_rows_deleted === true) { |
1670 | sharee_table.one('tbody') |
1671 | .appendChild('<tr id="sharee-table-not-shared"></tr>') |
1672 | @@ -529,15 +507,6 @@ |
1673 | .setContent("This project's private information " + |
1674 | "is not shared with anyone."); |
1675 | } |
1676 | -======= |
1677 | - if (all_rows_deleted === true) { |
1678 | - sharee_table.one('tbody') |
1679 | - .appendChild('<tr id="sharee-table-loading"></tr>') |
1680 | - .appendChild('<td></td>') |
1681 | - .setContent("This project's private information " + |
1682 | - "is not shared with anyone."); |
1683 | - } |
1684 | ->>>>>>> MERGE-SOURCE |
1685 | }; |
1686 | var anim_duration = this.get('anim_duration'); |
1687 | if (anim_duration === 0 ) { |
1688 | |
1689 | === modified file 'lib/lp/registry/javascript/sharing/tests/test_shareelisting_navigator.html' |
1690 | --- lib/lp/registry/javascript/sharing/tests/test_shareelisting_navigator.html 2012-03-29 14:36:36 +0000 |
1691 | +++ lib/lp/registry/javascript/sharing/tests/test_shareelisting_navigator.html 2012-03-28 03:50:33 +0000 |
1692 | @@ -1,4 +1,3 @@ |
1693 | -<<<<<<< TREE |
1694 | <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" |
1695 | "http://www.w3.org/TR/html4/strict.dtd"> |
1696 | <!-- |
1697 | @@ -61,67 +60,3 @@ |
1698 | </script> |
1699 | </body> |
1700 | </html> |
1701 | -======= |
1702 | -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" |
1703 | - "http://www.w3.org/TR/html4/strict.dtd"> |
1704 | -<!-- |
1705 | -Copyright 2012 Canonical Ltd. This software is licensed under the |
1706 | -GNU Affero General Public License version 3 (see the file LICENSE). |
1707 | ---> |
1708 | - |
1709 | -<html> |
1710 | - <head> |
1711 | - <title>Sharee Listing Navigator Tests</title> |
1712 | - |
1713 | - <!-- YUI and test setup --> |
1714 | - <script type="text/javascript" |
1715 | - src="../../../../../../build/js/yui/yui/yui.js"> |
1716 | - </script> |
1717 | - <link rel="stylesheet" |
1718 | - href="../../../../../../build/js/yui/console/assets/console-core.css" /> |
1719 | - <link rel="stylesheet" |
1720 | - href="../../../../../../build/js/yui/console/assets/skins/sam/console.css" /> |
1721 | - <link rel="stylesheet" |
1722 | - href="../../../../../../build/js/yui/test/assets/skins/sam/test.css" /> |
1723 | - |
1724 | - <script type="text/javascript" |
1725 | - src="../../../../../../build/js/lp/app/testing/testrunner.js"></script> |
1726 | - |
1727 | - <link rel="stylesheet" href="../../../../app/javascript/testing/test.css" /> |
1728 | - |
1729 | - <!-- Dependencies --> |
1730 | - <script type="text/javascript" |
1731 | - src="../../../../../../build/js/lp/app/client.js"></script> |
1732 | - <script type="text/javascript" |
1733 | - src="../../../../../../build/js/lp/app/lp.js"></script> |
1734 | - <script type="text/javascript" |
1735 | - src="../../../../../../build/js/lp/app/mustache.js"></script> |
1736 | - <script type="text/javascript" |
1737 | - src="../../../../../../build/js/lp/app/indicator/indicator.js"></script> |
1738 | - <script type="text/javascript" |
1739 | - src="../../../../../../build/js/lp/app/overlay/overlay.js"></script> |
1740 | - <script type="text/javascript" |
1741 | - src="../../../../../../build/js/lp/app/listing_navigator.js"></script> |
1742 | - |
1743 | - <!-- The module under test. --> |
1744 | - <script type="text/javascript" src="../shareelisting_navigator.js"></script> |
1745 | - |
1746 | - <!-- The test suite --> |
1747 | - <script type="text/javascript" src="test_shareelisting_navigator.js"></script> |
1748 | - |
1749 | - </head> |
1750 | - <body class="yui3-skin-sam"> |
1751 | - <ul id="suites"> |
1752 | - <li>lp.registry.sharing.shareelisting_navigator.test</li> |
1753 | - </ul> |
1754 | - <div id="fixture"></div> |
1755 | - <script type="text/x-template" id="sharee-table-template"> |
1756 | - <table id='sharee-table'> |
1757 | - <tr id='sharee-table-loading'><td> |
1758 | - Loading... |
1759 | - </td></tr> |
1760 | - </table> |
1761 | - </script> |
1762 | - </body> |
1763 | -</html> |
1764 | ->>>>>>> MERGE-SOURCE |
1765 | |
1766 | === modified file 'lib/lp/registry/javascript/sharing/tests/test_shareepicker.js' |
1767 | --- lib/lp/registry/javascript/sharing/tests/test_shareepicker.js 2012-03-29 14:36:36 +0000 |
1768 | +++ lib/lp/registry/javascript/sharing/tests/test_shareepicker.js 2012-03-27 02:27:21 +0000 |
1769 | @@ -181,7 +181,6 @@ |
1770 | // Check the title and step title are correct. |
1771 | var steptitle = cb.one('.contains-steptitle h2').getContent(); |
1772 | Y.Assert.areEqual( |
1773 | -<<<<<<< TREE |
1774 | 'Update sharing policies', |
1775 | this.picker.get('headerContent').get('text')); |
1776 | Y.Assert.areEqual( |
1777 | @@ -191,14 +190,6 @@ |
1778 | Y.Assert.isNotNull(cb.one('input[value=ALL]')); |
1779 | Y.Assert.isNotNull(cb.one('input[value=NOTHING]')); |
1780 | Y.Assert.isNull(cb.one('input[value=SOME]')); |
1781 | -======= |
1782 | - 'Select sharing policies for Fred', steptitle); |
1783 | - // By default, selections only for ALL and NOTHING are available |
1784 | - // (and no others). |
1785 | - Y.Assert.isNotNull(cb.one('input[value=ALL]')); |
1786 | - Y.Assert.isNotNull(cb.one('input[value=NOTHING]')); |
1787 | - Y.Assert.isNull(cb.one('input[value=SOME]')); |
1788 | ->>>>>>> MERGE-SOURCE |
1789 | // Selected permission checkboxes should be ticked. |
1790 | cb.all('input[name=field.permission.P1]') |
1791 | .each(function(node) { |
1792 | |
1793 | === modified file 'lib/lp/registry/javascript/sharing/tests/test_shareetable.html' |
1794 | --- lib/lp/registry/javascript/sharing/tests/test_shareetable.html 2012-03-29 14:36:36 +0000 |
1795 | +++ lib/lp/registry/javascript/sharing/tests/test_shareetable.html 2012-03-28 03:50:33 +0000 |
1796 | @@ -73,7 +73,6 @@ |
1797 | <ul id="suites"> |
1798 | <li>lp.registry.sharing.shareetable.test</li> |
1799 | </ul> |
1800 | -<<<<<<< TREE |
1801 | <div id="fixture"></div> |
1802 | <script type="text/x-template" id="sharee-table-template"> |
1803 | <table id='sharee-table'> |
1804 | @@ -82,15 +81,5 @@ |
1805 | </td></tr> |
1806 | </table> |
1807 | </script> |
1808 | -======= |
1809 | - <div id="fixture"></div> |
1810 | - <script type="text/x-template" id="sharee-table-template"> |
1811 | - <table id='sharee-table'> |
1812 | - <tr id='sharee-table-loading'><td> |
1813 | - Loading... |
1814 | - </td></tr> |
1815 | - </table> |
1816 | - </script> |
1817 | ->>>>>>> MERGE-SOURCE |
1818 | </body> |
1819 | </html> |
1820 | |
1821 | === modified file 'lib/lp/registry/javascript/sharing/tests/test_shareetable.js' |
1822 | --- lib/lp/registry/javascript/sharing/tests/test_shareetable.js 2012-03-29 14:36:36 +0000 |
1823 | +++ lib/lp/registry/javascript/sharing/tests/test_shareetable.js 2012-03-28 03:50:33 +0000 |
1824 | @@ -25,7 +25,6 @@ |
1825 | ] |
1826 | } |
1827 | }; |
1828 | -<<<<<<< TREE |
1829 | this.sharing_permissions = { |
1830 | s1: 'S1', |
1831 | s2: 'S2' |
1832 | @@ -40,27 +39,13 @@ |
1833 | Y.one('#sharee-table-template').getContent()); |
1834 | this.fixture.appendChild(sharee_table); |
1835 | |
1836 | -======= |
1837 | - this.fixture = Y.one('#fixture'); |
1838 | - var sharee_table = Y.Node.create( |
1839 | - Y.one('#sharee-table-template').getContent()); |
1840 | - this.fixture.appendChild(sharee_table); |
1841 | - |
1842 | ->>>>>>> MERGE-SOURCE |
1843 | }, |
1844 | |
1845 | tearDown: function () { |
1846 | -<<<<<<< TREE |
1847 | if (this.fixture !== null) { |
1848 | this.fixture.empty(true); |
1849 | } |
1850 | delete this.fixture; |
1851 | -======= |
1852 | - if (this.fixture !== null) { |
1853 | - this.fixture.empty(); |
1854 | - } |
1855 | - delete this.fixture; |
1856 | ->>>>>>> MERGE-SOURCE |
1857 | delete window.LP; |
1858 | }, |
1859 | |
1860 | @@ -72,7 +57,6 @@ |
1861 | sharee_table: Y.one('#sharee-table'), |
1862 | anim_duration: 0, |
1863 | sharees: window.LP.cache.sharee_data, |
1864 | -<<<<<<< TREE |
1865 | sharing_permissions: this.sharing_permissions, |
1866 | information_types: this.information_types, |
1867 | write_enabled: true |
1868 | @@ -80,14 +64,6 @@ |
1869 | window.LP.cache.sharee_data = config.sharees; |
1870 | var ns = Y.lp.registry.sharing.shareetable; |
1871 | return new ns.ShareeTableWidget(config); |
1872 | -======= |
1873 | - sharing_permissions: window.LP.cache.sharing_permissions, |
1874 | - information_types: window.LP.cache.information_types, |
1875 | - write_enabled: true |
1876 | - }, overrides); |
1877 | - var ns = Y.lp.registry.sharing.shareetable; |
1878 | - return new ns.ShareeTableWidget(config); |
1879 | ->>>>>>> MERGE-SOURCE |
1880 | }, |
1881 | |
1882 | test_library_exists: function () { |
1883 | @@ -104,7 +80,6 @@ |
1884 | "Sharee table failed to be instantiated"); |
1885 | }, |
1886 | |
1887 | -<<<<<<< TREE |
1888 | // Read only mode disables the correct things. |
1889 | test_readonly: function() { |
1890 | this.sharee_table = this._create_Widget({ |
1891 | @@ -146,34 +121,6 @@ |
1892 | Y.Assert.isNull(Y.one('tr#sharee-table-not-shared')); |
1893 | }, |
1894 | |
1895 | -======= |
1896 | - // Read only mode disables the correct things. |
1897 | - test_readonly: function() { |
1898 | - this.sharee_table = this._create_Widget({ |
1899 | - write_enabled: false |
1900 | - }); |
1901 | - this.sharee_table.render(); |
1902 | - Y.all('#sharee-table ' + |
1903 | - '.sprite.add, .sprite.edit, .sprite.remove a') |
1904 | - .each(function(link) { |
1905 | - Y.Assert.isTrue(link.hasClass('unseen')); |
1906 | - }); |
1907 | - }, |
1908 | - |
1909 | - // When there are no sharees, the table contains an informative message. |
1910 | - test_no_sharee_message: function() { |
1911 | - this.sharee_table = this._create_Widget({ |
1912 | - sharees: [] |
1913 | - }); |
1914 | - this.sharee_table.render(); |
1915 | - Y.Assert.areEqual( |
1916 | - "This project's private information is not shared " + |
1917 | - "with anyone.", |
1918 | - Y.one('#sharee-table tr#sharee-table-loading td') |
1919 | - .getContent()); |
1920 | - }, |
1921 | - |
1922 | ->>>>>>> MERGE-SOURCE |
1923 | // The given sharee is correctly rendered. |
1924 | _test_sharee_rendered: function(sharee) { |
1925 | // The sharee row |
1926 | @@ -344,7 +291,6 @@ |
1927 | this.sharee_table.syncUI(); |
1928 | // Check the results. |
1929 | var self = this; |
1930 | -<<<<<<< TREE |
1931 | Y.Array.each(sharee_data, function(sharee) { |
1932 | self._test_sharee_rendered(sharee); |
1933 | }); |
1934 | @@ -400,63 +346,6 @@ |
1935 | 'permissions': {'P1': 's2'}}; |
1936 | this.sharee_table.navigator.fire('updateContent', [new_sharee]); |
1937 | this._test_sharee_rendered(new_sharee); |
1938 | -======= |
1939 | - Y.Array.each(sharee_data, function(sharee) { |
1940 | - self._test_sharee_rendered(sharee); |
1941 | - }); |
1942 | - var deleted_row = '#sharee-table tr[id=permission-fred]'; |
1943 | - Y.Assert.isNull(Y.one(deleted_row)); |
1944 | - }, |
1945 | - |
1946 | - // The navigator model total attribute is updated when the currently |
1947 | - // displayed sharee data changes. |
1948 | - test_navigation_totals_updated: function() { |
1949 | - this.sharee_table = this._create_Widget(); |
1950 | - this.sharee_table.render(); |
1951 | - // We manipulate the cached model data - delete, add and update |
1952 | - var sharee_data = window.LP.cache.sharee_data; |
1953 | - // Insert a new record. |
1954 | - var new_sharee = { |
1955 | - 'name': 'joe', 'display_name': 'Joe Smith', |
1956 | - 'role': '(Maintainer)', web_link: '~joe', |
1957 | - 'self_link': '~joe', |
1958 | - 'permissions': {'P1': 's2'}}; |
1959 | - sharee_data.splice(0, 0, new_sharee); |
1960 | - this.sharee_table.syncUI(); |
1961 | - // Check the results. |
1962 | - Y.Assert.areEqual( |
1963 | - 3, this.sharee_table.navigator.get('model').get('total')); |
1964 | - }, |
1965 | - |
1966 | - // When all rows are deleted, the table contains an informative message. |
1967 | - test_delete_all: function() { |
1968 | - this.sharee_table = this._create_Widget(); |
1969 | - this.sharee_table.render(); |
1970 | - // We manipulate the cached model data. |
1971 | - var sharee_data = window.LP.cache.sharee_data; |
1972 | - // Delete all the records. |
1973 | - sharee_data.splice(0, 2); |
1974 | - this.sharee_table.syncUI(); |
1975 | - // Check the results. |
1976 | - Y.Assert.areEqual( |
1977 | - "This project's private information is not shared " + |
1978 | - "with anyone.", |
1979 | - Y.one('#sharee-table tr#sharee-table-loading td') |
1980 | - .getContent()); |
1981 | - }, |
1982 | - |
1983 | - // A batch update is correctly rendered. |
1984 | - test_navigator_content_update: function() { |
1985 | - this.sharee_table = this._create_Widget(); |
1986 | - this.sharee_table.render(); |
1987 | - var new_sharee = { |
1988 | - 'name': 'joe', 'display_name': 'Joe Smith', |
1989 | - 'role': '(Maintainer)', web_link: '~joe', |
1990 | - 'self_link': '~joe', |
1991 | - 'permissions': {'P1': 's2'}}; |
1992 | - this.sharee_table.navigator.fire('updateContent', [new_sharee]); |
1993 | - this._test_sharee_rendered(new_sharee); |
1994 | ->>>>>>> MERGE-SOURCE |
1995 | } |
1996 | })); |
1997 | |
1998 | |
1999 | === modified file 'lib/lp/registry/model/accesspolicy.py' |
2000 | --- lib/lp/registry/model/accesspolicy.py 2012-03-29 14:36:36 +0000 |
2001 | +++ lib/lp/registry/model/accesspolicy.py 2012-03-23 04:12:59 +0000 |
2002 | @@ -18,12 +18,8 @@ |
2003 | And, |
2004 | In, |
2005 | Or, |
2006 | -<<<<<<< TREE |
2007 | Select, |
2008 | SQL, |
2009 | -======= |
2010 | - SQL, |
2011 | ->>>>>>> MERGE-SOURCE |
2012 | ) |
2013 | from storm.properties import ( |
2014 | DateTime, |
2015 | @@ -359,7 +355,6 @@ |
2016 | Person, Person.id == cls.grantee_id, cls.policy_id.is_in(ids)) |
2017 | |
2018 | @classmethod |
2019 | -<<<<<<< TREE |
2020 | def findGranteePermissionsByPolicy(cls, policies, grantees=None): |
2021 | """See `IAccessPolicyGrantFlatSource`.""" |
2022 | policies_by_id = dict((policy.id, policy) for policy in policies) |
2023 | @@ -409,30 +404,6 @@ |
2024 | result_decorator=set_permission, pre_iter_hook=load_permissions) |
2025 | |
2026 | @classmethod |
2027 | -======= |
2028 | - def findGranteePermissionsByPolicy(cls, policies, grantees=None): |
2029 | - """See `IAccessPolicyGrantFlatSource`.""" |
2030 | - ids = [policy.id for policy in policies] |
2031 | - sharing_permission_term = SQL(""" |
2032 | - CASE( |
2033 | - MIN(COALESCE(artifact, 0))) |
2034 | - WHEN 0 THEN 'ALL' |
2035 | - ELSE 'SOME' |
2036 | - END |
2037 | - """) |
2038 | - constraints = [ |
2039 | - Person.id == cls.grantee_id, |
2040 | - AccessPolicy.id == cls.policy_id, |
2041 | - cls.policy_id.is_in(ids)] |
2042 | - if grantees: |
2043 | - grantee_ids = [grantee.id for grantee in grantees] |
2044 | - constraints.append(cls.grantee_id.is_in(grantee_ids)) |
2045 | - return IStore(cls).find( |
2046 | - (Person, AccessPolicy, sharing_permission_term), |
2047 | - *constraints).group_by(Person, AccessPolicy) |
2048 | - |
2049 | - @classmethod |
2050 | ->>>>>>> MERGE-SOURCE |
2051 | def findArtifactsByGrantee(cls, grantee, policies): |
2052 | """See `IAccessPolicyGrantFlatSource`.""" |
2053 | ids = [policy.id for policy in policies] |
2054 | |
2055 | === modified file 'lib/lp/registry/model/person.py' |
2056 | --- lib/lp/registry/model/person.py 2012-03-29 14:36:36 +0000 |
2057 | +++ lib/lp/registry/model/person.py 2012-03-29 06:02:46 +0000 |
2058 | @@ -1850,24 +1850,16 @@ |
2059 | Bug, |
2060 | Join(BugSubscription, BugSubscription.bug_id == Bug.id)), |
2061 | where=And( |
2062 | -<<<<<<< TREE |
2063 | Bug.information_type.is_in(PRIVATE_INFORMATION_TYPES), |
2064 | -======= |
2065 | - Bug._private == True, |
2066 | ->>>>>>> MERGE-SOURCE |
2067 | BugSubscription.person_id == self.id)), |
2068 | Select( |
2069 | Bug.id, |
2070 | tables=( |
2071 | Bug, |
2072 | Join(BugTask, BugTask.bugID == Bug.id)), |
2073 | -<<<<<<< TREE |
2074 | where=And(Bug.information_type.is_in( |
2075 | PRIVATE_INFORMATION_TYPES), |
2076 | BugTask.assignee == self.id)), |
2077 | -======= |
2078 | - where=And(Bug._private == True, BugTask.assignee == self.id)), |
2079 | ->>>>>>> MERGE-SOURCE |
2080 | limit=1)) |
2081 | if private_bugs_involved.rowcount: |
2082 | raise TeamSubscriptionPolicyError( |
2083 | |
2084 | === modified file 'lib/lp/registry/model/productjob.py' |
2085 | --- lib/lp/registry/model/productjob.py 2012-03-29 14:36:36 +0000 |
2086 | +++ lib/lp/registry/model/productjob.py 2012-03-24 12:36:13 +0000 |
2087 | @@ -1,4 +1,3 @@ |
2088 | -<<<<<<< TREE |
2089 | # Copyright 2012 Canonical Ltd. This software is licensed under the |
2090 | # GNU Affero General Public License version 3 (see the file LICENSE). |
2091 | |
2092 | @@ -283,156 +282,3 @@ |
2093 | 'Launchpad', config.canonical.noreply_from_address) |
2094 | self.sendEmailToMaintainer( |
2095 | self.email_template_name, self.subject, from_address) |
2096 | -======= |
2097 | -# Copyright 2012 Canonical Ltd. This software is licensed under the |
2098 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
2099 | - |
2100 | -"""Jobs classes to update products and send notifications.""" |
2101 | - |
2102 | -__metaclass__ = type |
2103 | -__all__ = [ |
2104 | - 'ProductJob', |
2105 | - ] |
2106 | - |
2107 | -from lazr.delegates import delegates |
2108 | -import simplejson |
2109 | -from storm.expr import ( |
2110 | - And, |
2111 | - ) |
2112 | -from storm.locals import ( |
2113 | - Int, |
2114 | - Reference, |
2115 | - Unicode, |
2116 | - ) |
2117 | -from zope.interface import ( |
2118 | - classProvides, |
2119 | - implements, |
2120 | - ) |
2121 | - |
2122 | -from lp.registry.enums import ProductJobType |
2123 | -from lp.registry.interfaces.product import ( |
2124 | - IProduct, |
2125 | - ) |
2126 | -from lp.registry.interfaces.productjob import ( |
2127 | - IProductJob, |
2128 | - IProductJobSource, |
2129 | - ) |
2130 | -from lp.registry.model.product import Product |
2131 | -from lp.services.database.decoratedresultset import DecoratedResultSet |
2132 | -from lp.services.database.enumcol import EnumCol |
2133 | -from lp.services.database.lpstorm import ( |
2134 | - IMasterStore, |
2135 | - IStore, |
2136 | - ) |
2137 | -from lp.services.database.stormbase import StormBase |
2138 | -from lp.services.job.model.job import Job |
2139 | -from lp.services.job.runner import BaseRunnableJob |
2140 | - |
2141 | - |
2142 | -class ProductJob(StormBase): |
2143 | - """Base class for product jobs.""" |
2144 | - |
2145 | - implements(IProductJob) |
2146 | - |
2147 | - __storm_table__ = 'ProductJob' |
2148 | - |
2149 | - id = Int(primary=True) |
2150 | - |
2151 | - job_id = Int(name='job') |
2152 | - job = Reference(job_id, Job.id) |
2153 | - |
2154 | - product_id = Int(name='product') |
2155 | - product = Reference(product_id, Product.id) |
2156 | - |
2157 | - job_type = EnumCol(enum=ProductJobType, notNull=True) |
2158 | - |
2159 | - _json_data = Unicode('json_data') |
2160 | - |
2161 | - @property |
2162 | - def metadata(self): |
2163 | - return simplejson.loads(self._json_data) |
2164 | - |
2165 | - def __init__(self, product, job_type, metadata): |
2166 | - """Constructor. |
2167 | - |
2168 | - :param product: The product the job is for. |
2169 | - :param job_type: The type job the product needs run. |
2170 | - :param metadata: A dict of JSON-compatible data to pass to the job. |
2171 | - """ |
2172 | - super(ProductJob, self).__init__() |
2173 | - self.job = Job() |
2174 | - self.product = product |
2175 | - self.job_type = job_type |
2176 | - json_data = simplejson.dumps(metadata) |
2177 | - self._json_data = json_data.decode('utf-8') |
2178 | - |
2179 | - |
2180 | -class ProductJobDerived(BaseRunnableJob): |
2181 | - """Intermediate class for deriving from ProductJob. |
2182 | - |
2183 | - Storm classes can't simply be subclassed or you can end up with |
2184 | - multiple objects referencing the same row in the db. This class uses |
2185 | - lazr.delegates, which is a little bit simpler than storm's |
2186 | - inheritance solution to the problem. Subclasses need to override |
2187 | - the run() method. |
2188 | - """ |
2189 | - |
2190 | - delegates(IProductJob) |
2191 | - classProvides(IProductJobSource) |
2192 | - |
2193 | - def __init__(self, job): |
2194 | - self.context = job |
2195 | - |
2196 | - def __repr__(self): |
2197 | - return ( |
2198 | - "<{self.__class__.__name__} for {self.product.name} " |
2199 | - "status={self.job.status}>").format(self=self) |
2200 | - |
2201 | - @classmethod |
2202 | - def create(cls, product, metadata): |
2203 | - """See `IProductJob`.""" |
2204 | - if not IProduct.providedBy(product): |
2205 | - raise TypeError("Product must be an IProduct: %s" % repr(product)) |
2206 | - job = ProductJob( |
2207 | - product=product, job_type=cls.class_job_type, metadata=metadata) |
2208 | - return cls(job) |
2209 | - |
2210 | - @classmethod |
2211 | - def find(cls, product, date_since=None, job_type=None): |
2212 | - """See `IPersonMergeJobSource`.""" |
2213 | - conditions = [ |
2214 | - ProductJob.job_id == Job.id, |
2215 | - ProductJob.product == product.id, |
2216 | - ] |
2217 | - if date_since is not None: |
2218 | - conditions.append( |
2219 | - Job.date_created >= date_since) |
2220 | - if job_type is not None: |
2221 | - conditions.append( |
2222 | - ProductJob.job_type == job_type) |
2223 | - return DecoratedResultSet( |
2224 | - IStore(ProductJob).find( |
2225 | - ProductJob, *conditions), cls) |
2226 | - |
2227 | - @classmethod |
2228 | - def iterReady(cls): |
2229 | - """Iterate through all ready ProductJobs.""" |
2230 | - store = IMasterStore(ProductJob) |
2231 | - jobs = store.find( |
2232 | - ProductJob, |
2233 | - And(ProductJob.job_type == cls.class_job_type, |
2234 | - ProductJob.job_id.is_in(Job.ready_jobs))) |
2235 | - return (cls(job) for job in jobs) |
2236 | - |
2237 | - @property |
2238 | - def log_name(self): |
2239 | - return self.__class__.__name__ |
2240 | - |
2241 | - def getOopsVars(self): |
2242 | - """See `IRunnableJob`.""" |
2243 | - vars = BaseRunnableJob.getOopsVars(self) |
2244 | - vars.extend([ |
2245 | - ('product', self.context.product.name), |
2246 | - ]) |
2247 | - return vars |
2248 | ->>>>>>> MERGE-SOURCE |
2249 | |
2250 | === modified file 'lib/lp/registry/services/sharingservice.py' |
2251 | --- lib/lp/registry/services/sharingservice.py 2012-03-29 14:36:36 +0000 |
2252 | +++ lib/lp/registry/services/sharingservice.py 2012-03-26 20:49:13 +0000 |
2253 | @@ -27,14 +27,9 @@ |
2254 | ) |
2255 | from lp.registry.interfaces.product import IProduct |
2256 | from lp.registry.interfaces.projectgroup import IProjectGroup |
2257 | -<<<<<<< TREE |
2258 | from lp.registry.interfaces.sharingservice import ISharingService |
2259 | from lp.registry.model.person import Person |
2260 | from lp.services.features import getFeatureFlag |
2261 | -======= |
2262 | -from lp.registry.model.person import Person |
2263 | -from lp.services.features import getFeatureFlag |
2264 | ->>>>>>> MERGE-SOURCE |
2265 | from lp.services.webapp.authorization import available_with_permission |
2266 | |
2267 | |
2268 | @@ -52,7 +47,6 @@ |
2269 | """See `IService`.""" |
2270 | return 'sharing' |
2271 | |
2272 | -<<<<<<< TREE |
2273 | @property |
2274 | def write_enabled(self): |
2275 | return bool(getFeatureFlag( |
2276 | @@ -64,13 +58,6 @@ |
2277 | return [a for a in |
2278 | flat_source.findArtifactsByGrantee(person, policies)] |
2279 | |
2280 | -======= |
2281 | - @property |
2282 | - def write_enabled(self): |
2283 | - return bool(getFeatureFlag( |
2284 | - 'disclosure.enhanced_sharing.writable')) |
2285 | - |
2286 | ->>>>>>> MERGE-SOURCE |
2287 | def getInformationTypes(self, pillar): |
2288 | """See `ISharingService`.""" |
2289 | allowed_types = [ |
2290 | @@ -115,7 +102,6 @@ |
2291 | @available_with_permission('launchpad.Driver', 'pillar') |
2292 | def getPillarSharees(self, pillar): |
2293 | """See `ISharingService`.""" |
2294 | -<<<<<<< TREE |
2295 | policies = getUtility(IAccessPolicySource).findByPillar([pillar]) |
2296 | ap_grant_flat = getUtility(IAccessPolicyGrantFlatSource) |
2297 | # XXX 2012-03-22 wallyworld bug 961836 |
2298 | @@ -135,31 +121,8 @@ |
2299 | |
2300 | def jsonShareeData(self, grant_permissions): |
2301 | """See `ISharingService`.""" |
2302 | -======= |
2303 | - policies = getUtility(IAccessPolicySource).findByPillar([pillar]) |
2304 | - ap_grant_flat = getUtility(IAccessPolicyGrantFlatSource) |
2305 | - # XXX 2012-03-22 wallyworld bug 961836 |
2306 | - # We want to use person_sort_key(Person.displayname, Person.name) but |
2307 | - # StormRangeFactory doesn't support that yet. |
2308 | - grantees = ap_grant_flat.findGranteesByPolicy( |
2309 | - policies).order_by(Person.displayname, Person.name) |
2310 | - return grantees |
2311 | - |
2312 | - @available_with_permission('launchpad.Driver', 'pillar') |
2313 | - def getPillarShareeData(self, pillar, grantees=None): |
2314 | - """See `ISharingService`.""" |
2315 | - policies = getUtility(IAccessPolicySource).findByPillar([pillar]) |
2316 | - ap_grant_flat = getUtility(IAccessPolicyGrantFlatSource) |
2317 | - # XXX 2012-03-22 wallyworld bug 961836 |
2318 | - # We want to use person_sort_key(Person.displayname, Person.name) but |
2319 | - # StormRangeFactory doesn't support that yet. |
2320 | - grant_permissions = ap_grant_flat.findGranteePermissionsByPolicy( |
2321 | - policies, grantees).order_by(Person.displayname, Person.name) |
2322 | - |
2323 | ->>>>>>> MERGE-SOURCE |
2324 | result = [] |
2325 | request = get_current_web_service_request() |
2326 | -<<<<<<< TREE |
2327 | browser_request = IWebBrowserOriginatingRequest(request) |
2328 | for (grantee, permissions) in grant_permissions: |
2329 | result.append({ |
2330 | @@ -171,22 +134,6 @@ |
2331 | 'permissions': dict( |
2332 | (policy.type.name, permission.name) |
2333 | for (policy, permission) in permissions.iteritems())}) |
2334 | -======= |
2335 | - browser_request = IWebBrowserOriginatingRequest(request) |
2336 | - for (grantee, policy, sharing_permission) in grant_permissions: |
2337 | - if not grantee.id in person_by_id: |
2338 | - person_data = { |
2339 | - 'name': grantee.name, |
2340 | - 'meta': 'team' if grantee.is_team else 'person', |
2341 | - 'display_name': grantee.displayname, |
2342 | - 'self_link': absoluteURL(grantee, request), |
2343 | - 'permissions': {}} |
2344 | - person_data['web_link'] = absoluteURL(grantee, browser_request) |
2345 | - person_by_id[grantee.id] = person_data |
2346 | - result.append(person_data) |
2347 | - person_data = person_by_id[grantee.id] |
2348 | - person_data['permissions'][policy.type.name] = sharing_permission |
2349 | ->>>>>>> MERGE-SOURCE |
2350 | return result |
2351 | |
2352 | @available_with_permission('launchpad.Edit', 'pillar') |
2353 | @@ -251,7 +198,6 @@ |
2354 | self.deletePillarSharee(pillar, sharee, info_types_for_nothing) |
2355 | |
2356 | # Return sharee data to the caller. |
2357 | -<<<<<<< TREE |
2358 | ap_grant_flat = getUtility(IAccessPolicyGrantFlatSource) |
2359 | grant_permissions = list(ap_grant_flat.findGranteePermissionsByPolicy( |
2360 | all_pillar_policies, [sharee])) |
2361 | @@ -259,12 +205,6 @@ |
2362 | return None |
2363 | [sharee] = self.jsonShareeData(grant_permissions) |
2364 | return sharee |
2365 | -======= |
2366 | - sharees = self.getPillarShareeData(pillar, [sharee]) |
2367 | - if not sharees: |
2368 | - return None |
2369 | - return sharees[0] |
2370 | ->>>>>>> MERGE-SOURCE |
2371 | |
2372 | @available_with_permission('launchpad.Edit', 'pillar') |
2373 | def deletePillarSharee(self, pillar, sharee, |
2374 | @@ -296,18 +236,9 @@ |
2375 | |
2376 | # Second delete any access artifact grants. |
2377 | ap_grant_flat = getUtility(IAccessPolicyGrantFlatSource) |
2378 | -<<<<<<< TREE |
2379 | to_delete = list(ap_grant_flat.findArtifactsByGrantee( |
2380 | sharee, pillar_policies)) |
2381 | if len(to_delete) > 0: |
2382 | accessartifact_grant_source = getUtility( |
2383 | IAccessArtifactGrantSource) |
2384 | accessartifact_grant_source.revokeByArtifact(to_delete) |
2385 | -======= |
2386 | - to_delete = ap_grant_flat.findArtifactsByGrantee( |
2387 | - sharee, pillar_policies) |
2388 | - if to_delete.count() > 0: |
2389 | - accessartifact_grant_source = getUtility( |
2390 | - IAccessArtifactGrantSource) |
2391 | - accessartifact_grant_source.revokeByArtifact(to_delete) |
2392 | ->>>>>>> MERGE-SOURCE |
2393 | |
2394 | === modified file 'lib/lp/registry/services/tests/test_sharingservice.py' |
2395 | --- lib/lp/registry/services/tests/test_sharingservice.py 2012-03-29 14:36:36 +0000 |
2396 | +++ lib/lp/registry/services/tests/test_sharingservice.py 2012-03-23 04:00:29 +0000 |
2397 | @@ -123,7 +123,6 @@ |
2398 | distro, |
2399 | [InformationType.EMBARGOEDSECURITY, InformationType.USERDATA]) |
2400 | |
2401 | -<<<<<<< TREE |
2402 | def test_jsonShareeData(self): |
2403 | # jsonShareeData returns the expected data. |
2404 | product = self.factory.makeProduct() |
2405 | @@ -246,123 +245,6 @@ |
2406 | self._assert_getPillarShareeDataUnauthorized(product) |
2407 | |
2408 | def _assert_getPillarSharees(self, pillar): |
2409 | -======= |
2410 | - def _assert_getPillarShareeData(self, pillar): |
2411 | - # getPillarShareeData returns the expected data. |
2412 | - access_policy = self.factory.makeAccessPolicy( |
2413 | - pillar=pillar, |
2414 | - type=InformationType.PROPRIETARY) |
2415 | - grantee = self.factory.makePerson() |
2416 | - # Make access policy grant so that 'All' is returned. |
2417 | - self.factory.makeAccessPolicyGrant(access_policy, grantee) |
2418 | - # Make access artifact grants so that 'Some' is returned. |
2419 | - artifact_grant = self.factory.makeAccessArtifactGrant() |
2420 | - self.factory.makeAccessPolicyArtifact( |
2421 | - artifact=artifact_grant.abstract_artifact, policy=access_policy) |
2422 | - |
2423 | - sharees = self.service.getPillarShareeData(pillar) |
2424 | - expected_sharees = [ |
2425 | - self._makeShareeData( |
2426 | - grantee, |
2427 | - [(InformationType.PROPRIETARY, SharingPermission.ALL)]), |
2428 | - self._makeShareeData( |
2429 | - artifact_grant.grantee, |
2430 | - [(InformationType.PROPRIETARY, SharingPermission.SOME)])] |
2431 | - self.assertContentEqual(expected_sharees, sharees) |
2432 | - |
2433 | - def test_getProductShareeData(self): |
2434 | - # Users with launchpad.Driver can view sharees. |
2435 | - driver = self.factory.makePerson() |
2436 | - product = self.factory.makeProduct(driver=driver) |
2437 | - login_person(driver) |
2438 | - self._assert_getPillarShareeData(product) |
2439 | - |
2440 | - def test_getDistroShareeData(self): |
2441 | - # Users with launchpad.Driver can view sharees. |
2442 | - driver = self.factory.makePerson() |
2443 | - distro = self.factory.makeDistribution(driver=driver) |
2444 | - login_person(driver) |
2445 | - self._assert_getPillarShareeData(distro) |
2446 | - |
2447 | - def test_getPillarShareeDataQueryCount(self): |
2448 | - # getPillarShareeData only should use 2 queries regardless of how many |
2449 | - # sharees are returned. |
2450 | - driver = self.factory.makePerson() |
2451 | - product = self.factory.makeProduct(driver=driver) |
2452 | - login_person(driver) |
2453 | - access_policy = self.factory.makeAccessPolicy( |
2454 | - pillar=product, |
2455 | - type=InformationType.PROPRIETARY) |
2456 | - |
2457 | - def makeGrants(): |
2458 | - grantee = self.factory.makePerson() |
2459 | - # Make access policy grant so that 'All' is returned. |
2460 | - self.factory.makeAccessPolicyGrant(access_policy, grantee) |
2461 | - # Make access artifact grants so that 'Some' is returned. |
2462 | - artifact_grant = self.factory.makeAccessArtifactGrant() |
2463 | - self.factory.makeAccessPolicyArtifact( |
2464 | - artifact=artifact_grant.abstract_artifact, |
2465 | - policy=access_policy) |
2466 | - |
2467 | - # Make some grants and check the count. |
2468 | - for x in range(5): |
2469 | - makeGrants() |
2470 | - with StormStatementRecorder() as recorder: |
2471 | - sharees = self.service.getPillarShareeData(product) |
2472 | - self.assertEqual(10, len(sharees)) |
2473 | - self.assertThat(recorder, HasQueryCount(Equals(2))) |
2474 | - # Make some more grants and check again. |
2475 | - for x in range(5): |
2476 | - makeGrants() |
2477 | - with StormStatementRecorder() as recorder: |
2478 | - sharees = self.service.getPillarShareeData(product) |
2479 | - self.assertEqual(20, len(sharees)) |
2480 | - self.assertThat(recorder, HasQueryCount(Equals(2))) |
2481 | - |
2482 | - def test_getPillarShareeData_filter_grantees(self): |
2483 | - # getPillarShareeData only returns grantees in the specified list. |
2484 | - driver = self.factory.makePerson() |
2485 | - pillar = self.factory.makeProduct(driver=driver) |
2486 | - login_person(driver) |
2487 | - access_policy = self.factory.makeAccessPolicy( |
2488 | - pillar=pillar, |
2489 | - type=InformationType.PROPRIETARY) |
2490 | - grantee_in_result = self.factory.makePerson() |
2491 | - grantee_not_in_result = self.factory.makePerson() |
2492 | - self.factory.makeAccessPolicyGrant(access_policy, grantee_in_result) |
2493 | - self.factory.makeAccessPolicyGrant( |
2494 | - access_policy, grantee_not_in_result) |
2495 | - |
2496 | - sharees = self.service.getPillarShareeData(pillar, [grantee_in_result]) |
2497 | - expected_sharees = [ |
2498 | - self._makeShareeData( |
2499 | - grantee_in_result, |
2500 | - [(InformationType.PROPRIETARY, SharingPermission.ALL)])] |
2501 | - self.assertContentEqual(expected_sharees, sharees) |
2502 | - |
2503 | - def _assert_getPillarShareeDataUnauthorized(self, pillar): |
2504 | - # getPillarShareeData raises an Unauthorized exception if the user is |
2505 | - # not permitted to do so. |
2506 | - access_policy = self.factory.makeAccessPolicy(pillar=pillar) |
2507 | - grantee = self.factory.makePerson() |
2508 | - self.factory.makeAccessPolicyGrant(access_policy, grantee) |
2509 | - self.assertRaises( |
2510 | - Unauthorized, self.service.getPillarShareeData, pillar) |
2511 | - |
2512 | - def test_getPillarShareeDataAnonymous(self): |
2513 | - # Anonymous users are not allowed. |
2514 | - product = self.factory.makeProduct() |
2515 | - login(ANONYMOUS) |
2516 | - self._assert_getPillarShareeDataUnauthorized(product) |
2517 | - |
2518 | - def test_getPillarShareeDataAnyone(self): |
2519 | - # Unauthorized users are not allowed. |
2520 | - product = self.factory.makeProduct() |
2521 | - login_person(self.factory.makePerson()) |
2522 | - self._assert_getPillarShareeDataUnauthorized(product) |
2523 | - |
2524 | - def _assert_getPillarSharees(self, pillar): |
2525 | ->>>>>>> MERGE-SOURCE |
2526 | # getPillarSharees returns the expected data. |
2527 | access_policy = self.factory.makeAccessPolicy( |
2528 | pillar=pillar, |
2529 | @@ -370,7 +252,6 @@ |
2530 | grantee = self.factory.makePerson() |
2531 | # Make access policy grant so that 'All' is returned. |
2532 | self.factory.makeAccessPolicyGrant(access_policy, grantee) |
2533 | -<<<<<<< TREE |
2534 | # Make access artifact grants so that 'Some' is returned. |
2535 | artifact_grant = self.factory.makeAccessArtifactGrant() |
2536 | self.factory.makeAccessPolicyArtifact( |
2537 | @@ -381,16 +262,6 @@ |
2538 | (grantee, {access_policy: SharingPermission.ALL}), |
2539 | (artifact_grant.grantee, {access_policy: SharingPermission.SOME})] |
2540 | self.assertContentEqual(expected_sharees, sharees) |
2541 | -======= |
2542 | - # Make access artifact grants so that 'Some' is returned. |
2543 | - artifact_grant = self.factory.makeAccessArtifactGrant() |
2544 | - self.factory.makeAccessPolicyArtifact( |
2545 | - artifact=artifact_grant.abstract_artifact, policy=access_policy) |
2546 | - |
2547 | - sharees = self.service.getPillarSharees(pillar) |
2548 | - expected_sharees = [grantee, artifact_grant.grantee] |
2549 | - self.assertContentEqual(expected_sharees, sharees) |
2550 | ->>>>>>> MERGE-SOURCE |
2551 | |
2552 | def test_getProductSharees(self): |
2553 | # Users with launchpad.Driver can view sharees. |
2554 | @@ -483,14 +354,7 @@ |
2555 | (InformationType.EMBARGOEDSECURITY, SharingPermission.ALL), |
2556 | (InformationType.USERDATA, SharingPermission.SOME)] |
2557 | expected_sharee_data = self._makeShareeData( |
2558 | -<<<<<<< TREE |
2559 | - sharee, expected_permissions) |
2560 | -======= |
2561 | - sharee, expected_permissions) |
2562 | - self.assertEqual(expected_sharee_data, sharee_data) |
2563 | - # Check that getPillarShareeData returns what we expect. |
2564 | - [sharee_data] = self.service.getPillarShareeData(pillar) |
2565 | ->>>>>>> MERGE-SOURCE |
2566 | + sharee, expected_permissions) |
2567 | self.assertEqual(expected_sharee_data, sharee_data) |
2568 | # Check that getPillarSharees returns what we expect. |
2569 | expected_sharee_grants = [ |
2570 | @@ -604,7 +468,6 @@ |
2571 | if types_to_delete is not None: |
2572 | expected_information_types = ( |
2573 | set(information_types).difference(types_to_delete)) |
2574 | -<<<<<<< TREE |
2575 | expected_policies = [ |
2576 | access_policy for access_policy in access_policies |
2577 | if access_policy.type in expected_information_types] |
2578 | @@ -614,20 +477,9 @@ |
2579 | # Add the expected data for the other sharee. |
2580 | another_person_data = ( |
2581 | another, {access_policies[0]: SharingPermission.ALL}) |
2582 | -======= |
2583 | - remaining_grantee_person_data = self._makeShareeData( |
2584 | - grantee, |
2585 | - [(info_type, SharingPermission.ALL) |
2586 | - for info_type in expected_information_types]) |
2587 | - |
2588 | - expected_data.append(remaining_grantee_person_data) |
2589 | - # Add the data for the other sharee. |
2590 | - another_person_data = self._makeShareeData( |
2591 | - another, [(information_types[0], SharingPermission.ALL)]) |
2592 | ->>>>>>> MERGE-SOURCE |
2593 | expected_data.append(another_person_data) |
2594 | self.assertContentEqual( |
2595 | - expected_data, self.service.getPillarShareeData(pillar)) |
2596 | + expected_data, self.service.getPillarSharees(pillar)) |
2597 | |
2598 | def test_deleteProductShareeAll(self): |
2599 | # Users with launchpad.Edit can delete all access for a sharee. |
2600 | |
2601 | === modified file 'lib/lp/registry/subscribers.py' |
2602 | --- lib/lp/registry/subscribers.py 2012-03-29 14:36:36 +0000 |
2603 | +++ lib/lp/registry/subscribers.py 2012-03-22 23:21:24 +0000 |
2604 | @@ -8,281 +8,140 @@ |
2605 | 'product_licenses_modified', |
2606 | ] |
2607 | |
2608 | -<<<<<<< TREE |
2609 | -from datetime import datetime |
2610 | -import textwrap |
2611 | - |
2612 | -import pytz |
2613 | -from zope.security.proxy import removeSecurityProxy |
2614 | - |
2615 | -from lp.registry.interfaces.person import IPerson |
2616 | -from lp.registry.interfaces.product import License |
2617 | -from lp.services.config import config |
2618 | -from lp.services.mail.helpers import get_email_template |
2619 | -from lp.services.mail.sendmail import ( |
2620 | - format_address, |
2621 | - simple_sendmail, |
2622 | - ) |
2623 | -from lp.services.webapp.menu import structured |
2624 | -from lp.services.webapp.publisher import ( |
2625 | - canonical_url, |
2626 | - get_current_browser_request, |
2627 | - ) |
2628 | - |
2629 | - |
2630 | -def product_licenses_modified(product, event): |
2631 | - """Send a notification if licenses changed and a license is special.""" |
2632 | - if not event.edited_fields: |
2633 | - return |
2634 | - licenses_changed = 'licenses' in event.edited_fields |
2635 | - needs_notification = LicenseNotification.needs_notification(product) |
2636 | - if licenses_changed and needs_notification: |
2637 | - user = IPerson(event.user) |
2638 | - notification = LicenseNotification(product, user) |
2639 | - notification.send() |
2640 | - notification.display() |
2641 | - |
2642 | - |
2643 | -class LicenseNotification: |
2644 | - """Send notification about special licenses to the user.""" |
2645 | - |
2646 | - def __init__(self, product, user): |
2647 | - self.product = product |
2648 | - self.user = user |
2649 | - |
2650 | - @staticmethod |
2651 | - def needs_notification(product): |
2652 | - licenses = list(product.licenses) |
2653 | - return ( |
2654 | - License.OTHER_PROPRIETARY in licenses |
2655 | - or License.OTHER_OPEN_SOURCE in licenses |
2656 | - or [License.DONT_KNOW] == licenses) |
2657 | - |
2658 | - def getTemplateName(self): |
2659 | - """Return the name of the email template for the licensing case.""" |
2660 | - licenses = list(self.product.licenses) |
2661 | - if [License.DONT_KNOW] == licenses: |
2662 | - template_name = 'product-license-dont-know.txt' |
2663 | - elif License.OTHER_PROPRIETARY in licenses: |
2664 | - template_name = 'product-license-other-proprietary.txt' |
2665 | - else: |
2666 | - template_name = 'product-license-other-open-source.txt' |
2667 | - return template_name |
2668 | - |
2669 | - def getCommercialUseMessage(self): |
2670 | - """Return a message explaining the current commercial subscription.""" |
2671 | - commercial_subscription = self.product.commercial_subscription |
2672 | - if commercial_subscription is None: |
2673 | - return '' |
2674 | - iso_date = commercial_subscription.date_expires.date().isoformat() |
2675 | - if not self.product.has_current_commercial_subscription: |
2676 | - message = "%s's commercial subscription expired on %s." |
2677 | - elif 'complimentary' in commercial_subscription.sales_system_id: |
2678 | - message = ( |
2679 | - "%s's complimentary commercial subscription expires on %s.") |
2680 | - else: |
2681 | - message = "%s's commercial subscription expires on %s." |
2682 | - message = message % (self.product.displayname, iso_date) |
2683 | - return textwrap.fill(message, 72) |
2684 | - |
2685 | - def send(self): |
2686 | - """Send a message to the user about the product's license.""" |
2687 | - if not self.needs_notification(self.product): |
2688 | - # The project has a common license. |
2689 | - return False |
2690 | - user_address = format_address( |
2691 | - self.user.displayname, self.user.preferredemail.email) |
2692 | - from_address = format_address( |
2693 | - "Launchpad", config.canonical.noreply_from_address) |
2694 | - commercial_address = format_address( |
2695 | - 'Commercial', 'commercial@launchpad.net') |
2696 | - substitutions = dict( |
2697 | - user_displayname=self.user.displayname, |
2698 | - user_name=self.user.name, |
2699 | - product_name=self.product.name, |
2700 | - product_url=canonical_url(self.product), |
2701 | - commercial_use_expiration=self.getCommercialUseMessage(), |
2702 | - ) |
2703 | - # Email the user about license policy. |
2704 | - subject = ( |
2705 | - "License information for %(product_name)s " |
2706 | - "in Launchpad" % substitutions) |
2707 | - template = get_email_template( |
2708 | - self.getTemplateName(), app='registry') |
2709 | - message = template % substitutions |
2710 | - simple_sendmail( |
2711 | - from_address, user_address, |
2712 | - subject, message, headers={'Reply-To': commercial_address}) |
2713 | - # Inform that Launchpad recognized the license change. |
2714 | - self._addLicenseChangeToReviewWhiteboard() |
2715 | - return True |
2716 | - |
2717 | - def display(self): |
2718 | - """Show a message in a browser page about the product's license.""" |
2719 | - request = get_current_browser_request() |
2720 | - message = self.getCommercialUseMessage() |
2721 | - if request is None or message == '': |
2722 | - return False |
2723 | - safe_message = structured( |
2724 | - '%s<br />Learn more about ' |
2725 | - '<a href="https://help.launchpad.net/CommercialHosting">' |
2726 | - 'commercial subscriptions</a>', message) |
2727 | - request.response.addNotification(safe_message) |
2728 | - return True |
2729 | - |
2730 | - @staticmethod |
2731 | - def _formatDate(now=None): |
2732 | - """Return the date formatted for messages.""" |
2733 | - if now is None: |
2734 | - now = datetime.now(tz=pytz.UTC) |
2735 | - return now.strftime('%Y-%m-%d') |
2736 | - |
2737 | - def _addLicenseChangeToReviewWhiteboard(self): |
2738 | - """Update the whiteboard for the reviewer's benefit.""" |
2739 | - now = self._formatDate() |
2740 | - whiteboard = 'User notified of license policy on %s.' % now |
2741 | - naked_product = removeSecurityProxy(self.product) |
2742 | - if naked_product.reviewer_whiteboard is None: |
2743 | - naked_product.reviewer_whiteboard = whiteboard |
2744 | - else: |
2745 | - naked_product.reviewer_whiteboard += '\n' + whiteboard |
2746 | -======= |
2747 | -from datetime import datetime |
2748 | -import textwrap |
2749 | - |
2750 | -import pytz |
2751 | - |
2752 | -from zope.security.proxy import removeSecurityProxy |
2753 | - |
2754 | -from lp.registry.interfaces.person import IPerson |
2755 | -from lp.registry.interfaces.product import License |
2756 | -from lp.services.config import config |
2757 | -from lp.services.mail.helpers import get_email_template |
2758 | -from lp.services.mail.sendmail import ( |
2759 | - format_address, |
2760 | - simple_sendmail, |
2761 | - ) |
2762 | -from lp.services.webapp.menu import structured |
2763 | -from lp.services.webapp.publisher import ( |
2764 | - canonical_url, |
2765 | - get_current_browser_request, |
2766 | - ) |
2767 | - |
2768 | - |
2769 | -def product_licenses_modified(product, event): |
2770 | - """Send a notification if licenses changed and a license is special.""" |
2771 | - if not event.edited_fields: |
2772 | - return |
2773 | - licenses_changed = 'licenses' in event.edited_fields |
2774 | - needs_notification = LicenseNotification.needs_notification(product) |
2775 | - if licenses_changed and needs_notification: |
2776 | - user = IPerson(event.user) |
2777 | - notification = LicenseNotification(product, user) |
2778 | - notification.send() |
2779 | - notification.display() |
2780 | - |
2781 | - |
2782 | -class LicenseNotification: |
2783 | - """Send notification about special licenses to the user.""" |
2784 | - |
2785 | - def __init__(self, product, user): |
2786 | - self.product = product |
2787 | - self.user = user |
2788 | - |
2789 | - @staticmethod |
2790 | - def needs_notification(product): |
2791 | - licenses = list(product.licenses) |
2792 | - return ( |
2793 | - License.OTHER_PROPRIETARY in licenses |
2794 | - or License.OTHER_OPEN_SOURCE in licenses |
2795 | - or [License.DONT_KNOW] == licenses) |
2796 | - |
2797 | - def getTemplateName(self): |
2798 | - """Return the name of the email template for the licensing case.""" |
2799 | - licenses = list(self.product.licenses) |
2800 | - if [License.DONT_KNOW] == licenses: |
2801 | - template_name = 'product-license-dont-know.txt' |
2802 | - elif License.OTHER_PROPRIETARY in licenses: |
2803 | - template_name = 'product-license-other-proprietary.txt' |
2804 | - else: |
2805 | - template_name = 'product-license-other-open-source.txt' |
2806 | - return template_name |
2807 | - |
2808 | - def getCommercialUseMessage(self): |
2809 | - """Return a message explaining the current commercial subscription.""" |
2810 | - commercial_subscription = self.product.commercial_subscription |
2811 | - if commercial_subscription is None: |
2812 | - return '' |
2813 | - iso_date = commercial_subscription.date_expires.date().isoformat() |
2814 | - if not self.product.has_current_commercial_subscription: |
2815 | - message = "%s's commercial subscription expired on %s." |
2816 | - elif 'complimentary' in commercial_subscription.sales_system_id: |
2817 | - message = ( |
2818 | - "%s's complimentary commercial subscription expires on %s.") |
2819 | - else: |
2820 | - message = "%s's commercial subscription expires on %s." |
2821 | - message = message % (self.product.displayname, iso_date) |
2822 | - return textwrap.fill(message, 72) |
2823 | - |
2824 | - def send(self): |
2825 | - """Send a message to the user about the product's license.""" |
2826 | - if not self.needs_notification(self.product): |
2827 | - # The project has a common license. |
2828 | - return False |
2829 | - user_address = format_address( |
2830 | - self.user.displayname, self.user.preferredemail.email) |
2831 | - from_address = format_address( |
2832 | - "Launchpad", config.canonical.noreply_from_address) |
2833 | - commercial_address = format_address( |
2834 | - 'Commercial', 'commercial@launchpad.net') |
2835 | - substitutions = dict( |
2836 | - user_displayname=self.user.displayname, |
2837 | - user_name=self.user.name, |
2838 | - product_name=self.product.name, |
2839 | - product_url=canonical_url(self.product), |
2840 | - commercial_use_expiration=self.getCommercialUseMessage(), |
2841 | - ) |
2842 | - # Email the user about license policy. |
2843 | - subject = ( |
2844 | - "License information for %(product_name)s " |
2845 | - "in Launchpad" % substitutions) |
2846 | - template = get_email_template( |
2847 | - self.getTemplateName(), app='registry') |
2848 | - message = template % substitutions |
2849 | - simple_sendmail( |
2850 | - from_address, user_address, |
2851 | - subject, message, headers={'Reply-To': commercial_address}) |
2852 | - # Inform that Launchpad recognized the license change. |
2853 | - self._addLicenseChangeToReviewWhiteboard() |
2854 | - return True |
2855 | - |
2856 | - def display(self): |
2857 | - """Show a message in a browser page about the product's license.""" |
2858 | - request = get_current_browser_request() |
2859 | - message = self.getCommercialUseMessage() |
2860 | - if request is None or message == '': |
2861 | - return False |
2862 | - safe_message = structured( |
2863 | - '%s<br />Learn more about ' |
2864 | - '<a href="https://help.launchpad.net/CommercialHosting">' |
2865 | - 'commercial subscriptions</a>', message) |
2866 | - request.response.addNotification(safe_message) |
2867 | - return True |
2868 | - |
2869 | - @staticmethod |
2870 | - def _formatDate(now=None): |
2871 | - """Return the date formatted for messages.""" |
2872 | - if now is None: |
2873 | - now = datetime.now(tz=pytz.UTC) |
2874 | - return now.strftime('%Y-%m-%d') |
2875 | - |
2876 | - def _addLicenseChangeToReviewWhiteboard(self): |
2877 | - """Update the whiteboard for the reviewer's benefit.""" |
2878 | - now = self._formatDate() |
2879 | - whiteboard = 'User notified of license policy on %s.' % now |
2880 | - naked_product = removeSecurityProxy(self.product) |
2881 | - if naked_product.reviewer_whiteboard is None: |
2882 | - naked_product.reviewer_whiteboard = whiteboard |
2883 | - else: |
2884 | - naked_product.reviewer_whiteboard += '\n' + whiteboard |
2885 | ->>>>>>> MERGE-SOURCE |
2886 | +from datetime import datetime |
2887 | +import textwrap |
2888 | + |
2889 | +import pytz |
2890 | +from zope.security.proxy import removeSecurityProxy |
2891 | + |
2892 | +from lp.registry.interfaces.person import IPerson |
2893 | +from lp.registry.interfaces.product import License |
2894 | +from lp.services.config import config |
2895 | +from lp.services.mail.helpers import get_email_template |
2896 | +from lp.services.mail.sendmail import ( |
2897 | + format_address, |
2898 | + simple_sendmail, |
2899 | + ) |
2900 | +from lp.services.webapp.menu import structured |
2901 | +from lp.services.webapp.publisher import ( |
2902 | + canonical_url, |
2903 | + get_current_browser_request, |
2904 | + ) |
2905 | + |
2906 | + |
2907 | +def product_licenses_modified(product, event): |
2908 | + """Send a notification if licenses changed and a license is special.""" |
2909 | + if not event.edited_fields: |
2910 | + return |
2911 | + licenses_changed = 'licenses' in event.edited_fields |
2912 | + needs_notification = LicenseNotification.needs_notification(product) |
2913 | + if licenses_changed and needs_notification: |
2914 | + user = IPerson(event.user) |
2915 | + notification = LicenseNotification(product, user) |
2916 | + notification.send() |
2917 | + notification.display() |
2918 | + |
2919 | + |
2920 | +class LicenseNotification: |
2921 | + """Send notification about special licenses to the user.""" |
2922 | + |
2923 | + def __init__(self, product, user): |
2924 | + self.product = product |
2925 | + self.user = user |
2926 | + |
2927 | + @staticmethod |
2928 | + def needs_notification(product): |
2929 | + licenses = list(product.licenses) |
2930 | + return ( |
2931 | + License.OTHER_PROPRIETARY in licenses |
2932 | + or License.OTHER_OPEN_SOURCE in licenses |
2933 | + or [License.DONT_KNOW] == licenses) |
2934 | + |
2935 | + def getTemplateName(self): |
2936 | + """Return the name of the email template for the licensing case.""" |
2937 | + licenses = list(self.product.licenses) |
2938 | + if [License.DONT_KNOW] == licenses: |
2939 | + template_name = 'product-license-dont-know.txt' |
2940 | + elif License.OTHER_PROPRIETARY in licenses: |
2941 | + template_name = 'product-license-other-proprietary.txt' |
2942 | + else: |
2943 | + template_name = 'product-license-other-open-source.txt' |
2944 | + return template_name |
2945 | + |
2946 | + def getCommercialUseMessage(self): |
2947 | + """Return a message explaining the current commercial subscription.""" |
2948 | + commercial_subscription = self.product.commercial_subscription |
2949 | + if commercial_subscription is None: |
2950 | + return '' |
2951 | + iso_date = commercial_subscription.date_expires.date().isoformat() |
2952 | + if not self.product.has_current_commercial_subscription: |
2953 | + message = "%s's commercial subscription expired on %s." |
2954 | + elif 'complimentary' in commercial_subscription.sales_system_id: |
2955 | + message = ( |
2956 | + "%s's complimentary commercial subscription expires on %s.") |
2957 | + else: |
2958 | + message = "%s's commercial subscription expires on %s." |
2959 | + message = message % (self.product.displayname, iso_date) |
2960 | + return textwrap.fill(message, 72) |
2961 | + |
2962 | + def send(self): |
2963 | + """Send a message to the user about the product's license.""" |
2964 | + if not self.needs_notification(self.product): |
2965 | + # The project has a common license. |
2966 | + return False |
2967 | + user_address = format_address( |
2968 | + self.user.displayname, self.user.preferredemail.email) |
2969 | + from_address = format_address( |
2970 | + "Launchpad", config.canonical.noreply_from_address) |
2971 | + commercial_address = format_address( |
2972 | + 'Commercial', 'commercial@launchpad.net') |
2973 | + substitutions = dict( |
2974 | + user_displayname=self.user.displayname, |
2975 | + user_name=self.user.name, |
2976 | + product_name=self.product.name, |
2977 | + product_url=canonical_url(self.product), |
2978 | + commercial_use_expiration=self.getCommercialUseMessage(), |
2979 | + ) |
2980 | + # Email the user about license policy. |
2981 | + subject = ( |
2982 | + "License information for %(product_name)s " |
2983 | + "in Launchpad" % substitutions) |
2984 | + template = get_email_template( |
2985 | + self.getTemplateName(), app='registry') |
2986 | + message = template % substitutions |
2987 | + simple_sendmail( |
2988 | + from_address, user_address, |
2989 | + subject, message, headers={'Reply-To': commercial_address}) |
2990 | + # Inform that Launchpad recognized the license change. |
2991 | + self._addLicenseChangeToReviewWhiteboard() |
2992 | + return True |
2993 | + |
2994 | + def display(self): |
2995 | + """Show a message in a browser page about the product's license.""" |
2996 | + request = get_current_browser_request() |
2997 | + message = self.getCommercialUseMessage() |
2998 | + if request is None or message == '': |
2999 | + return False |
3000 | + safe_message = structured( |
3001 | + '%s<br />Learn more about ' |
3002 | + '<a href="https://help.launchpad.net/CommercialHosting">' |
3003 | + 'commercial subscriptions</a>', message) |
3004 | + request.response.addNotification(safe_message) |
3005 | + return True |
3006 | + |
3007 | + @staticmethod |
3008 | + def _formatDate(now=None): |
3009 | + """Return the date formatted for messages.""" |
3010 | + if now is None: |
3011 | + now = datetime.now(tz=pytz.UTC) |
3012 | + return now.strftime('%Y-%m-%d') |
3013 | + |
3014 | + def _addLicenseChangeToReviewWhiteboard(self): |
3015 | + """Update the whiteboard for the reviewer's benefit.""" |
3016 | + now = self._formatDate() |
3017 | + whiteboard = 'User notified of license policy on %s.' % now |
3018 | + naked_product = removeSecurityProxy(self.product) |
3019 | + if naked_product.reviewer_whiteboard is None: |
3020 | + naked_product.reviewer_whiteboard = whiteboard |
3021 | + else: |
3022 | + naked_product.reviewer_whiteboard += '\n' + whiteboard |
3023 | |
3024 | === modified file 'lib/lp/registry/templates/pillar-sharing-details.pt' |
3025 | --- lib/lp/registry/templates/pillar-sharing-details.pt 2012-03-29 14:36:36 +0000 |
3026 | +++ lib/lp/registry/templates/pillar-sharing-details.pt 2012-03-26 20:49:13 +0000 |
3027 | @@ -1,4 +1,3 @@ |
3028 | -<<<<<<< TREE |
3029 | <html |
3030 | xmlns="http://www.w3.org/1999/xhtml" |
3031 | xmlns:tal="http://xml.zope.org/namespaces/tal" |
3032 | @@ -61,19 +60,3 @@ |
3033 | </div> |
3034 | </body> |
3035 | </html> |
3036 | -======= |
3037 | -<html |
3038 | - xmlns="http://www.w3.org/1999/xhtml" |
3039 | - xmlns:tal="http://xml.zope.org/namespaces/tal" |
3040 | - xmlns:metal="http://xml.zope.org/namespaces/metal" |
3041 | - xmlns:i18n="http://xml.zope.org/namespaces/i18n" |
3042 | - metal:use-macro="view/macro:page/main_only" |
3043 | - i18n:domain="launchpad" |
3044 | -> |
3045 | - |
3046 | -<body> |
3047 | - <div metal:fill-slot="main"> |
3048 | - </div> |
3049 | -</body> |
3050 | -</html> |
3051 | ->>>>>>> MERGE-SOURCE |
3052 | |
3053 | === modified file 'lib/lp/registry/templates/pillar-sharing.pt' |
3054 | --- lib/lp/registry/templates/pillar-sharing.pt 2012-03-29 14:36:36 +0000 |
3055 | +++ lib/lp/registry/templates/pillar-sharing.pt 2012-03-22 09:00:36 +0000 |
3056 | @@ -28,7 +28,6 @@ |
3057 | Proprietary, embargoed security, or user-data information is |
3058 | shared with these users and teams. |
3059 | </p> |
3060 | -<<<<<<< TREE |
3061 | <ul class="horizontal"> |
3062 | <li><a id='add-sharee-link' class='sprite add js-action' href="#">Share |
3063 | with someone</a></li> |
3064 | @@ -41,19 +40,5 @@ |
3065 | |
3066 | </div> |
3067 | |
3068 | -======= |
3069 | - <ul class="horizontal"> |
3070 | - <li><a id='add-sharee-link' class='sprite add js-action' href="#">Share |
3071 | - with someone</a></li> |
3072 | - <li><a id="audit-link" class="sprite info" href='#'>Audit sharing</a></li> |
3073 | - </ul> |
3074 | - |
3075 | - <div tal:define="batch_navigator view/shareeData"> |
3076 | - <tal:shareelisting content="structure batch_navigator/@@+sharee-table-view" /> |
3077 | - </div> |
3078 | - |
3079 | - </div> |
3080 | - |
3081 | ->>>>>>> MERGE-SOURCE |
3082 | </body> |
3083 | </html> |
3084 | |
3085 | === modified file 'lib/lp/registry/tests/test_accesspolicy.py' |
3086 | --- lib/lp/registry/tests/test_accesspolicy.py 2012-03-29 14:36:36 +0000 |
3087 | +++ lib/lp/registry/tests/test_accesspolicy.py 2012-03-23 03:40:17 +0000 |
3088 | @@ -468,7 +468,6 @@ |
3089 | artifact=artifact_grant.abstract_artifact, policy=another_policy) |
3090 | self.assertContentEqual( |
3091 | [policy_grant.grantee, artifact_grant.grantee], |
3092 | -<<<<<<< TREE |
3093 | apgfs.findGranteesByPolicy([ |
3094 | policy, another_policy, policy_with_no_grantees])) |
3095 | |
3096 | @@ -537,60 +536,6 @@ |
3097 | [(policy_grant.grantee, {policy: SharingPermission.ALL})], |
3098 | apgfs.findGranteePermissionsByPolicy( |
3099 | [policy], [grantee_in_result])) |
3100 | -======= |
3101 | - apgfs.findGranteesByPolicy([ |
3102 | - policy, another_policy, policy_with_no_grantees])) |
3103 | - |
3104 | - def findGranteePermissionsByPolicy(self): |
3105 | - # findGranteePermissionsByPolicy() returns anyone with a grant for any |
3106 | - # of the policies or the policies' artifacts. |
3107 | - apgfs = getUtility(IAccessPolicyGrantFlatSource) |
3108 | - |
3109 | - # People with grants on the policy show up. |
3110 | - policy_with_no_grantees = self.factory.makeAccessPolicy() |
3111 | - policy = self.factory.makeAccessPolicy() |
3112 | - policy_grant = self.factory.makeAccessPolicyGrant(policy=policy) |
3113 | - self.assertContentEqual( |
3114 | - [(policy_grant.grantee, policy, 'ALL')], |
3115 | - apgfs.findGranteePermissionsByPolicy( |
3116 | - [policy, policy_with_no_grantees])) |
3117 | - |
3118 | - # But not people with grants on artifacts. |
3119 | - artifact_grant = self.factory.makeAccessArtifactGrant() |
3120 | - self.assertContentEqual( |
3121 | - [(policy_grant.grantee, policy, 'ALL')], |
3122 | - apgfs.findGranteePermissionsByPolicy( |
3123 | - [policy, policy_with_no_grantees])) |
3124 | - |
3125 | - # Unless the artifacts are linked to the policy. |
3126 | - another_policy = self.factory.makeAccessPolicy() |
3127 | - self.factory.makeAccessPolicyArtifact( |
3128 | - artifact=artifact_grant.abstract_artifact, policy=another_policy) |
3129 | - self.assertContentEqual( |
3130 | - [(policy_grant.grantee, policy, 'ALL'), |
3131 | - (artifact_grant.grantee, another_policy, 'SOME')], |
3132 | - apgfs.findGranteePermissionsByPolicy([ |
3133 | - policy, another_policy, policy_with_no_grantees])) |
3134 | - |
3135 | - def test_findGranteePermissionsByPolicy_filter_grantees(self): |
3136 | - # findGranteePermissionsByPolicy() returns anyone with a grant for any |
3137 | - # of the policies or the policies' artifacts so long as the grantee is |
3138 | - # in the specified list of grantees. |
3139 | - apgfs = getUtility(IAccessPolicyGrantFlatSource) |
3140 | - |
3141 | - # People with grants on the policy show up. |
3142 | - policy = self.factory.makeAccessPolicy() |
3143 | - grantee_in_result = self.factory.makePerson() |
3144 | - grantee_not_in_result = self.factory.makePerson() |
3145 | - policy_grant = self.factory.makeAccessPolicyGrant( |
3146 | - policy=policy, grantee=grantee_in_result) |
3147 | - self.factory.makeAccessPolicyGrant( |
3148 | - policy=policy, grantee=grantee_not_in_result) |
3149 | - self.assertContentEqual( |
3150 | - [(policy_grant.grantee, policy, 'ALL')], |
3151 | - apgfs.findGranteePermissionsByPolicy( |
3152 | - [policy], [grantee_in_result])) |
3153 | ->>>>>>> MERGE-SOURCE |
3154 | |
3155 | def test_findArtifactsByGrantee(self): |
3156 | # findArtifactsByGrantee() returns the artifacts for grantee for any of |
3157 | |
3158 | === modified file 'lib/lp/registry/tests/test_pillar.py' |
3159 | --- lib/lp/registry/tests/test_pillar.py 2012-03-29 14:36:36 +0000 |
3160 | +++ lib/lp/registry/tests/test_pillar.py 2012-03-22 23:21:24 +0000 |
3161 | @@ -5,20 +5,11 @@ |
3162 | |
3163 | from zope.component import getUtility |
3164 | |
3165 | -<<<<<<< TREE |
3166 | -from lp.registry.interfaces.pillar import ( |
3167 | - IPillarNameSet, |
3168 | - IPillarPerson, |
3169 | - ) |
3170 | -from lp.registry.model.pillar import PillarPerson |
3171 | -======= |
3172 | -from lp.registry.interfaces.pillar import ( |
3173 | - IPillarNameSet, |
3174 | - IPillarPerson, |
3175 | - ) |
3176 | -from lp.registry.model.pillar import PillarPerson |
3177 | - |
3178 | ->>>>>>> MERGE-SOURCE |
3179 | +from lp.registry.interfaces.pillar import ( |
3180 | + IPillarNameSet, |
3181 | + IPillarPerson, |
3182 | + ) |
3183 | +from lp.registry.model.pillar import PillarPerson |
3184 | from lp.testing import ( |
3185 | login, |
3186 | TestCaseWithFactory, |
3187 | |
3188 | === modified file 'lib/lp/registry/tests/test_productjob.py' |
3189 | --- lib/lp/registry/tests/test_productjob.py 2012-03-29 14:36:36 +0000 |
3190 | +++ lib/lp/registry/tests/test_productjob.py 2012-03-24 12:41:36 +0000 |
3191 | @@ -1,4 +1,3 @@ |
3192 | -<<<<<<< TREE |
3193 | # Copyright 2010 Canonical Ltd. This software is licensed under the |
3194 | # GNU Affero General Public License version 3 (see the file LICENSE). |
3195 | |
3196 | @@ -370,191 +369,3 @@ |
3197 | self.assertEqual(subject, notifications[0]['Subject']) |
3198 | self.assertIn( |
3199 | 'Launchpad <noreply@launchpad.net>', notifications[0]['From']) |
3200 | -======= |
3201 | -# Copyright 2010 Canonical Ltd. This software is licensed under the |
3202 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
3203 | - |
3204 | -"""Tests for ProductJobs.""" |
3205 | - |
3206 | -__metaclass__ = type |
3207 | - |
3208 | -from datetime import ( |
3209 | - datetime, |
3210 | - timedelta, |
3211 | - ) |
3212 | - |
3213 | -import pytz |
3214 | - |
3215 | -from zope.interface import ( |
3216 | - classProvides, |
3217 | - implements, |
3218 | - ) |
3219 | -from zope.security.proxy import removeSecurityProxy |
3220 | - |
3221 | -from lp.registry.enums import ProductJobType |
3222 | -from lp.registry.interfaces.productjob import ( |
3223 | - IProductJob, |
3224 | - IProductJobSource, |
3225 | - ) |
3226 | -from lp.registry.model.productjob import ( |
3227 | - ProductJob, |
3228 | - ProductJobDerived, |
3229 | - ) |
3230 | -from lp.testing import TestCaseWithFactory |
3231 | -from lp.testing.layers import ( |
3232 | - DatabaseFunctionalLayer, |
3233 | - LaunchpadZopelessLayer, |
3234 | - ) |
3235 | - |
3236 | - |
3237 | -class ProductJobTestCase(TestCaseWithFactory): |
3238 | - """Test case for basic ProductJob class.""" |
3239 | - |
3240 | - layer = LaunchpadZopelessLayer |
3241 | - |
3242 | - def test_init(self): |
3243 | - product = self.factory.makeProduct() |
3244 | - metadata = ('some', 'arbitrary', 'metadata') |
3245 | - product_job = ProductJob( |
3246 | - product, ProductJobType.REVIEWER_NOTIFICATION, metadata) |
3247 | - self.assertEqual(product, product_job.product) |
3248 | - self.assertEqual( |
3249 | - ProductJobType.REVIEWER_NOTIFICATION, product_job.job_type) |
3250 | - expected_json_data = '["some", "arbitrary", "metadata"]' |
3251 | - self.assertEqual(expected_json_data, product_job._json_data) |
3252 | - |
3253 | - def test_metadata(self): |
3254 | - # The python structure stored as json is returned as python. |
3255 | - product = self.factory.makeProduct() |
3256 | - metadata = { |
3257 | - 'a_list': ('some', 'arbitrary', 'metadata'), |
3258 | - 'a_number': 1, |
3259 | - 'a_string': 'string', |
3260 | - } |
3261 | - product_job = ProductJob( |
3262 | - product, ProductJobType.REVIEWER_NOTIFICATION, metadata) |
3263 | - metadata['a_list'] = list(metadata['a_list']) |
3264 | - self.assertEqual(metadata, product_job.metadata) |
3265 | - |
3266 | - |
3267 | -class IProductThingJob(IProductJob): |
3268 | - """An interface for testing derived job classes.""" |
3269 | - |
3270 | - |
3271 | -class IProductThingJobSource(IProductJobSource): |
3272 | - """An interface for testing derived job source classes.""" |
3273 | - |
3274 | - |
3275 | -class FakeProductJob(ProductJobDerived): |
3276 | - """A class that reuses other interfaces and types for testing.""" |
3277 | - class_job_type = ProductJobType.REVIEWER_NOTIFICATION |
3278 | - implements(IProductThingJob) |
3279 | - classProvides(IProductThingJobSource) |
3280 | - |
3281 | - |
3282 | -class OtherFakeProductJob(ProductJobDerived): |
3283 | - """A class that reuses other interfaces and types for testing.""" |
3284 | - class_job_type = ProductJobType.COMMERCIAL_EXPIRED |
3285 | - implements(IProductThingJob) |
3286 | - classProvides(IProductThingJobSource) |
3287 | - |
3288 | - |
3289 | -class ProductJobDerivedTestCase(TestCaseWithFactory): |
3290 | - """Test case for the ProductJobDerived class.""" |
3291 | - |
3292 | - layer = DatabaseFunctionalLayer |
3293 | - |
3294 | - def test_repr(self): |
3295 | - product = self.factory.makeProduct('fnord') |
3296 | - metadata = {'foo': 'bar'} |
3297 | - job = FakeProductJob.create(product, metadata) |
3298 | - self.assertEqual( |
3299 | - '<FakeProductJob for fnord status=Waiting>', repr(job)) |
3300 | - |
3301 | - def test_create_success(self): |
3302 | - # Create an instance of ProductJobDerived that delegates to |
3303 | - # ProductJob. |
3304 | - product = self.factory.makeProduct() |
3305 | - metadata = {'foo': 'bar'} |
3306 | - self.assertIs(True, IProductJobSource.providedBy(ProductJobDerived)) |
3307 | - job = FakeProductJob.create(product, metadata) |
3308 | - self.assertIsInstance(job, ProductJobDerived) |
3309 | - self.assertIs(True, IProductJob.providedBy(job)) |
3310 | - self.assertIs(True, IProductJob.providedBy(job.context)) |
3311 | - |
3312 | - def test_create_raises_error(self): |
3313 | - # ProductJobDerived.create() raises an error because it |
3314 | - # needs to be subclassed to work properly. |
3315 | - product = self.factory.makeProduct() |
3316 | - metadata = {'foo': 'bar'} |
3317 | - self.assertRaises( |
3318 | - AttributeError, ProductJobDerived.create, product, metadata) |
3319 | - |
3320 | - def test_iterReady(self): |
3321 | - # iterReady finds job in the READY status that are of the same type. |
3322 | - product = self.factory.makeProduct() |
3323 | - metadata = {'foo': 'bar'} |
3324 | - job_1 = FakeProductJob.create(product, metadata) |
3325 | - job_2 = FakeProductJob.create(product, metadata) |
3326 | - job_2.start() |
3327 | - OtherFakeProductJob.create(product, metadata) |
3328 | - jobs = list(FakeProductJob.iterReady()) |
3329 | - self.assertEqual(1, len(jobs)) |
3330 | - self.assertEqual(job_1, jobs[0]) |
3331 | - |
3332 | - def test_find_product(self): |
3333 | - # Find all the jobs for a product regardless of date or job type. |
3334 | - product = self.factory.makeProduct() |
3335 | - metadata = {'foo': 'bar'} |
3336 | - job_1 = FakeProductJob.create(product, metadata) |
3337 | - job_2 = OtherFakeProductJob.create(product, metadata) |
3338 | - FakeProductJob.create(self.factory.makeProduct(), metadata) |
3339 | - jobs = list(ProductJobDerived.find(product=product)) |
3340 | - self.assertEqual(2, len(jobs)) |
3341 | - self.assertContentEqual([job_1.id, job_2.id], [job.id for job in jobs]) |
3342 | - |
3343 | - def test_find_job_type(self): |
3344 | - # Find all the jobs for a product and job_type regardless of date. |
3345 | - product = self.factory.makeProduct() |
3346 | - metadata = {'foo': 'bar'} |
3347 | - job_1 = FakeProductJob.create(product, metadata) |
3348 | - job_2 = FakeProductJob.create(product, metadata) |
3349 | - OtherFakeProductJob.create(product, metadata) |
3350 | - jobs = list(ProductJobDerived.find( |
3351 | - product, job_type=ProductJobType.REVIEWER_NOTIFICATION)) |
3352 | - self.assertEqual(2, len(jobs)) |
3353 | - self.assertContentEqual([job_1.id, job_2.id], [job.id for job in jobs]) |
3354 | - |
3355 | - def test_find_date_since(self): |
3356 | - # Find all the jobs for a product since a date regardless of job_type. |
3357 | - now = datetime.now(pytz.utc) |
3358 | - seven_days_ago = now - timedelta(7) |
3359 | - thirty_days_ago = now - timedelta(30) |
3360 | - product = self.factory.makeProduct() |
3361 | - metadata = {'foo': 'bar'} |
3362 | - job_1 = FakeProductJob.create(product, metadata) |
3363 | - removeSecurityProxy(job_1.job).date_created = thirty_days_ago |
3364 | - job_2 = FakeProductJob.create(product, metadata) |
3365 | - removeSecurityProxy(job_2.job).date_created = seven_days_ago |
3366 | - job_3 = OtherFakeProductJob.create(product, metadata) |
3367 | - removeSecurityProxy(job_3.job).date_created = now |
3368 | - jobs = list(ProductJobDerived.find(product, date_since=seven_days_ago)) |
3369 | - self.assertEqual(2, len(jobs)) |
3370 | - self.assertContentEqual([job_2.id, job_3.id], [job.id for job in jobs]) |
3371 | - |
3372 | - def test_log_name(self): |
3373 | - # The log_name is the name of the implementing class. |
3374 | - product = self.factory.makeProduct('fnord') |
3375 | - metadata = {'foo': 'bar'} |
3376 | - job = FakeProductJob.create(product, metadata) |
3377 | - self.assertEqual('FakeProductJob', job.log_name) |
3378 | - |
3379 | - def test_getOopsVars(self): |
3380 | - # The project name is added to the oops vars. |
3381 | - product = self.factory.makeProduct('fnord') |
3382 | - metadata = {'foo': 'bar'} |
3383 | - job = FakeProductJob.create(product, metadata) |
3384 | - oops_vars = job.getOopsVars() |
3385 | - self.assertIs(True, len(oops_vars) > 1) |
3386 | - self.assertIn(('product', product.name), oops_vars) |
3387 | ->>>>>>> MERGE-SOURCE |
3388 | |
3389 | === modified file 'lib/lp/registry/tests/test_subscribers.py' |
3390 | --- lib/lp/registry/tests/test_subscribers.py 2012-03-29 14:36:36 +0000 |
3391 | +++ lib/lp/registry/tests/test_subscribers.py 2012-03-22 23:21:24 +0000 |
3392 | @@ -1,524 +1,260 @@ |
3393 | -<<<<<<< TREE |
3394 | -# Copyright 2012 Canonical Ltd. This software is licensed under the |
3395 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
3396 | - |
3397 | -"""Test subscruber classes and functions.""" |
3398 | - |
3399 | -__metaclass__ = type |
3400 | - |
3401 | -from datetime import datetime |
3402 | - |
3403 | -from lazr.lifecycle.event import ObjectModifiedEvent |
3404 | -import pytz |
3405 | -from zope.security.proxy import removeSecurityProxy |
3406 | - |
3407 | -from lp.registry.interfaces.product import License |
3408 | -from lp.registry.subscribers import ( |
3409 | - LicenseNotification, |
3410 | - product_licenses_modified, |
3411 | - ) |
3412 | -from lp.services.webapp.publisher import get_current_browser_request |
3413 | -from lp.testing import ( |
3414 | - login_person, |
3415 | - logout, |
3416 | - TestCaseWithFactory, |
3417 | - ) |
3418 | -from lp.testing.layers import DatabaseFunctionalLayer |
3419 | -from lp.testing.mail_helpers import pop_notifications |
3420 | - |
3421 | - |
3422 | -class ProductLicensesModifiedTestCase(TestCaseWithFactory): |
3423 | - |
3424 | - layer = DatabaseFunctionalLayer |
3425 | - |
3426 | - def make_product_event(self, licenses, edited_fields='licenses'): |
3427 | - product = self.factory.makeProduct(licenses=licenses) |
3428 | - pop_notifications() |
3429 | - login_person(product.owner) |
3430 | - event = ObjectModifiedEvent( |
3431 | - product, product, edited_fields, user=product.owner) |
3432 | - return product, event |
3433 | - |
3434 | - def test_product_licenses_modified_licenses_not_edited(self): |
3435 | - product, event = self.make_product_event( |
3436 | - [License.OTHER_PROPRIETARY], edited_fields='_owner') |
3437 | - product_licenses_modified(product, event) |
3438 | - notifications = pop_notifications() |
3439 | - self.assertEqual(0, len(notifications)) |
3440 | - |
3441 | - def test_product_licenses_modified_licenses_common_license(self): |
3442 | - product, event = self.make_product_event([License.MIT]) |
3443 | - product_licenses_modified(product, event) |
3444 | - notifications = pop_notifications() |
3445 | - self.assertEqual(0, len(notifications)) |
3446 | - request = get_current_browser_request() |
3447 | - self.assertEqual(0, len(request.response.notifications)) |
3448 | - |
3449 | - def test_product_licenses_modified_licenses_other_proprietary(self): |
3450 | - product, event = self.make_product_event([License.OTHER_PROPRIETARY]) |
3451 | - product_licenses_modified(product, event) |
3452 | - notifications = pop_notifications() |
3453 | - self.assertEqual(1, len(notifications)) |
3454 | - request = get_current_browser_request() |
3455 | - self.assertEqual(1, len(request.response.notifications)) |
3456 | - |
3457 | - def test_product_licenses_modified_licenses_other_open_source(self): |
3458 | - product, event = self.make_product_event([License.OTHER_OPEN_SOURCE]) |
3459 | - product_licenses_modified(product, event) |
3460 | - notifications = pop_notifications() |
3461 | - self.assertEqual(1, len(notifications)) |
3462 | - request = get_current_browser_request() |
3463 | - self.assertEqual(0, len(request.response.notifications)) |
3464 | - |
3465 | - def test_product_licenses_modified_licenses_other_dont_know(self): |
3466 | - product, event = self.make_product_event([License.DONT_KNOW]) |
3467 | - product_licenses_modified(product, event) |
3468 | - notifications = pop_notifications() |
3469 | - self.assertEqual(1, len(notifications)) |
3470 | - request = get_current_browser_request() |
3471 | - self.assertEqual(0, len(request.response.notifications)) |
3472 | - |
3473 | - |
3474 | -class LicenseNotificationTestCase(TestCaseWithFactory): |
3475 | - |
3476 | - layer = DatabaseFunctionalLayer |
3477 | - |
3478 | - def make_product_user(self, licenses): |
3479 | - # Setup an a view that implements ProductLicenseMixin. |
3480 | - super(LicenseNotificationTestCase, self).setUp() |
3481 | - user = self.factory.makePerson( |
3482 | - name='registrant', email='registrant@launchpad.dev') |
3483 | - login_person(user) |
3484 | - product = self.factory.makeProduct( |
3485 | - name='ball', owner=user, licenses=licenses) |
3486 | - pop_notifications() |
3487 | - return product, user |
3488 | - |
3489 | - def verify_whiteboard(self, product): |
3490 | - # Verify that the review whiteboard was updated. |
3491 | - naked_product = removeSecurityProxy(product) |
3492 | - entries = naked_product.reviewer_whiteboard.split('\n') |
3493 | - whiteboard, stamp = entries[-1].rsplit(' ', 1) |
3494 | - self.assertEqual( |
3495 | - 'User notified of license policy on', whiteboard) |
3496 | - |
3497 | - def verify_user_email(self, notification): |
3498 | - # Verify that the user was sent an email about the license change. |
3499 | - self.assertEqual( |
3500 | - 'License information for ball in Launchpad', |
3501 | - notification['Subject']) |
3502 | - self.assertEqual( |
3503 | - 'Registrant <registrant@launchpad.dev>', |
3504 | - notification['To']) |
3505 | - self.assertEqual( |
3506 | - 'Commercial <commercial@launchpad.net>', |
3507 | - notification['Reply-To']) |
3508 | - |
3509 | - def test_send_known_license(self): |
3510 | - # A known license does not generate an email. |
3511 | - product, user = self.make_product_user([License.GNU_GPL_V2]) |
3512 | - notification = LicenseNotification(product, user) |
3513 | - result = notification.send() |
3514 | - self.assertIs(False, result) |
3515 | - self.assertEqual(0, len(pop_notifications())) |
3516 | - |
3517 | - def test_send_other_dont_know(self): |
3518 | - # An Other/I don't know license sends one email. |
3519 | - product, user = self.make_product_user([License.DONT_KNOW]) |
3520 | - notification = LicenseNotification(product, user) |
3521 | - result = notification.send() |
3522 | - self.assertIs(True, result) |
3523 | - self.verify_whiteboard(product) |
3524 | - notifications = pop_notifications() |
3525 | - self.assertEqual(1, len(notifications)) |
3526 | - self.verify_user_email(notifications.pop()) |
3527 | - |
3528 | - def test_send_other_open_source(self): |
3529 | - # An Other/Open Source license sends one email. |
3530 | - product, user = self.make_product_user([License.OTHER_OPEN_SOURCE]) |
3531 | - notification = LicenseNotification(product, user) |
3532 | - result = notification.send() |
3533 | - self.assertIs(True, result) |
3534 | - self.verify_whiteboard(product) |
3535 | - notifications = pop_notifications() |
3536 | - self.assertEqual(1, len(notifications)) |
3537 | - self.verify_user_email(notifications.pop()) |
3538 | - |
3539 | - def test_send_other_proprietary(self): |
3540 | - # An Other/Proprietary license sends one email. |
3541 | - product, user = self.make_product_user([License.OTHER_PROPRIETARY]) |
3542 | - notification = LicenseNotification(product, user) |
3543 | - result = notification.send() |
3544 | - self.assertIs(True, result) |
3545 | - self.verify_whiteboard(product) |
3546 | - notifications = pop_notifications() |
3547 | - self.assertEqual(1, len(notifications)) |
3548 | - self.verify_user_email(notifications.pop()) |
3549 | - |
3550 | - def test_display_no_request(self): |
3551 | - # If there is no request, there is no reason to show a message in |
3552 | - # the browser. |
3553 | - product, user = self.make_product_user([License.GNU_GPL_V2]) |
3554 | - notification = LicenseNotification(product, user) |
3555 | - logout() |
3556 | - result = notification.display() |
3557 | - self.assertIs(False, result) |
3558 | - |
3559 | - def test_display_no_message(self): |
3560 | - # A notification is not added if there is no message to show. |
3561 | - product, user = self.make_product_user([License.GNU_GPL_V2]) |
3562 | - notification = LicenseNotification(product, user) |
3563 | - result = notification.display() |
3564 | - self.assertEqual('', notification.getCommercialUseMessage()) |
3565 | - self.assertIs(False, result) |
3566 | - |
3567 | - def test_display_has_message(self): |
3568 | - # A notification is added if there is a message to show. |
3569 | - product, user = self.make_product_user([License.OTHER_PROPRIETARY]) |
3570 | - notification = LicenseNotification(product, user) |
3571 | - result = notification.display() |
3572 | - message = notification.getCommercialUseMessage() |
3573 | - self.assertIs(True, result) |
3574 | - request = get_current_browser_request() |
3575 | - self.assertEqual(1, len(request.response.notifications)) |
3576 | - self.assertIn(message, request.response.notifications[0].message) |
3577 | - self.assertIn( |
3578 | - '<a href="https://help.launchpad.net/CommercialHosting">', |
3579 | - request.response.notifications[0].message) |
3580 | - |
3581 | - def test_display_escapee_user_data(self): |
3582 | - # A notification is added if there is a message to show. |
3583 | - product, user = self.make_product_user([License.OTHER_PROPRIETARY]) |
3584 | - product.displayname = '<b>Look</b>' |
3585 | - notification = LicenseNotification(product, user) |
3586 | - result = notification.display() |
3587 | - self.assertIs(True, result) |
3588 | - request = get_current_browser_request() |
3589 | - self.assertEqual(1, len(request.response.notifications)) |
3590 | - self.assertIn( |
3591 | - '<b>Look</b>', |
3592 | - request.response.notifications[0].message) |
3593 | - |
3594 | - def test_formatDate(self): |
3595 | - # Verify the date format. |
3596 | - now = datetime(2005, 6, 15, 0, 0, 0, 0, pytz.UTC) |
3597 | - result = LicenseNotification._formatDate(now) |
3598 | - self.assertEqual('2005-06-15', result) |
3599 | - |
3600 | - def test_getTemplateName_other_dont_know(self): |
3601 | - product, user = self.make_product_user([License.DONT_KNOW]) |
3602 | - notification = LicenseNotification(product, user) |
3603 | - self.assertEqual( |
3604 | - 'product-license-dont-know.txt', |
3605 | - notification.getTemplateName()) |
3606 | - |
3607 | - def test_getTemplateName_propietary(self): |
3608 | - product, user = self.make_product_user([License.OTHER_PROPRIETARY]) |
3609 | - notification = LicenseNotification(product, user) |
3610 | - self.assertEqual( |
3611 | - 'product-license-other-proprietary.txt', |
3612 | - notification.getTemplateName()) |
3613 | - |
3614 | - def test_getTemplateName_other_open_source(self): |
3615 | - product, user = self.make_product_user([License.OTHER_OPEN_SOURCE]) |
3616 | - notification = LicenseNotification(product, user) |
3617 | - self.assertEqual( |
3618 | - 'product-license-other-open-source.txt', |
3619 | - notification.getTemplateName()) |
3620 | - |
3621 | - def test_getCommercialUseMessage_without_commercial_subscription(self): |
3622 | - product, user = self.make_product_user([License.MIT]) |
3623 | - notification = LicenseNotification(product, user) |
3624 | - self.assertEqual('', notification.getCommercialUseMessage()) |
3625 | - |
3626 | - def test_getCommercialUseMessage_with_complimentary_cs(self): |
3627 | - product, user = self.make_product_user([License.OTHER_PROPRIETARY]) |
3628 | - notification = LicenseNotification(product, user) |
3629 | - message = ( |
3630 | - "Ball's complimentary commercial subscription expires on %s." % |
3631 | - product.commercial_subscription.date_expires.date().isoformat()) |
3632 | - self.assertEqual(message, notification.getCommercialUseMessage()) |
3633 | - |
3634 | - def test_getCommercialUseMessage_with_commercial_subscription(self): |
3635 | - product, user = self.make_product_user([License.MIT]) |
3636 | - self.factory.makeCommercialSubscription(product) |
3637 | - product.licenses = [License.MIT, License.OTHER_PROPRIETARY] |
3638 | - notification = LicenseNotification(product, user) |
3639 | - message = ( |
3640 | - "Ball's commercial subscription expires on %s." % |
3641 | - product.commercial_subscription.date_expires.date().isoformat()) |
3642 | - self.assertEqual(message, notification.getCommercialUseMessage()) |
3643 | - |
3644 | - def test_getCommercialUseMessage_with_expired_cs(self): |
3645 | - product, user = self.make_product_user([License.MIT]) |
3646 | - self.factory.makeCommercialSubscription(product, expired=True) |
3647 | - product.licenses = [License.MIT, License.OTHER_PROPRIETARY] |
3648 | - notification = LicenseNotification(product, user) |
3649 | - message = ( |
3650 | - "Ball's commercial subscription expired on %s." % |
3651 | - product.commercial_subscription.date_expires.date().isoformat()) |
3652 | - self.assertEqual(message, notification.getCommercialUseMessage()) |
3653 | - self.assertEqual(message, notification.getCommercialUseMessage()) |
3654 | -======= |
3655 | -# Copyright 2012 Canonical Ltd. This software is licensed under the |
3656 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
3657 | - |
3658 | -"""Test subscruber classes and functions.""" |
3659 | - |
3660 | -__metaclass__ = type |
3661 | - |
3662 | -from datetime import datetime |
3663 | - |
3664 | -import pytz |
3665 | - |
3666 | -from zope.security.proxy import removeSecurityProxy |
3667 | -from lazr.lifecycle.event import ObjectModifiedEvent |
3668 | - |
3669 | -from lp.registry.interfaces.product import License |
3670 | -from lp.registry.subscribers import ( |
3671 | - LicenseNotification, |
3672 | - product_licenses_modified, |
3673 | - ) |
3674 | -from lp.services.webapp.publisher import get_current_browser_request |
3675 | -from lp.testing import ( |
3676 | - login_person, |
3677 | - logout, |
3678 | - TestCaseWithFactory, |
3679 | - ) |
3680 | -from lp.testing.layers import DatabaseFunctionalLayer |
3681 | -from lp.testing.mail_helpers import pop_notifications |
3682 | - |
3683 | - |
3684 | -class ProductLicensesModifiedTestCase(TestCaseWithFactory): |
3685 | - |
3686 | - layer = DatabaseFunctionalLayer |
3687 | - |
3688 | - def make_product_event(self, licenses, edited_fields='licenses'): |
3689 | - product = self.factory.makeProduct(licenses=licenses) |
3690 | - pop_notifications() |
3691 | - login_person(product.owner) |
3692 | - event = ObjectModifiedEvent( |
3693 | - product, product, edited_fields, user=product.owner) |
3694 | - return product, event |
3695 | - |
3696 | - def test_product_licenses_modified_licenses_not_edited(self): |
3697 | - product, event = self.make_product_event( |
3698 | - [License.OTHER_PROPRIETARY], edited_fields='_owner') |
3699 | - product_licenses_modified(product, event) |
3700 | - notifications = pop_notifications() |
3701 | - self.assertEqual(0, len(notifications)) |
3702 | - |
3703 | - def test_product_licenses_modified_licenses_common_license(self): |
3704 | - product, event = self.make_product_event([License.MIT]) |
3705 | - product_licenses_modified(product, event) |
3706 | - notifications = pop_notifications() |
3707 | - self.assertEqual(0, len(notifications)) |
3708 | - request = get_current_browser_request() |
3709 | - self.assertEqual(0, len(request.response.notifications)) |
3710 | - |
3711 | - def test_product_licenses_modified_licenses_other_proprietary(self): |
3712 | - product, event = self.make_product_event([License.OTHER_PROPRIETARY]) |
3713 | - product_licenses_modified(product, event) |
3714 | - notifications = pop_notifications() |
3715 | - self.assertEqual(1, len(notifications)) |
3716 | - request = get_current_browser_request() |
3717 | - self.assertEqual(1, len(request.response.notifications)) |
3718 | - |
3719 | - def test_product_licenses_modified_licenses_other_open_source(self): |
3720 | - product, event = self.make_product_event([License.OTHER_OPEN_SOURCE]) |
3721 | - product_licenses_modified(product, event) |
3722 | - notifications = pop_notifications() |
3723 | - self.assertEqual(1, len(notifications)) |
3724 | - request = get_current_browser_request() |
3725 | - self.assertEqual(0, len(request.response.notifications)) |
3726 | - |
3727 | - def test_product_licenses_modified_licenses_other_dont_know(self): |
3728 | - product, event = self.make_product_event([License.DONT_KNOW]) |
3729 | - product_licenses_modified(product, event) |
3730 | - notifications = pop_notifications() |
3731 | - self.assertEqual(1, len(notifications)) |
3732 | - request = get_current_browser_request() |
3733 | - self.assertEqual(0, len(request.response.notifications)) |
3734 | - |
3735 | - |
3736 | -class LicenseNotificationTestCase(TestCaseWithFactory): |
3737 | - |
3738 | - layer = DatabaseFunctionalLayer |
3739 | - |
3740 | - def make_product_user(self, licenses): |
3741 | - # Setup an a view that implements ProductLicenseMixin. |
3742 | - super(LicenseNotificationTestCase, self).setUp() |
3743 | - user = self.factory.makePerson( |
3744 | - name='registrant', email='registrant@launchpad.dev') |
3745 | - login_person(user) |
3746 | - product = self.factory.makeProduct( |
3747 | - name='ball', owner=user, licenses=licenses) |
3748 | - pop_notifications() |
3749 | - return product, user |
3750 | - |
3751 | - def verify_whiteboard(self, product): |
3752 | - # Verify that the review whiteboard was updated. |
3753 | - naked_product = removeSecurityProxy(product) |
3754 | - entries = naked_product.reviewer_whiteboard.split('\n') |
3755 | - whiteboard, stamp = entries[-1].rsplit(' ', 1) |
3756 | - self.assertEqual( |
3757 | - 'User notified of license policy on', whiteboard) |
3758 | - |
3759 | - def verify_user_email(self, notification): |
3760 | - # Verify that the user was sent an email about the license change. |
3761 | - self.assertEqual( |
3762 | - 'License information for ball in Launchpad', |
3763 | - notification['Subject']) |
3764 | - self.assertEqual( |
3765 | - 'Registrant <registrant@launchpad.dev>', |
3766 | - notification['To']) |
3767 | - self.assertEqual( |
3768 | - 'Commercial <commercial@launchpad.net>', |
3769 | - notification['Reply-To']) |
3770 | - |
3771 | - def test_send_known_license(self): |
3772 | - # A known license does not generate an email. |
3773 | - product, user = self.make_product_user([License.GNU_GPL_V2]) |
3774 | - notification = LicenseNotification(product, user) |
3775 | - result = notification.send() |
3776 | - self.assertIs(False, result) |
3777 | - self.assertEqual(0, len(pop_notifications())) |
3778 | - |
3779 | - def test_send_other_dont_know(self): |
3780 | - # An Other/I don't know license sends one email. |
3781 | - product, user = self.make_product_user([License.DONT_KNOW]) |
3782 | - notification = LicenseNotification(product, user) |
3783 | - result = notification.send() |
3784 | - self.assertIs(True, result) |
3785 | - self.verify_whiteboard(product) |
3786 | - notifications = pop_notifications() |
3787 | - self.assertEqual(1, len(notifications)) |
3788 | - self.verify_user_email(notifications.pop()) |
3789 | - |
3790 | - def test_send_other_open_source(self): |
3791 | - # An Other/Open Source license sends one email. |
3792 | - product, user = self.make_product_user([License.OTHER_OPEN_SOURCE]) |
3793 | - notification = LicenseNotification(product, user) |
3794 | - result = notification.send() |
3795 | - self.assertIs(True, result) |
3796 | - self.verify_whiteboard(product) |
3797 | - notifications = pop_notifications() |
3798 | - self.assertEqual(1, len(notifications)) |
3799 | - self.verify_user_email(notifications.pop()) |
3800 | - |
3801 | - def test_send_other_proprietary(self): |
3802 | - # An Other/Proprietary license sends one email. |
3803 | - product, user = self.make_product_user([License.OTHER_PROPRIETARY]) |
3804 | - notification = LicenseNotification(product, user) |
3805 | - result = notification.send() |
3806 | - self.assertIs(True, result) |
3807 | - self.verify_whiteboard(product) |
3808 | - notifications = pop_notifications() |
3809 | - self.assertEqual(1, len(notifications)) |
3810 | - self.verify_user_email(notifications.pop()) |
3811 | - |
3812 | - def test_display_no_request(self): |
3813 | - # If there is no request, there is no reason to show a message in |
3814 | - # the browser. |
3815 | - product, user = self.make_product_user([License.GNU_GPL_V2]) |
3816 | - notification = LicenseNotification(product, user) |
3817 | - logout() |
3818 | - result = notification.display() |
3819 | - self.assertIs(False, result) |
3820 | - |
3821 | - def test_display_no_message(self): |
3822 | - # A notification is not added if there is no message to show. |
3823 | - product, user = self.make_product_user([License.GNU_GPL_V2]) |
3824 | - notification = LicenseNotification(product, user) |
3825 | - result = notification.display() |
3826 | - self.assertEqual('', notification.getCommercialUseMessage()) |
3827 | - self.assertIs(False, result) |
3828 | - |
3829 | - def test_display_has_message(self): |
3830 | - # A notification is added if there is a message to show. |
3831 | - product, user = self.make_product_user([License.OTHER_PROPRIETARY]) |
3832 | - notification = LicenseNotification(product, user) |
3833 | - result = notification.display() |
3834 | - message = notification.getCommercialUseMessage() |
3835 | - self.assertIs(True, result) |
3836 | - request = get_current_browser_request() |
3837 | - self.assertEqual(1, len(request.response.notifications)) |
3838 | - self.assertIn(message, request.response.notifications[0].message) |
3839 | - self.assertIn( |
3840 | - '<a href="https://help.launchpad.net/CommercialHosting">', |
3841 | - request.response.notifications[0].message) |
3842 | - |
3843 | - def test_display_escapee_user_data(self): |
3844 | - # A notification is added if there is a message to show. |
3845 | - product, user = self.make_product_user([License.OTHER_PROPRIETARY]) |
3846 | - product.displayname = '<b>Look</b>' |
3847 | - notification = LicenseNotification(product, user) |
3848 | - result = notification.display() |
3849 | - self.assertIs(True, result) |
3850 | - request = get_current_browser_request() |
3851 | - self.assertEqual(1, len(request.response.notifications)) |
3852 | - self.assertIn( |
3853 | - '<b>Look</b>', |
3854 | - request.response.notifications[0].message) |
3855 | - |
3856 | - def test_formatDate(self): |
3857 | - # Verify the date format. |
3858 | - now = datetime(2005, 6, 15, 0, 0, 0, 0, pytz.UTC) |
3859 | - result = LicenseNotification._formatDate(now) |
3860 | - self.assertEqual('2005-06-15', result) |
3861 | - |
3862 | - def test_getTemplateName_other_dont_know(self): |
3863 | - product, user = self.make_product_user([License.DONT_KNOW]) |
3864 | - notification = LicenseNotification(product, user) |
3865 | - self.assertEqual( |
3866 | - 'product-license-dont-know.txt', |
3867 | - notification.getTemplateName()) |
3868 | - |
3869 | - def test_getTemplateName_propietary(self): |
3870 | - product, user = self.make_product_user([License.OTHER_PROPRIETARY]) |
3871 | - notification = LicenseNotification(product, user) |
3872 | - self.assertEqual( |
3873 | - 'product-license-other-proprietary.txt', |
3874 | - notification.getTemplateName()) |
3875 | - |
3876 | - def test_getTemplateName_other_open_source(self): |
3877 | - product, user = self.make_product_user([License.OTHER_OPEN_SOURCE]) |
3878 | - notification = LicenseNotification(product, user) |
3879 | - self.assertEqual( |
3880 | - 'product-license-other-open-source.txt', |
3881 | - notification.getTemplateName()) |
3882 | - |
3883 | - def test_getCommercialUseMessage_without_commercial_subscription(self): |
3884 | - product, user = self.make_product_user([License.MIT]) |
3885 | - notification = LicenseNotification(product, user) |
3886 | - self.assertEqual('', notification.getCommercialUseMessage()) |
3887 | - |
3888 | - def test_getCommercialUseMessage_with_complimentary_cs(self): |
3889 | - product, user = self.make_product_user([License.OTHER_PROPRIETARY]) |
3890 | - notification = LicenseNotification(product, user) |
3891 | - message = ( |
3892 | - "Ball's complimentary commercial subscription expires on %s." % |
3893 | - product.commercial_subscription.date_expires.date().isoformat()) |
3894 | - self.assertEqual(message, notification.getCommercialUseMessage()) |
3895 | - |
3896 | - def test_getCommercialUseMessage_with_commercial_subscription(self): |
3897 | - product, user = self.make_product_user([License.MIT]) |
3898 | - self.factory.makeCommercialSubscription(product) |
3899 | - product.licenses = [License.MIT, License.OTHER_PROPRIETARY] |
3900 | - notification = LicenseNotification(product, user) |
3901 | - message = ( |
3902 | - "Ball's commercial subscription expires on %s." % |
3903 | - product.commercial_subscription.date_expires.date().isoformat()) |
3904 | - self.assertEqual(message, notification.getCommercialUseMessage()) |
3905 | - |
3906 | - def test_getCommercialUseMessage_with_expired_cs(self): |
3907 | - product, user = self.make_product_user([License.MIT]) |
3908 | - self.factory.makeCommercialSubscription(product, expired=True) |
3909 | - product.licenses = [License.MIT, License.OTHER_PROPRIETARY] |
3910 | - notification = LicenseNotification(product, user) |
3911 | - message = ( |
3912 | - "Ball's commercial subscription expired on %s." % |
3913 | - product.commercial_subscription.date_expires.date().isoformat()) |
3914 | - self.assertEqual(message, notification.getCommercialUseMessage()) |
3915 | - self.assertEqual(message, notification.getCommercialUseMessage()) |
3916 | ->>>>>>> MERGE-SOURCE |
3917 | +# Copyright 2012 Canonical Ltd. This software is licensed under the |
3918 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
3919 | + |
3920 | +"""Test subscruber classes and functions.""" |
3921 | + |
3922 | +__metaclass__ = type |
3923 | + |
3924 | +from datetime import datetime |
3925 | + |
3926 | +from lazr.lifecycle.event import ObjectModifiedEvent |
3927 | +import pytz |
3928 | +from zope.security.proxy import removeSecurityProxy |
3929 | + |
3930 | +from lp.registry.interfaces.product import License |
3931 | +from lp.registry.subscribers import ( |
3932 | + LicenseNotification, |
3933 | + product_licenses_modified, |
3934 | + ) |
3935 | +from lp.services.webapp.publisher import get_current_browser_request |
3936 | +from lp.testing import ( |
3937 | + login_person, |
3938 | + logout, |
3939 | + TestCaseWithFactory, |
3940 | + ) |
3941 | +from lp.testing.layers import DatabaseFunctionalLayer |
3942 | +from lp.testing.mail_helpers import pop_notifications |
3943 | + |
3944 | + |
3945 | +class ProductLicensesModifiedTestCase(TestCaseWithFactory): |
3946 | + |
3947 | + layer = DatabaseFunctionalLayer |
3948 | + |
3949 | + def make_product_event(self, licenses, edited_fields='licenses'): |
3950 | + product = self.factory.makeProduct(licenses=licenses) |
3951 | + pop_notifications() |
3952 | + login_person(product.owner) |
3953 | + event = ObjectModifiedEvent( |
3954 | + product, product, edited_fields, user=product.owner) |
3955 | + return product, event |
3956 | + |
3957 | + def test_product_licenses_modified_licenses_not_edited(self): |
3958 | + product, event = self.make_product_event( |
3959 | + [License.OTHER_PROPRIETARY], edited_fields='_owner') |
3960 | + product_licenses_modified(product, event) |
3961 | + notifications = pop_notifications() |
3962 | + self.assertEqual(0, len(notifications)) |
3963 | + |
3964 | + def test_product_licenses_modified_licenses_common_license(self): |
3965 | + product, event = self.make_product_event([License.MIT]) |
3966 | + product_licenses_modified(product, event) |
3967 | + notifications = pop_notifications() |
3968 | + self.assertEqual(0, len(notifications)) |
3969 | + request = get_current_browser_request() |
3970 | + self.assertEqual(0, len(request.response.notifications)) |
3971 | + |
3972 | + def test_product_licenses_modified_licenses_other_proprietary(self): |
3973 | + product, event = self.make_product_event([License.OTHER_PROPRIETARY]) |
3974 | + product_licenses_modified(product, event) |
3975 | + notifications = pop_notifications() |
3976 | + self.assertEqual(1, len(notifications)) |
3977 | + request = get_current_browser_request() |
3978 | + self.assertEqual(1, len(request.response.notifications)) |
3979 | + |
3980 | + def test_product_licenses_modified_licenses_other_open_source(self): |
3981 | + product, event = self.make_product_event([License.OTHER_OPEN_SOURCE]) |
3982 | + product_licenses_modified(product, event) |
3983 | + notifications = pop_notifications() |
3984 | + self.assertEqual(1, len(notifications)) |
3985 | + request = get_current_browser_request() |
3986 | + self.assertEqual(0, len(request.response.notifications)) |
3987 | + |
3988 | + def test_product_licenses_modified_licenses_other_dont_know(self): |
3989 | + product, event = self.make_product_event([License.DONT_KNOW]) |
3990 | + product_licenses_modified(product, event) |
3991 | + notifications = pop_notifications() |
3992 | + self.assertEqual(1, len(notifications)) |
3993 | + request = get_current_browser_request() |
3994 | + self.assertEqual(0, len(request.response.notifications)) |
3995 | + |
3996 | + |
3997 | +class LicenseNotificationTestCase(TestCaseWithFactory): |
3998 | + |
3999 | + layer = DatabaseFunctionalLayer |
4000 | + |
4001 | + def make_product_user(self, licenses): |
4002 | + # Setup an a view that implements ProductLicenseMixin. |
4003 | + super(LicenseNotificationTestCase, self).setUp() |
4004 | + user = self.factory.makePerson( |
4005 | + name='registrant', email='registrant@launchpad.dev') |
4006 | + login_person(user) |
4007 | + product = self.factory.makeProduct( |
4008 | + name='ball', owner=user, licenses=licenses) |
4009 | + pop_notifications() |
4010 | + return product, user |
4011 | + |
4012 | + def verify_whiteboard(self, product): |
4013 | + # Verify that the review whiteboard was updated. |
4014 | + naked_product = removeSecurityProxy(product) |
4015 | + entries = naked_product.reviewer_whiteboard.split('\n') |
4016 | + whiteboard, stamp = entries[-1].rsplit(' ', 1) |
4017 | + self.assertEqual( |
4018 | + 'User notified of license policy on', whiteboard) |
4019 | + |
4020 | + def verify_user_email(self, notification): |
4021 | + # Verify that the user was sent an email about the license change. |
4022 | + self.assertEqual( |
4023 | + 'License information for ball in Launchpad', |
4024 | + notification['Subject']) |
4025 | + self.assertEqual( |
4026 | + 'Registrant <registrant@launchpad.dev>', |
4027 | + notification['To']) |
4028 | + self.assertEqual( |
4029 | + 'Commercial <commercial@launchpad.net>', |
4030 | + notification['Reply-To']) |
4031 | + |
4032 | + def test_send_known_license(self): |
4033 | + # A known license does not generate an email. |
4034 | + product, user = self.make_product_user([License.GNU_GPL_V2]) |
4035 | + notification = LicenseNotification(product, user) |
4036 | + result = notification.send() |
4037 | + self.assertIs(False, result) |
4038 | + self.assertEqual(0, len(pop_notifications())) |
4039 | + |
4040 | + def test_send_other_dont_know(self): |
4041 | + # An Other/I don't know license sends one email. |
4042 | + product, user = self.make_product_user([License.DONT_KNOW]) |
4043 | + notification = LicenseNotification(product, user) |
4044 | + result = notification.send() |
4045 | + self.assertIs(True, result) |
4046 | + self.verify_whiteboard(product) |
4047 | + notifications = pop_notifications() |
4048 | + self.assertEqual(1, len(notifications)) |
4049 | + self.verify_user_email(notifications.pop()) |
4050 | + |
4051 | + def test_send_other_open_source(self): |
4052 | + # An Other/Open Source license sends one email. |
4053 | + product, user = self.make_product_user([License.OTHER_OPEN_SOURCE]) |
4054 | + notification = LicenseNotification(product, user) |
4055 | + result = notification.send() |
4056 | + self.assertIs(True, result) |
4057 | + self.verify_whiteboard(product) |
4058 | + notifications = pop_notifications() |
4059 | + self.assertEqual(1, len(notifications)) |
4060 | + self.verify_user_email(notifications.pop()) |
4061 | + |
4062 | + def test_send_other_proprietary(self): |
4063 | + # An Other/Proprietary license sends one email. |
4064 | + product, user = self.make_product_user([License.OTHER_PROPRIETARY]) |
4065 | + notification = LicenseNotification(product, user) |
4066 | + result = notification.send() |
4067 | + self.assertIs(True, result) |
4068 | + self.verify_whiteboard(product) |
4069 | + notifications = pop_notifications() |
4070 | + self.assertEqual(1, len(notifications)) |
4071 | + self.verify_user_email(notifications.pop()) |
4072 | + |
4073 | + def test_display_no_request(self): |
4074 | + # If there is no request, there is no reason to show a message in |
4075 | + # the browser. |
4076 | + product, user = self.make_product_user([License.GNU_GPL_V2]) |
4077 | + notification = LicenseNotification(product, user) |
4078 | + logout() |
4079 | + result = notification.display() |
4080 | + self.assertIs(False, result) |
4081 | + |
4082 | + def test_display_no_message(self): |
4083 | + # A notification is not added if there is no message to show. |
4084 | + product, user = self.make_product_user([License.GNU_GPL_V2]) |
4085 | + notification = LicenseNotification(product, user) |
4086 | + result = notification.display() |
4087 | + self.assertEqual('', notification.getCommercialUseMessage()) |
4088 | + self.assertIs(False, result) |
4089 | + |
4090 | + def test_display_has_message(self): |
4091 | + # A notification is added if there is a message to show. |
4092 | + product, user = self.make_product_user([License.OTHER_PROPRIETARY]) |
4093 | + notification = LicenseNotification(product, user) |
4094 | + result = notification.display() |
4095 | + message = notification.getCommercialUseMessage() |
4096 | + self.assertIs(True, result) |
4097 | + request = get_current_browser_request() |
4098 | + self.assertEqual(1, len(request.response.notifications)) |
4099 | + self.assertIn(message, request.response.notifications[0].message) |
4100 | + self.assertIn( |
4101 | + '<a href="https://help.launchpad.net/CommercialHosting">', |
4102 | + request.response.notifications[0].message) |
4103 | + |
4104 | + def test_display_escapee_user_data(self): |
4105 | + # A notification is added if there is a message to show. |
4106 | + product, user = self.make_product_user([License.OTHER_PROPRIETARY]) |
4107 | + product.displayname = '<b>Look</b>' |
4108 | + notification = LicenseNotification(product, user) |
4109 | + result = notification.display() |
4110 | + self.assertIs(True, result) |
4111 | + request = get_current_browser_request() |
4112 | + self.assertEqual(1, len(request.response.notifications)) |
4113 | + self.assertIn( |
4114 | + '<b>Look</b>', |
4115 | + request.response.notifications[0].message) |
4116 | + |
4117 | + def test_formatDate(self): |
4118 | + # Verify the date format. |
4119 | + now = datetime(2005, 6, 15, 0, 0, 0, 0, pytz.UTC) |
4120 | + result = LicenseNotification._formatDate(now) |
4121 | + self.assertEqual('2005-06-15', result) |
4122 | + |
4123 | + def test_getTemplateName_other_dont_know(self): |
4124 | + product, user = self.make_product_user([License.DONT_KNOW]) |
4125 | + notification = LicenseNotification(product, user) |
4126 | + self.assertEqual( |
4127 | + 'product-license-dont-know.txt', |
4128 | + notification.getTemplateName()) |
4129 | + |
4130 | + def test_getTemplateName_propietary(self): |
4131 | + product, user = self.make_product_user([License.OTHER_PROPRIETARY]) |
4132 | + notification = LicenseNotification(product, user) |
4133 | + self.assertEqual( |
4134 | + 'product-license-other-proprietary.txt', |
4135 | + notification.getTemplateName()) |
4136 | + |
4137 | + def test_getTemplateName_other_open_source(self): |
4138 | + product, user = self.make_product_user([License.OTHER_OPEN_SOURCE]) |
4139 | + notification = LicenseNotification(product, user) |
4140 | + self.assertEqual( |
4141 | + 'product-license-other-open-source.txt', |
4142 | + notification.getTemplateName()) |
4143 | + |
4144 | + def test_getCommercialUseMessage_without_commercial_subscription(self): |
4145 | + product, user = self.make_product_user([License.MIT]) |
4146 | + notification = LicenseNotification(product, user) |
4147 | + self.assertEqual('', notification.getCommercialUseMessage()) |
4148 | + |
4149 | + def test_getCommercialUseMessage_with_complimentary_cs(self): |
4150 | + product, user = self.make_product_user([License.OTHER_PROPRIETARY]) |
4151 | + notification = LicenseNotification(product, user) |
4152 | + message = ( |
4153 | + "Ball's complimentary commercial subscription expires on %s." % |
4154 | + product.commercial_subscription.date_expires.date().isoformat()) |
4155 | + self.assertEqual(message, notification.getCommercialUseMessage()) |
4156 | + |
4157 | + def test_getCommercialUseMessage_with_commercial_subscription(self): |
4158 | + product, user = self.make_product_user([License.MIT]) |
4159 | + self.factory.makeCommercialSubscription(product) |
4160 | + product.licenses = [License.MIT, License.OTHER_PROPRIETARY] |
4161 | + notification = LicenseNotification(product, user) |
4162 | + message = ( |
4163 | + "Ball's commercial subscription expires on %s." % |
4164 | + product.commercial_subscription.date_expires.date().isoformat()) |
4165 | + self.assertEqual(message, notification.getCommercialUseMessage()) |
4166 | + |
4167 | + def test_getCommercialUseMessage_with_expired_cs(self): |
4168 | + product, user = self.make_product_user([License.MIT]) |
4169 | + self.factory.makeCommercialSubscription(product, expired=True) |
4170 | + product.licenses = [License.MIT, License.OTHER_PROPRIETARY] |
4171 | + notification = LicenseNotification(product, user) |
4172 | + message = ( |
4173 | + "Ball's commercial subscription expired on %s." % |
4174 | + product.commercial_subscription.date_expires.date().isoformat()) |
4175 | + self.assertEqual(message, notification.getCommercialUseMessage()) |
4176 | + self.assertEqual(message, notification.getCommercialUseMessage()) |
4177 | |
4178 | === modified file 'lib/lp/scripts/garbo.py' |
4179 | --- lib/lp/scripts/garbo.py 2012-03-29 14:36:36 +0000 |
4180 | +++ lib/lp/scripts/garbo.py 2012-03-26 22:47:47 +0000 |
4181 | @@ -81,7 +81,6 @@ |
4182 | from lp.services.librarian.model import TimeLimitedToken |
4183 | from lp.services.log.logger import PrefixFilter |
4184 | from lp.services.looptuner import TunableLoop |
4185 | -from lp.services.memcache.interfaces import IMemcacheClient |
4186 | from lp.services.oauth.model import OAuthNonce |
4187 | from lp.services.openid.model.openidconsumer import OpenIDConsumerNonce |
4188 | from lp.services.propertycache import cachedproperty |
4189 | @@ -1092,43 +1091,6 @@ |
4190 | self.offset += chunk_size |
4191 | |
4192 | |
4193 | -<<<<<<< TREE |
4194 | -======= |
4195 | -class BugLegacyAccessMirrorer(TunableLoop): |
4196 | - """A `TunableLoop` to populate the access policy schema for all bugs.""" |
4197 | - |
4198 | - maximum_chunk_size = 5000 |
4199 | - |
4200 | - def __init__(self, log, abort_time=None): |
4201 | - super(BugLegacyAccessMirrorer, self).__init__(log, abort_time) |
4202 | - watermark = getUtility(IMemcacheClient).get( |
4203 | - '%s:bug-legacy-access-mirrorer' % config.instance_name) |
4204 | - self.start_at = watermark or 0 |
4205 | - |
4206 | - def findBugIDs(self): |
4207 | - return IMasterStore(Bug).find( |
4208 | - (Bug.id,), Bug.id >= self.start_at).order_by(Bug.id) |
4209 | - |
4210 | - def isDone(self): |
4211 | - return self.findBugIDs().is_empty() |
4212 | - |
4213 | - def __call__(self, chunk_size): |
4214 | - ids = [row[0] for row in self.findBugIDs()[:chunk_size]] |
4215 | - list(IMasterStore(Bug).using(Bug).find( |
4216 | - SQL('bug_mirror_legacy_access(Bug.id)'), |
4217 | - Bug.id.is_in(ids))) |
4218 | - |
4219 | - self.start_at = ids[-1] + 1 |
4220 | - result = getUtility(IMemcacheClient).set( |
4221 | - '%s:bug-legacy-access-mirrorer' % config.instance_name, |
4222 | - self.start_at) |
4223 | - if not result: |
4224 | - self.log.warning('Failed to set start_at in memcache.') |
4225 | - |
4226 | - transaction.commit() |
4227 | - |
4228 | - |
4229 | ->>>>>>> MERGE-SOURCE |
4230 | class BaseDatabaseGarbageCollector(LaunchpadCronScript): |
4231 | """Abstract base class to run a collection of TunableLoops.""" |
4232 | script_name = None # Script name for locking and database user. Override. |
4233 | @@ -1380,10 +1342,6 @@ |
4234 | UnusedSessionPruner, |
4235 | DuplicateSessionPruner, |
4236 | BugHeatUpdater, |
4237 | -<<<<<<< TREE |
4238 | -======= |
4239 | - BugLegacyAccessMirrorer, |
4240 | ->>>>>>> MERGE-SOURCE |
4241 | ] |
4242 | experimental_tunable_loops = [] |
4243 | |
4244 | |
4245 | === modified file 'lib/lp/scripts/tests/test_garbo.py' |
4246 | --- lib/lp/scripts/tests/test_garbo.py 2012-03-29 14:36:36 +0000 |
4247 | +++ lib/lp/scripts/tests/test_garbo.py 2012-03-26 22:47:47 +0000 |
4248 | @@ -54,10 +54,6 @@ |
4249 | ) |
4250 | from lp.code.model.codeimportevent import CodeImportEvent |
4251 | from lp.code.model.codeimportresult import CodeImportResult |
4252 | -<<<<<<< TREE |
4253 | -======= |
4254 | -from lp.registry.interfaces.accesspolicy import IAccessArtifactSource |
4255 | ->>>>>>> MERGE-SOURCE |
4256 | from lp.registry.interfaces.distribution import IDistributionSet |
4257 | from lp.registry.interfaces.person import IPersonSet |
4258 | from lp.scripts.garbo import ( |
4259 | @@ -1108,29 +1104,6 @@ |
4260 | self.assertEqual(whiteboard, spec.whiteboard) |
4261 | self.assertEqual(0, spec.work_items.count()) |
4262 | |
4263 | -<<<<<<< TREE |
4264 | -======= |
4265 | - def test_BugLegacyAccessMirrorer(self): |
4266 | - # Private bugs without corresponding data in the access policy |
4267 | - # schema get mirrored. |
4268 | - switch_dbuser('testadmin') |
4269 | - bug = self.factory.makeBug(private=True) |
4270 | - # Remove the existing mirrored data. |
4271 | - getUtility(IAccessArtifactSource).delete([bug]) |
4272 | - transaction.commit() |
4273 | - self.runHourly() |
4274 | - # Check that there's an artifact again, and delete it. |
4275 | - switch_dbuser('testadmin') |
4276 | - [artifact] = getUtility(IAccessArtifactSource).find([bug]) |
4277 | - getUtility(IAccessArtifactSource).delete([bug]) |
4278 | - transaction.commit() |
4279 | - self.runHourly() |
4280 | - # A watermark is kept in memcache, so a second run doesn't |
4281 | - # consider the same bug. |
4282 | - self.assertContentEqual( |
4283 | - [], getUtility(IAccessArtifactSource).find([bug])) |
4284 | - |
4285 | ->>>>>>> MERGE-SOURCE |
4286 | |
4287 | class TestGarboTasks(TestCaseWithFactory): |
4288 | layer = LaunchpadZopelessLayer |
4289 | |
4290 | === modified file 'lib/lp/services/features/flags.py' |
4291 | --- lib/lp/services/features/flags.py 2012-03-29 14:36:36 +0000 |
4292 | +++ lib/lp/services/features/flags.py 2012-03-26 21:23:40 +0000 |
4293 | @@ -290,7 +290,6 @@ |
4294 | ('disclosure.enhanced_sharing.enabled', |
4295 | 'boolean', |
4296 | ('If true, will allow the use of the new sharing view and apis used ' |
4297 | -<<<<<<< TREE |
4298 | 'for the new disclosure data model to view but not write data.'), |
4299 | '', |
4300 | 'Sharing overview', |
4301 | @@ -308,18 +307,6 @@ |
4302 | 'to edit the new disclosure data model.'), |
4303 | '', |
4304 | 'Sharing management', |
4305 | -======= |
4306 | - 'for the new disclosure data model to view but not write data.'), |
4307 | - '', |
4308 | - 'Sharing overview', |
4309 | - ''), |
4310 | - ('disclosure.enhanced_sharing.writable', |
4311 | - 'boolean', |
4312 | - ('If true, will allow the use of the new sharing view and apis used ' |
4313 | - 'to edit the new disclosure data model.'), |
4314 | - '', |
4315 | - 'Sharing management', |
4316 | ->>>>>>> MERGE-SOURCE |
4317 | ''), |
4318 | ('garbo.workitem_migrator.enabled', |
4319 | 'boolean', |
4320 | |
4321 | === added file 'lib/lp/services/job/celeryconfig.py' |
4322 | --- lib/lp/services/job/celeryconfig.py 1970-01-01 00:00:00 +0000 |
4323 | +++ lib/lp/services/job/celeryconfig.py 2012-03-29 14:36:38 +0000 |
4324 | @@ -0,0 +1,4 @@ |
4325 | +from lp.services.config import config |
4326 | +BROKER_URL = "amqplib://%s" % config.rabbitmq.host |
4327 | +CELERY_IMPORTS = ("lp.services.job.celeryjob", ) |
4328 | +CELERY_RESULT_BACKEND = "amqp" |
4329 | |
4330 | === added file 'lib/lp/services/job/celeryjob.py' |
4331 | --- lib/lp/services/job/celeryjob.py 1970-01-01 00:00:00 +0000 |
4332 | +++ lib/lp/services/job/celeryjob.py 2012-03-29 14:36:38 +0000 |
4333 | @@ -0,0 +1,30 @@ |
4334 | +# Copyright 2012 Canonical Ltd. This software is licensed under the |
4335 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
4336 | + |
4337 | +"""Celery-specific Job code. |
4338 | + |
4339 | +Because celery sets up configuration at import time, code that is not designed |
4340 | +to use Celery may break if this is used. |
4341 | +""" |
4342 | + |
4343 | +__metaclass__ = type |
4344 | + |
4345 | +__all__ = ['CeleryRunJob'] |
4346 | + |
4347 | +import os |
4348 | + |
4349 | +os.environ.setdefault('CELERY_CONFIG_MODULE', 'lp.services.job.celeryconfig') |
4350 | +from lazr.jobrunner.celerytask import RunJob |
4351 | + |
4352 | +from lp.services.job.model.job import UniversalJobSource |
4353 | +from lp.services.job.runner import BaseJobRunner |
4354 | + |
4355 | + |
4356 | +class CeleryRunJob(RunJob): |
4357 | + """The Celery Task that runs a job.""" |
4358 | + |
4359 | + job_source = UniversalJobSource |
4360 | + |
4361 | + def getJobRunner(self): |
4362 | + """Return a BaseJobRunner, to support customization.""" |
4363 | + return BaseJobRunner() |
4364 | |
4365 | === modified file 'lib/lp/services/job/interfaces/job.py' |
4366 | --- lib/lp/services/job/interfaces/job.py 2012-02-24 21:46:11 +0000 |
4367 | +++ lib/lp/services/job/interfaces/job.py 2012-03-29 14:36:38 +0000 |
4368 | @@ -13,7 +13,6 @@ |
4369 | 'IRunnableJob', |
4370 | 'ITwistedJobSource', |
4371 | 'JobStatus', |
4372 | - 'LeaseHeld', |
4373 | ] |
4374 | |
4375 | |
4376 | @@ -38,13 +37,6 @@ |
4377 | from lp.registry.interfaces.person import IPerson |
4378 | |
4379 | |
4380 | -class LeaseHeld(Exception): |
4381 | - """Raised when attempting to acquire a list that is already held.""" |
4382 | - |
4383 | - def __init__(self): |
4384 | - Exception.__init__(self, 'Lease is already held.') |
4385 | - |
4386 | - |
4387 | class JobStatus(DBEnumeratedType): |
4388 | """Values that ICodeImportJob.state can take.""" |
4389 | |
4390 | |
4391 | === modified file 'lib/lp/services/job/model/job.py' |
4392 | --- lib/lp/services/job/model/job.py 2012-03-14 16:32:59 +0000 |
4393 | +++ lib/lp/services/job/model/job.py 2012-03-29 14:36:38 +0000 |
4394 | @@ -4,13 +4,21 @@ |
4395 | """ORM object representing jobs.""" |
4396 | |
4397 | __metaclass__ = type |
4398 | -__all__ = ['InvalidTransition', 'Job', 'JobStatus'] |
4399 | +__all__ = [ |
4400 | + 'EnumeratedSubclass', |
4401 | + 'InvalidTransition', |
4402 | + 'Job', |
4403 | + 'JobStatus', |
4404 | + 'UniversalJobSource', |
4405 | + ] |
4406 | |
4407 | |
4408 | from calendar import timegm |
4409 | import datetime |
4410 | import time |
4411 | |
4412 | +from lazr.jobrunner.jobrunner import LeaseHeld |
4413 | + |
4414 | import pytz |
4415 | from sqlobject import ( |
4416 | IntCol, |
4417 | @@ -28,16 +36,18 @@ |
4418 | import transaction |
4419 | from zope.interface import implements |
4420 | |
4421 | +from lp.services.config import dbconfig |
4422 | from lp.services.database import bulk |
4423 | from lp.services.database.constants import UTC_NOW |
4424 | from lp.services.database.datetimecol import UtcDateTimeCol |
4425 | from lp.services.database.enumcol import EnumCol |
4426 | +from lp.services.database.lpstorm import IStore |
4427 | from lp.services.database.sqlbase import SQLBase |
4428 | from lp.services.job.interfaces.job import ( |
4429 | IJob, |
4430 | JobStatus, |
4431 | - LeaseHeld, |
4432 | ) |
4433 | +from lp.services import scripts |
4434 | |
4435 | |
4436 | UTC = pytz.timezone('UTC') |
4437 | @@ -201,6 +211,29 @@ |
4438 | self.lease_expires = None |
4439 | |
4440 | |
4441 | +class EnumeratedSubclass(type): |
4442 | + """Metaclass for when subclasses are assigned enums.""" |
4443 | + |
4444 | + def __init__(cls, name, bases, dict_): |
4445 | + if getattr(cls, '_subclass', None) is None: |
4446 | + cls._subclass = {} |
4447 | + job_type = dict_.get('class_job_type') |
4448 | + if job_type is not None: |
4449 | + value = cls._subclass.setdefault(job_type, cls) |
4450 | + assert value is cls, ( |
4451 | + '%s already registered to %s.' % ( |
4452 | + job_type.name, value.__name__)) |
4453 | + # Perform any additional set-up requested by class. |
4454 | + cls._register_subclass(cls) |
4455 | + |
4456 | + @staticmethod |
4457 | + def _register_subclass(cls): |
4458 | + pass |
4459 | + |
4460 | + def makeSubclass(cls, job): |
4461 | + return cls._subclass[job.job_type](job) |
4462 | + |
4463 | + |
4464 | Job.ready_jobs = Select( |
4465 | Job.id, |
4466 | And( |
4467 | @@ -208,3 +241,29 @@ |
4468 | Or(Job.lease_expires == None, Job.lease_expires < UTC_NOW), |
4469 | Or(Job.scheduled_start == None, Job.scheduled_start <= UTC_NOW), |
4470 | )) |
4471 | + |
4472 | + |
4473 | +class UniversalJobSource: |
4474 | + """Returns the RunnableJob associated with a Job.id. |
4475 | + |
4476 | + Only BranchJobs are supported at present. |
4477 | + """ |
4478 | + |
4479 | + memory_limit = 2 * (1024 ** 3) |
4480 | + |
4481 | + needs_init = True |
4482 | + |
4483 | + @classmethod |
4484 | + def get(cls, job_id): |
4485 | + if cls.needs_init: |
4486 | + scripts.execute_zcml_for_scripts(use_web_security=False) |
4487 | + cls.needs_init = False |
4488 | + |
4489 | + dbconfig.override( |
4490 | + dbuser='branchscanner', isolation_level='read_committed') |
4491 | + from lp.code.model.branchjob import ( |
4492 | + BranchJob, |
4493 | + ) |
4494 | + store = IStore(BranchJob) |
4495 | + branch_job = store.find(BranchJob, BranchJob.job == job_id).one() |
4496 | + return branch_job.makeDerived() |
4497 | |
4498 | === modified file 'lib/lp/services/job/runner.py' |
4499 | --- lib/lp/services/job/runner.py 2012-03-22 11:58:42 +0000 |
4500 | +++ lib/lp/services/job/runner.py 2012-03-29 14:36:38 +0000 |
4501 | @@ -6,6 +6,7 @@ |
4502 | __metaclass__ = type |
4503 | |
4504 | __all__ = [ |
4505 | + 'BaseJobRunner', |
4506 | 'BaseRunnableJob', |
4507 | 'BaseRunnableJobSource', |
4508 | 'JobCronScript', |
4509 | @@ -38,7 +39,10 @@ |
4510 | pool, |
4511 | ) |
4512 | from lazr.delegates import delegates |
4513 | -from lazr.jobrunner.jobrunner import JobRunner as LazrJobRunner |
4514 | +from lazr.jobrunner.jobrunner import ( |
4515 | + JobRunner as LazrJobRunner, |
4516 | + LeaseHeld, |
4517 | + ) |
4518 | import transaction |
4519 | from twisted.internet import reactor |
4520 | from twisted.internet.defer import ( |
4521 | @@ -61,7 +65,6 @@ |
4522 | from lp.services.job.interfaces.job import ( |
4523 | IJob, |
4524 | IRunnableJob, |
4525 | - LeaseHeld, |
4526 | ) |
4527 | from lp.services.mail.sendmail import ( |
4528 | MailController, |
4529 | |
4530 | === added file 'lib/lp/services/job/tests/celeryconfig.py' |
4531 | --- lib/lp/services/job/tests/celeryconfig.py 1970-01-01 00:00:00 +0000 |
4532 | +++ lib/lp/services/job/tests/celeryconfig.py 2012-03-29 14:36:38 +0000 |
4533 | @@ -0,0 +1,7 @@ |
4534 | +import os |
4535 | +BROKER_VHOST = "/" |
4536 | +CELERY_RESULT_BACKEND = "amqp" |
4537 | +CELERY_IMPORTS = ("lp.services.job.celeryjob", ) |
4538 | +CELERYD_LOG_LEVEL = 'INFO' |
4539 | +CELERYD_CONCURRENCY = 1 |
4540 | +BROKER_URL = os.environ['BROKER_URL'] |
4541 | |
4542 | === added file 'lib/lp/services/job/tests/test_celeryjob.py' |
4543 | --- lib/lp/services/job/tests/test_celeryjob.py 1970-01-01 00:00:00 +0000 |
4544 | +++ lib/lp/services/job/tests/test_celeryjob.py 2012-03-29 14:36:38 +0000 |
4545 | @@ -0,0 +1,42 @@ |
4546 | +# Copyright 2009 Canonical Ltd. This software is licensed under the |
4547 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
4548 | + |
4549 | + |
4550 | +import os |
4551 | + |
4552 | +import transaction |
4553 | + |
4554 | +from lp.code.model.branchjob import BranchScanJob |
4555 | +from lp.testing import TestCaseWithFactory |
4556 | +from lp.testing.layers import ZopelessAppServerLayer |
4557 | + |
4558 | + |
4559 | +class TestCelery(TestCaseWithFactory): |
4560 | + |
4561 | + layer = ZopelessAppServerLayer |
4562 | + |
4563 | + def test_run_scan_job(self): |
4564 | + """Running a job via Celery succeeds and emits expected output.""" |
4565 | + # Delay importing anything that uses Celery until RabbitMQLayer is |
4566 | + # running, so that config.rabbitmq.host is defined when |
4567 | + # lp.services.job.celeryconfig is loaded. |
4568 | + from lp.services.job.celeryjob import CeleryRunJob |
4569 | + from celery.exceptions import TimeoutError |
4570 | + from lazr.jobrunner.tests.test_celerytask import running |
4571 | + cmd_args = ('--config', 'lp.services.job.tests.celeryconfig') |
4572 | + env = dict(os.environ) |
4573 | + env['BROKER_URL'] = CeleryRunJob.app.conf['BROKER_URL'] |
4574 | + with running('bin/celeryd', cmd_args, env=env) as proc: |
4575 | + self.useBzrBranches() |
4576 | + db_branch, bzr_tree = self.create_branch_and_tree() |
4577 | + bzr_tree.commit( |
4578 | + 'First commit', rev_id='rev1', committer='me@example.org') |
4579 | + job = BranchScanJob.create(db_branch) |
4580 | + transaction.commit() |
4581 | + try: |
4582 | + CeleryRunJob.delay(job.job_id).wait(30) |
4583 | + except TimeoutError: |
4584 | + pass |
4585 | + self.assertIn( |
4586 | + 'Updating branch scanner status: 1 revs', proc.stderr.read()) |
4587 | + self.assertEqual(db_branch.revision_count, 1) |
4588 | |
4589 | === modified file 'lib/lp/services/job/tests/test_job.py' |
4590 | --- lib/lp/services/job/tests/test_job.py 2011-12-30 06:14:56 +0000 |
4591 | +++ lib/lp/services/job/tests/test_job.py 2012-03-29 14:36:38 +0000 |
4592 | @@ -7,6 +7,7 @@ |
4593 | import time |
4594 | |
4595 | import pytz |
4596 | +from lazr.jobrunner.jobrunner import LeaseHeld |
4597 | from storm.locals import Store |
4598 | |
4599 | from lp.services.database.constants import UTC_NOW |
4600 | @@ -18,7 +19,6 @@ |
4601 | from lp.services.job.model.job import ( |
4602 | InvalidTransition, |
4603 | Job, |
4604 | - LeaseHeld, |
4605 | ) |
4606 | from lp.services.webapp.testing import verifyObject |
4607 | from lp.testing import ( |
4608 | |
4609 | === modified file 'lib/lp/services/job/tests/test_runner.py' |
4610 | --- lib/lp/services/job/tests/test_runner.py 2012-03-29 14:36:36 +0000 |
4611 | +++ lib/lp/services/job/tests/test_runner.py 2012-03-29 14:36:38 +0000 |
4612 | @@ -8,7 +8,10 @@ |
4613 | from textwrap import dedent |
4614 | from time import sleep |
4615 | |
4616 | -from lazr.jobrunner.jobrunner import SuspendJobException |
4617 | +from lazr.jobrunner.jobrunner import ( |
4618 | + LeaseHeld, |
4619 | + SuspendJobException, |
4620 | + ) |
4621 | from testtools.matchers import MatchesRegex |
4622 | from testtools.testcase import ExpectedException |
4623 | import transaction |
4624 | @@ -19,7 +22,6 @@ |
4625 | from lp.services.job.interfaces.job import ( |
4626 | IRunnableJob, |
4627 | JobStatus, |
4628 | - LeaseHeld, |
4629 | ) |
4630 | from lp.services.job.model.job import Job |
4631 | from lp.services.job.runner import ( |
4632 | @@ -554,20 +556,11 @@ |
4633 | self.assertEqual(expected_exception, (oops['type'], oops['value'])) |
4634 | self.assertThat(logger.getLogBuffer(), MatchesRegex( |
4635 | dedent("""\ |
4636 | -<<<<<<< TREE |
4637 | INFO Running through Twisted. |
4638 | INFO Running <StuckJob.*?> \(ID .*?\). |
4639 | INFO Running <StuckJob.*?> \(ID .*?\). |
4640 | INFO Job resulted in OOPS: .* |
4641 | """))) |
4642 | -======= |
4643 | - INFO Running through Twisted. |
4644 | - INFO Running StuckJob \(ID .*\). |
4645 | - INFO Running StuckJob \(ID .*\). |
4646 | - ERROR OOPS while executing job \d+: \['OOPS-.*\] %s\('%s',\) |
4647 | - INFO Job resulted in OOPS: .* |
4648 | - """ % (expected_exception)))) |
4649 | ->>>>>>> MERGE-SOURCE |
4650 | |
4651 | def test_timeout_short(self): |
4652 | """When a job exceeds its lease, an exception is raised. |
4653 | @@ -590,16 +583,10 @@ |
4654 | logger.getLogBuffer(), MatchesRegex( |
4655 | dedent("""\ |
4656 | INFO Running through Twisted. |
4657 | -<<<<<<< TREE |
4658 | - INFO Running <ShorterStuckJob.*?> \(ID .*?\). |
4659 | - INFO Running <ShorterStuckJob.*?> \(ID .*?\). |
4660 | -======= |
4661 | - INFO Running ShorterStuckJob \(ID .*\). |
4662 | - INFO Running ShorterStuckJob \(ID .*\). |
4663 | - ERROR OOPS while executing job \d+: \['OOPS-.*\] %s\('%s',\) |
4664 | ->>>>>>> MERGE-SOURCE |
4665 | + INFO Running <ShorterStuckJob.*?> \(ID .*?\). |
4666 | + INFO Running <ShorterStuckJob.*?> \(ID .*?\). |
4667 | INFO Job resulted in OOPS: %s |
4668 | - """) % ('TimeoutError', 'Job ran too long.', oops['id']))) |
4669 | + """) % oops['id'])) |
4670 | self.assertEqual(('TimeoutError', 'Job ran too long.'), |
4671 | (oops['type'], oops['value'])) |
4672 | |
4673 | |
4674 | === modified file 'lib/lp/testing/factory.py' |
4675 | --- lib/lp/testing/factory.py 2012-03-29 14:36:36 +0000 |
4676 | +++ lib/lp/testing/factory.py 2012-03-29 14:36:38 +0000 |
4677 | @@ -1407,7 +1407,7 @@ |
4678 | # We just remove the security proxies to be able to change the objects |
4679 | # here. |
4680 | removeSecurityProxy(branch).branchChanged( |
4681 | - '', 'rev1', None, None, None) |
4682 | + '', 'rev1', None, None, None, celery_scan=False) |
4683 | naked_series = removeSecurityProxy(product.development_focus) |
4684 | naked_series.branch = branch |
4685 | return branch |
4686 | @@ -1422,7 +1422,7 @@ |
4687 | # We just remove the security proxies to be able to change the branch |
4688 | # here. |
4689 | removeSecurityProxy(branch).branchChanged( |
4690 | - '', 'rev1', None, None, None) |
4691 | + '', 'rev1', None, None, None, celery_scan=False) |
4692 | with person_logged_in(package.distribution.owner): |
4693 | package.development_version.setBranch( |
4694 | PackagePublishingPocket.RELEASE, branch, |
4695 | @@ -1628,7 +1628,7 @@ |
4696 | if branch.branch_type not in (BranchType.REMOTE, BranchType.HOSTED): |
4697 | branch.startMirroring() |
4698 | removeSecurityProxy(branch).branchChanged( |
4699 | - '', parent.revision_id, None, None, None) |
4700 | + '', parent.revision_id, None, None, None, celery_scan=False) |
4701 | branch.updateScannedDetails(parent, sequence) |
4702 | |
4703 | def makeBranchRevision(self, branch, revision_id=None, sequence=None, |
4704 | |
4705 | === modified file 'lib/lp/testing/yuixhr.py' |
4706 | --- lib/lp/testing/yuixhr.py 2012-03-29 14:36:36 +0000 |
4707 | +++ lib/lp/testing/yuixhr.py 2012-03-23 18:07:23 +0000 |
4708 | @@ -235,7 +235,6 @@ |
4709 | <!DOCTYPE html> |
4710 | <html> |
4711 | <head> |
4712 | -<<<<<<< TREE |
4713 | <title>Test</title> |
4714 | %(javascript_block)s |
4715 | <script type="text/javascript"> |
4716 | @@ -253,45 +252,6 @@ |
4717 | <link rel="stylesheet" href="/+icing/rev%(revno)s/combo.css"/> |
4718 | <script type="text/javascript" src="%(test_module)s"></script> |
4719 | </head> |
4720 | -======= |
4721 | - <title>Test</title> |
4722 | - <script type="text/javascript" |
4723 | - src="/+icing/rev%(revno)s/build/launchpad.js"></script> |
4724 | - <script type="text/javascript"> |
4725 | - YUI.GlobalConfig = { |
4726 | - fetchCSS: false, |
4727 | - timeout: 50, |
4728 | - ignore: [ |
4729 | - 'yui2-yahoo', 'yui2-event', 'yui2-dom', |
4730 | - 'yui2-calendar','yui2-dom-event' |
4731 | - ] |
4732 | - } |
4733 | - </script> |
4734 | - <link rel="stylesheet" |
4735 | - href="/+icing/yui/assets/skins/sam/skin.css"/> |
4736 | - <link rel="stylesheet" href="/+icing/rev%(revno)s/combo.css"/> |
4737 | - <style> |
4738 | - /* Taken and customized from testlogger.css */ |
4739 | - .yui-console-entry-src { display:none; } |
4740 | - .yui-console-entry.yui-console-entry-pass .yui-console-entry-cat { |
4741 | - background-color: green; |
4742 | - font-weight: bold; |
4743 | - color: white; |
4744 | - } |
4745 | - .yui-console-entry.yui-console-entry-fail .yui-console-entry-cat { |
4746 | - background-color: red; |
4747 | - font-weight: bold; |
4748 | - color: white; |
4749 | - } |
4750 | - .yui-console-entry.yui-console-entry-ignore .yui-console-entry-cat { |
4751 | - background-color: #666; |
4752 | - font-weight: bold; |
4753 | - color: white; |
4754 | - } |
4755 | - </style> |
4756 | - <script type="text/javascript" src="%(test_module)s"></script> |
4757 | - </head> |
4758 | ->>>>>>> MERGE-SOURCE |
4759 | <body class="yui3-skin-sam"> |
4760 | <div id="log"></div> |
4761 | <p>Want to re-run your test?</p> |
4762 | |
4763 | === modified file 'lib/lp/translations/model/translationsharingjob.py' |
4764 | --- lib/lp/translations/model/translationsharingjob.py 2011-12-30 06:14:56 +0000 |
4765 | +++ lib/lp/translations/model/translationsharingjob.py 2012-03-29 14:36:38 +0000 |
4766 | @@ -31,7 +31,10 @@ |
4767 | IJob, |
4768 | JobStatus, |
4769 | ) |
4770 | -from lp.services.job.model.job import Job |
4771 | +from lp.services.job.model.job import ( |
4772 | + EnumeratedSubclass, |
4773 | + Job, |
4774 | + ) |
4775 | from lp.translations.interfaces.translationsharingjob import ( |
4776 | ITranslationSharingJob, |
4777 | ) |
4778 | @@ -108,21 +111,13 @@ |
4779 | self.potemplate = potemplate |
4780 | |
4781 | |
4782 | -class RegisteredSubclass(type): |
4783 | - """Metaclass for when subclasses should be registered.""" |
4784 | - |
4785 | - def __init__(cls, name, bases, dict_): |
4786 | - cls._register_subclass(cls) |
4787 | - |
4788 | - |
4789 | class TranslationSharingJobDerived: |
4790 | """Base class for specialized TranslationTemplate Job types.""" |
4791 | |
4792 | - __metaclass__ = RegisteredSubclass |
4793 | + __metaclass__ = EnumeratedSubclass |
4794 | |
4795 | delegates(ITranslationSharingJob, 'job') |
4796 | |
4797 | - _subclass = {} |
4798 | _event_types = {} |
4799 | |
4800 | @property |
4801 | @@ -136,12 +131,6 @@ |
4802 | # TranslationPackagingJob) need to be able to override it and call |
4803 | # into it, and there's no syntax to call a base class's version of a |
4804 | # classmethod with the subclass as the first parameter. |
4805 | - job_type = getattr(cls, 'class_job_type', None) |
4806 | - if job_type is not None: |
4807 | - value = cls._subclass.setdefault(job_type, cls) |
4808 | - assert value is cls, ( |
4809 | - '%s already registered to %s.' % ( |
4810 | - job_type.name, value.__name__)) |
4811 | event_type = getattr(cls, 'create_on_event', None) |
4812 | if event_type is not None: |
4813 | cls._event_types.setdefault(event_type, []).append(cls) |
4814 | @@ -215,7 +204,7 @@ |
4815 | TranslationSharingJob.job == Job.id, |
4816 | Job.id.is_in(Job.ready_jobs), |
4817 | *extra_clauses) |
4818 | - return (cls._subclass[job.job_type](job) for job in jobs) |
4819 | + return (cls.makeSubclass(job) for job in jobs) |
4820 | |
4821 | @classmethod |
4822 | def getNextJobStatus(cls, packaging): |
4823 | |
4824 | === modified file 'versions.cfg' |
4825 | --- versions.cfg 2012-03-29 14:36:36 +0000 |
4826 | +++ versions.cfg 2012-03-27 17:29:47 +0000 |
4827 | @@ -42,11 +42,7 @@ |
4828 | lazr.config = 1.1.3 |
4829 | lazr.delegates = 1.2.0 |
4830 | lazr.enum = 1.1.3 |
4831 | -<<<<<<< TREE |
4832 | lazr.jobrunner = 0.2 |
4833 | -======= |
4834 | -lazr.jobrunner = 0.1 |
4835 | ->>>>>>> MERGE-SOURCE |
4836 | lazr.lifecycle = 1.1 |
4837 | lazr.restful = 0.19.6 |
4838 | lazr.restfulclient = 0.12.0 |
4839 | @@ -87,12 +83,8 @@ |
4840 | pymongo = 2.1.1 |
4841 | pyOpenSSL = 0.10 |
4842 | pystache = 0.3.1 |
4843 | -<<<<<<< TREE |
4844 | python-dateutil = 1.5 |
4845 | python-memcached = 1.48 |
4846 | -======= |
4847 | -python-memcached = 1.48 |
4848 | ->>>>>>> MERGE-SOURCE |
4849 | # 2.2.1 with the one-liner Expect: 100-continue fix from |
4850 | # lp:~wgrant/python-openid/python-openid-2.2.1-fix676372. |
4851 | python-openid = 2.2.1-fix676372 |
Overall, a very nice branch.
As discused on IRC, calling CeleryRunJob. delay(BrachScan Job.create( ...)) creates a race condition.
Another remark:
> === modified file 'lib/lp/ code/model/ branch. py' code/model/ branch. py 2012-03-21 12:34:12 +0000 code/model/ branch. py 2012-03-27 14:40:29 +0000 IBranchLookup) .getByUniqueNam e(location)
> --- lib/lp/
> +++ lib/lp/
> @@ -1032,7 +1032,8 @@
> return getUtility(
>
> def branchChanged(self, stacked_on_url, last_revision_id,
> - control_format, branch_format, repository_format):
> + control_format, branch_format, repository_format,
> + skip_celery=False):
This changes needs a related change in the interface class. I think
that the new parameter deserves a short explanation: As I understand it,
skip_celery should be False only in tests.