Merge lp:~lifeless/launchpad/merge into lp:launchpad/db-devel
- merge
- Merge into db-devel
Proposed by
Robert Collins
Status: | Rejected |
---|---|
Rejected by: | Robert Collins |
Proposed branch: | lp:~lifeless/launchpad/merge |
Merge into: | lp:launchpad/db-devel |
Diff against target: |
1823 lines (+855/-273) (has conflicts) 38 files modified
database/schema/security.cfg (+1/-0) lib/canonical/launchpad/doc/security-teams.txt (+2/-1) lib/canonical/launchpad/mail/commands.py (+1/-1) lib/lp/archiveuploader/tests/test_ppauploadprocessor.py (+8/-8) lib/lp/bugs/browser/bug.py (+18/-9) lib/lp/bugs/browser/bugtarget.py (+10/-1) lib/lp/bugs/configure.zcml (+1/-0) lib/lp/bugs/doc/bug-heat.txt (+22/-0) lib/lp/bugs/doc/bug.txt (+1/-1) lib/lp/bugs/doc/bugnotification-email.txt (+3/-3) lib/lp/bugs/doc/bugnotification-sending.txt (+2/-1) lib/lp/bugs/interfaces/bug.py (+12/-1) lib/lp/bugs/model/bug.py (+27/-0) lib/lp/bugs/scripts/bugheat.py (+1/-0) lib/lp/bugs/scripts/bugimport.py (+1/-1) lib/lp/bugs/scripts/tests/test_bugheat.py (+2/-2) lib/lp/bugs/tests/test_bugchanges.py (+2/-2) lib/lp/code/browser/branch.py (+13/-14) lib/lp/code/configure.zcml (+2/-1) lib/lp/code/errors.py (+23/-0) lib/lp/code/interfaces/branchmergeproposal.py (+3/-0) lib/lp/code/interfaces/codeimport.py (+29/-1) lib/lp/code/interfaces/webservice.py (+11/-0) lib/lp/code/model/branchmergeproposal.py (+11/-10) lib/lp/code/model/branchtarget.py (+20/-0) lib/lp/code/model/codeimport.py (+24/-0) lib/lp/code/model/tests/test_branchmergeproposals.py (+39/-0) lib/lp/code/model/tests/test_codeimport.py (+67/-0) lib/lp/code/stories/webservice/xx-code-import.txt (+368/-168) lib/lp/services/worlddata/doc/language.txt (+33/-1) lib/lp/services/worlddata/interfaces/language.py (+3/-2) lib/lp/services/worlddata/tests/test_doc.py (+12/-1) lib/lp/services/worlddata/tests/test_language.py (+21/-0) lib/lp/soyuz/doc/archive.txt (+2/-2) lib/lp/soyuz/doc/distroseriesqueue-translations.txt (+43/-9) lib/lp/soyuz/model/archive.py (+1/-1) lib/lp/soyuz/model/queue.py (+6/-3) lib/lp/translations/templates/language-index.pt (+10/-29) Text conflict in lib/lp/code/interfaces/webservice.py Text conflict in lib/lp/code/model/branchtarget.py Text conflict in lib/lp/code/stories/webservice/xx-code-import.txt |
To merge this branch: | bzr merge lp:~lifeless/launchpad/merge |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Canonical Launchpad Engineering | Pending | ||
Review via email: mp+23521@code.launchpad.net |
Commit message
Description of the change
Fix a few bugs around merge proposals:
- when transitioning out of 'queued', always dequeue.
- permit transitioning to merge-failed
- use the reviewed revid by default when queueing
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'Makefile' | |||
2 | === modified file 'configs/testrunner/launchpad-lazr.conf' | |||
3 | === modified file 'database/schema/security.cfg' | |||
4 | --- database/schema/security.cfg 2010-04-13 20:23:15 +0000 | |||
5 | +++ database/schema/security.cfg 2010-04-16 04:01:13 +0000 | |||
6 | @@ -1263,6 +1263,7 @@ | |||
7 | 1263 | public.question = SELECT | 1263 | public.question = SELECT |
8 | 1264 | public.questionbug = SELECT | 1264 | public.questionbug = SELECT |
9 | 1265 | public.distribution = SELECT | 1265 | public.distribution = SELECT |
10 | 1266 | public.distributionsourcepackage = SELECT, INSERT, UPDATE | ||
11 | 1266 | public.distroseries = SELECT | 1267 | public.distroseries = SELECT |
12 | 1267 | public.sourcepackagename = SELECT | 1268 | public.sourcepackagename = SELECT |
13 | 1268 | public.sourcepackagerelease = SELECT | 1269 | public.sourcepackagerelease = SELECT |
14 | 1269 | 1270 | ||
15 | === modified file 'lib/canonical/launchpad/doc/security-teams.txt' | |||
16 | --- lib/canonical/launchpad/doc/security-teams.txt 2009-08-14 12:59:56 +0000 | |||
17 | +++ lib/canonical/launchpad/doc/security-teams.txt 2010-04-16 04:01:13 +0000 | |||
18 | @@ -268,7 +268,8 @@ | |||
19 | 268 | >>> bug.addTask(owner=reporter, target=distribution) | 268 | >>> bug.addTask(owner=reporter, target=distribution) |
20 | 269 | <BugTask at ...> | 269 | <BugTask at ...> |
21 | 270 | >>> old_state = Snapshot(bug, providing=IBug) | 270 | >>> old_state = Snapshot(bug, providing=IBug) |
23 | 271 | >>> bug.security_related = True | 271 | >>> bug.setSecurityRelated(True) |
24 | 272 | True | ||
25 | 272 | >>> notify(ObjectModifiedEvent(bug, old_state, ['security_related'])) | 273 | >>> notify(ObjectModifiedEvent(bug, old_state, ['security_related'])) |
26 | 273 | >>> for subscriber_name in sorted( | 274 | >>> for subscriber_name in sorted( |
27 | 274 | ... s.displayname for s in bug.getDirectSubscribers()): | 275 | ... s.displayname for s in bug.getDirectSubscribers()): |
28 | 275 | 276 | ||
29 | === modified file 'lib/canonical/launchpad/mail/commands.py' | |||
30 | --- lib/canonical/launchpad/mail/commands.py 2010-02-17 11:13:06 +0000 | |||
31 | +++ lib/canonical/launchpad/mail/commands.py 2010-04-16 04:01:13 +0000 | |||
32 | @@ -279,7 +279,7 @@ | |||
33 | 279 | edited = True | 279 | edited = True |
34 | 280 | edited_fields.add('private') | 280 | edited_fields.add('private') |
35 | 281 | if context.security_related != security_related: | 281 | if context.security_related != security_related: |
37 | 282 | context.security_related = security_related | 282 | context.setSecurityRelated(security_related) |
38 | 283 | edited = True | 283 | edited = True |
39 | 284 | edited_fields.add('security_related') | 284 | edited_fields.add('security_related') |
40 | 285 | 285 | ||
41 | 286 | 286 | ||
42 | === modified file 'lib/lp/archiveuploader/tests/test_ppauploadprocessor.py' | |||
43 | --- lib/lp/archiveuploader/tests/test_ppauploadprocessor.py 2010-04-12 15:02:30 +0000 | |||
44 | +++ lib/lp/archiveuploader/tests/test_ppauploadprocessor.py 2010-04-16 04:01:13 +0000 | |||
45 | @@ -1252,9 +1252,9 @@ | |||
46 | 1252 | the size of the upload plus the current PPA size must be smaller | 1252 | the size of the upload plus the current PPA size must be smaller |
47 | 1253 | than the PPA.authorized_size, otherwise the upload will be rejected. | 1253 | than the PPA.authorized_size, otherwise the upload will be rejected. |
48 | 1254 | """ | 1254 | """ |
52 | 1255 | # Stuff 1024 MiB in name16 PPA, so anything will be above the | 1255 | # Stuff 2048 MiB in name16 PPA, so anything will be above the |
53 | 1256 | # default quota limit, 1024 MiB. | 1256 | # default quota limit, 2048 MiB. |
54 | 1257 | self._fillArchive(self.name16.archive, 1024 * (2 ** 20)) | 1257 | self._fillArchive(self.name16.archive, 2048 * (2 ** 20)) |
55 | 1258 | 1258 | ||
56 | 1259 | upload_dir = self.queueUpload("bar_1.0-1", "~name16/ubuntu") | 1259 | upload_dir = self.queueUpload("bar_1.0-1", "~name16/ubuntu") |
57 | 1260 | upload_results = self.processUpload(self.uploadprocessor, upload_dir) | 1260 | upload_results = self.processUpload(self.uploadprocessor, upload_dir) |
58 | @@ -1267,7 +1267,7 @@ | |||
59 | 1267 | contents = [ | 1267 | contents = [ |
60 | 1268 | "Subject: bar_1.0-1_source.changes rejected", | 1268 | "Subject: bar_1.0-1_source.changes rejected", |
61 | 1269 | "Rejected:", | 1269 | "Rejected:", |
63 | 1270 | "PPA exceeded its size limit (1024.00 of 1024.00 MiB). " | 1270 | "PPA exceeded its size limit (2048.00 of 2048.00 MiB). " |
64 | 1271 | "Ask a question in https://answers.launchpad.net/soyuz/ " | 1271 | "Ask a question in https://answers.launchpad.net/soyuz/ " |
65 | 1272 | "if you need more space."] | 1272 | "if you need more space."] |
66 | 1273 | self.assertEmail(contents) | 1273 | self.assertEmail(contents) |
67 | @@ -1278,9 +1278,9 @@ | |||
68 | 1278 | The system start warning users for uploads exceeding 95 % of | 1278 | The system start warning users for uploads exceeding 95 % of |
69 | 1279 | the current size limit. | 1279 | the current size limit. |
70 | 1280 | """ | 1280 | """ |
74 | 1281 | # Stuff 973 MiB into name16 PPA, approximately 95 % of | 1281 | # Stuff 1945 MiB into name16 PPA, approximately 95 % of |
75 | 1282 | # the default quota limit, 1024 MiB. | 1282 | # the default quota limit, 2048 MiB. |
76 | 1283 | self._fillArchive(self.name16.archive, 973 * (2 ** 20)) | 1283 | self._fillArchive(self.name16.archive, 2000 * (2 ** 20)) |
77 | 1284 | 1284 | ||
78 | 1285 | # Ensure the warning is sent in the acceptance notification. | 1285 | # Ensure the warning is sent in the acceptance notification. |
79 | 1286 | upload_dir = self.queueUpload("bar_1.0-1", "~name16/ubuntu") | 1286 | upload_dir = self.queueUpload("bar_1.0-1", "~name16/ubuntu") |
80 | @@ -1288,7 +1288,7 @@ | |||
81 | 1288 | contents = [ | 1288 | contents = [ |
82 | 1289 | "Subject: [PPA name16] [ubuntu/breezy] bar 1.0-1 (Accepted)", | 1289 | "Subject: [PPA name16] [ubuntu/breezy] bar 1.0-1 (Accepted)", |
83 | 1290 | "Upload Warnings:", | 1290 | "Upload Warnings:", |
85 | 1291 | "PPA exceeded 95 % of its size limit (973.00 of 1024.00 MiB). " | 1291 | "PPA exceeded 95 % of its size limit (2000.00 of 2048.00 MiB). " |
86 | 1292 | "Ask a question in https://answers.launchpad.net/soyuz/ " | 1292 | "Ask a question in https://answers.launchpad.net/soyuz/ " |
87 | 1293 | "if you need more space."] | 1293 | "if you need more space."] |
88 | 1294 | self.assertEmail(contents) | 1294 | self.assertEmail(contents) |
89 | 1295 | 1295 | ||
90 | === modified file 'lib/lp/bugs/browser/bug.py' | |||
91 | --- lib/lp/bugs/browser/bug.py 2010-04-07 11:28:32 +0000 | |||
92 | +++ lib/lp/bugs/browser/bug.py 2010-04-16 04:01:13 +0000 | |||
93 | @@ -673,7 +673,7 @@ | |||
94 | 673 | page_title = label | 673 | page_title = label |
95 | 674 | 674 | ||
96 | 675 | def setUpFields(self): | 675 | def setUpFields(self): |
98 | 676 | """Make the read-only version of `private` writable.""" | 676 | """Make the read-only version of the form fields writable.""" |
99 | 677 | private_field = Bool( | 677 | private_field = Bool( |
100 | 678 | __name__='private', | 678 | __name__='private', |
101 | 679 | title=_("This bug report should be private"), | 679 | title=_("This bug report should be private"), |
102 | @@ -681,10 +681,17 @@ | |||
103 | 681 | description=_("Private bug reports are visible only to " | 681 | description=_("Private bug reports are visible only to " |
104 | 682 | "their subscribers."), | 682 | "their subscribers."), |
105 | 683 | default=False) | 683 | default=False) |
106 | 684 | security_related_field = Bool( | ||
107 | 685 | __name__='security_related', | ||
108 | 686 | title=_("This bug is a security vulnerability"), | ||
109 | 687 | required=False, default=False) | ||
110 | 688 | |||
111 | 684 | super(BugSecrecyEditView, self).setUpFields() | 689 | super(BugSecrecyEditView, self).setUpFields() |
112 | 685 | self.form_fields = self.form_fields.omit('private') | 690 | self.form_fields = self.form_fields.omit('private') |
113 | 691 | self.form_fields = self.form_fields.omit('security_related') | ||
114 | 686 | self.form_fields = ( | 692 | self.form_fields = ( |
116 | 687 | formlib.form.Fields(private_field) + self.form_fields) | 693 | formlib.form.Fields(private_field) + |
117 | 694 | formlib.form.Fields(security_related_field)) | ||
118 | 688 | 695 | ||
119 | 689 | @property | 696 | @property |
120 | 690 | def initial_values(self): | 697 | def initial_values(self): |
121 | @@ -705,16 +712,18 @@ | |||
122 | 705 | bug_before_modification = Snapshot( | 712 | bug_before_modification = Snapshot( |
123 | 706 | bug, providing=providedBy(bug)) | 713 | bug, providing=providedBy(bug)) |
124 | 707 | private = data.pop('private') | 714 | private = data.pop('private') |
125 | 715 | security_related = data.pop('security_related') | ||
126 | 708 | private_changed = bug.setPrivate( | 716 | private_changed = bug.setPrivate( |
127 | 709 | private, getUtility(ILaunchBag).user) | 717 | private, getUtility(ILaunchBag).user) |
134 | 710 | if private_changed: | 718 | security_related_changed = bug.setSecurityRelated(security_related) |
135 | 711 | # Although the call to updateBugFromData later on will | 719 | if private_changed or security_related_changed: |
136 | 712 | # send notification of changes, it will only do so if it | 720 | changed_fields = [] |
137 | 713 | # makes the change. We have applied the 'private' change | 721 | if private_changed: |
138 | 714 | # already, so updateBugFromData will only send an event if | 722 | changed_fields.append('private') |
139 | 715 | # 'security_related' is changed, and we can't have that. | 723 | if security_related_changed: |
140 | 724 | changed_fields.append('security_related') | ||
141 | 716 | notify(ObjectModifiedEvent( | 725 | notify(ObjectModifiedEvent( |
143 | 717 | bug, bug_before_modification, ['private'])) | 726 | bug, bug_before_modification, changed_fields)) |
144 | 718 | 727 | ||
145 | 719 | # Apply other changes. | 728 | # Apply other changes. |
146 | 720 | self.updateBugFromData(data) | 729 | self.updateBugFromData(data) |
147 | 721 | 730 | ||
148 | === modified file 'lib/lp/bugs/browser/bugtarget.py' | |||
149 | --- lib/lp/bugs/browser/bugtarget.py 2010-03-16 16:23:50 +0000 | |||
150 | +++ lib/lp/bugs/browser/bugtarget.py 2010-04-16 04:01:13 +0000 | |||
151 | @@ -36,7 +36,7 @@ | |||
152 | 36 | from zope.interface import implements | 36 | from zope.interface import implements |
153 | 37 | from zope.publisher.interfaces import NotFound | 37 | from zope.publisher.interfaces import NotFound |
154 | 38 | from zope.publisher.interfaces.browser import IBrowserPublisher | 38 | from zope.publisher.interfaces.browser import IBrowserPublisher |
156 | 39 | from zope.schema import Choice | 39 | from zope.schema import Bool, Choice |
157 | 40 | from zope.schema.vocabulary import SimpleVocabulary | 40 | from zope.schema.vocabulary import SimpleVocabulary |
158 | 41 | 41 | ||
159 | 42 | from canonical.cachedproperty import cachedproperty | 42 | from canonical.cachedproperty import cachedproperty |
160 | @@ -45,6 +45,7 @@ | |||
161 | 45 | from lp.bugs.interfaces.apportjob import IProcessApportBlobJobSource | 45 | from lp.bugs.interfaces.apportjob import IProcessApportBlobJobSource |
162 | 46 | from lp.bugs.interfaces.bug import IBug | 46 | from lp.bugs.interfaces.bug import IBug |
163 | 47 | from lp.bugs.interfaces.bugtask import BugTaskSearchParams | 47 | from lp.bugs.interfaces.bugtask import BugTaskSearchParams |
164 | 48 | from canonical.launchpad import _ | ||
165 | 48 | from canonical.launchpad.browser.feeds import ( | 49 | from canonical.launchpad.browser.feeds import ( |
166 | 49 | BugFeedLink, BugTargetLatestBugsFeedLink, FeedsMixin) | 50 | BugFeedLink, BugTargetLatestBugsFeedLink, FeedsMixin) |
167 | 50 | from lp.bugs.interfaces.bugsupervisor import IHasBugSupervisor | 51 | from lp.bugs.interfaces.bugsupervisor import IHasBugSupervisor |
168 | @@ -297,6 +298,14 @@ | |||
169 | 297 | self.form_fields = self.form_fields.omit('subscribe_to_existing_bug') | 298 | self.form_fields = self.form_fields.omit('subscribe_to_existing_bug') |
170 | 298 | self.form_fields += formlib.form.Fields(subscribe_field) | 299 | self.form_fields += formlib.form.Fields(subscribe_field) |
171 | 299 | 300 | ||
172 | 301 | security_related_field = Bool( | ||
173 | 302 | __name__='security_related', | ||
174 | 303 | title=_("This bug is a security vulnerability"), | ||
175 | 304 | required=False, default=False) | ||
176 | 305 | |||
177 | 306 | self.form_fields = self.form_fields.omit('security_related') | ||
178 | 307 | self.form_fields += formlib.form.Fields(security_related_field) | ||
179 | 308 | |||
180 | 300 | def contextUsesMalone(self): | 309 | def contextUsesMalone(self): |
181 | 301 | """Does the context use Malone as its official bugtracker?""" | 310 | """Does the context use Malone as its official bugtracker?""" |
182 | 302 | if IProjectGroup.providedBy(self.context): | 311 | if IProjectGroup.providedBy(self.context): |
183 | 303 | 312 | ||
184 | === modified file 'lib/lp/bugs/configure.zcml' | |||
185 | --- lib/lp/bugs/configure.zcml 2010-04-08 08:55:10 +0000 | |||
186 | +++ lib/lp/bugs/configure.zcml 2010-04-16 04:01:13 +0000 | |||
187 | @@ -681,6 +681,7 @@ | |||
188 | 681 | expireNotifications | 681 | expireNotifications |
189 | 682 | setStatus | 682 | setStatus |
190 | 683 | setPrivate | 683 | setPrivate |
191 | 684 | setSecurityRelated | ||
192 | 684 | convertToQuestion | 685 | convertToQuestion |
193 | 685 | markUserAffected | 686 | markUserAffected |
194 | 686 | addTask | 687 | addTask |
195 | 687 | 688 | ||
196 | === modified file 'lib/lp/bugs/doc/bug-heat.txt' | |||
197 | --- lib/lp/bugs/doc/bug-heat.txt 2010-04-12 07:11:47 +0000 | |||
198 | +++ lib/lp/bugs/doc/bug-heat.txt 2010-04-16 04:01:13 +0000 | |||
199 | @@ -29,6 +29,28 @@ | |||
200 | 29 | datetime.datetime(..., tzinfo=<UTC>) | 29 | datetime.datetime(..., tzinfo=<UTC>) |
201 | 30 | 30 | ||
202 | 31 | 31 | ||
203 | 32 | Adjusting bug heat in transaction | ||
204 | 33 | --------------------------------- | ||
205 | 34 | |||
206 | 35 | Sometimes, when a bug changes, we want to see the changes reflected in the bug's | ||
207 | 36 | heat value immidiately, without waiting for heat to be recalculated. Currently | ||
208 | 37 | we adjust heat immidiately for bug privacy and security. | ||
209 | 38 | |||
210 | 39 | >>> bug_owner = factory.makePerson() | ||
211 | 40 | >>> bug = factory.makeBug(owner=bug_owner) | ||
212 | 41 | >>> bug.heat | ||
213 | 42 | 0 | ||
214 | 43 | >>> changed = bug.setPrivate(True, bug_owner) | ||
215 | 44 | >>> bug.heat | ||
216 | 45 | 150 | ||
217 | 46 | >>> changed = bug.setSecurityRelated(True) | ||
218 | 47 | >>> bug.heat | ||
219 | 48 | 400 | ||
220 | 49 | >>> changed = bug.setPrivate(False, bug_owner) | ||
221 | 50 | >>> bug.heat | ||
222 | 51 | 250 | ||
223 | 52 | |||
224 | 53 | |||
225 | 32 | Getting bugs whose heat is outdated | 54 | Getting bugs whose heat is outdated |
226 | 33 | ----------------------------------- | 55 | ----------------------------------- |
227 | 34 | 56 | ||
228 | 35 | 57 | ||
229 | === modified file 'lib/lp/bugs/doc/bug.txt' | |||
230 | --- lib/lp/bugs/doc/bug.txt 2010-02-11 05:08:47 +0000 | |||
231 | +++ lib/lp/bugs/doc/bug.txt 2010-04-16 04:01:13 +0000 | |||
232 | @@ -747,7 +747,7 @@ | |||
233 | 747 | 747 | ||
234 | 748 | >>> firefox_bug.security_related | 748 | >>> firefox_bug.security_related |
235 | 749 | False | 749 | False |
237 | 750 | >>> firefox_bug.security_related = True | 750 | >>> changed = firefox_bug.setSecurityRelated(True) |
238 | 751 | 751 | ||
239 | 752 | >>> bug_security_changed = ObjectModifiedEvent( | 752 | >>> bug_security_changed = ObjectModifiedEvent( |
240 | 753 | ... firefox_bug, bug_before_modification, ["security_related"]) | 753 | ... firefox_bug, bug_before_modification, ["security_related"]) |
241 | 754 | 754 | ||
242 | === modified file 'lib/lp/bugs/doc/bugnotification-email.txt' | |||
243 | --- lib/lp/bugs/doc/bugnotification-email.txt 2010-01-20 17:09:40 +0000 | |||
244 | +++ lib/lp/bugs/doc/bugnotification-email.txt 2010-04-16 04:01:13 +0000 | |||
245 | @@ -85,7 +85,7 @@ | |||
246 | 85 | 85 | ||
247 | 86 | New security related bugs are sent with a prominent warning: | 86 | New security related bugs are sent with a prominent warning: |
248 | 87 | 87 | ||
250 | 88 | >>> bug_four.security_related = True | 88 | >>> changed = bug_four.setSecurityRelated(True) |
251 | 89 | 89 | ||
252 | 90 | >>> subject, body = generate_bug_add_email(bug_four) | 90 | >>> subject, body = generate_bug_add_email(bug_four) |
253 | 91 | >>> subject | 91 | >>> subject |
254 | @@ -202,7 +202,7 @@ | |||
255 | 202 | 202 | ||
256 | 203 | >>> edited_bug.setPrivate(True, getUtility(ILaunchBag).user) | 203 | >>> edited_bug.setPrivate(True, getUtility(ILaunchBag).user) |
257 | 204 | True | 204 | True |
259 | 205 | >>> edited_bug.security_related = True | 205 | >>> changed = edited_bug.setSecurityRelated(True) |
260 | 206 | >>> bug_delta = BugDelta( | 206 | >>> bug_delta = BugDelta( |
261 | 207 | ... bug=edited_bug, | 207 | ... bug=edited_bug, |
262 | 208 | ... bugurl="http://www.example.com/bugs/2", | 208 | ... bugurl="http://www.example.com/bugs/2", |
263 | @@ -225,7 +225,7 @@ | |||
264 | 225 | 225 | ||
265 | 226 | >>> edited_bug.setPrivate(False, getUtility(ILaunchBag).user) | 226 | >>> edited_bug.setPrivate(False, getUtility(ILaunchBag).user) |
266 | 227 | True | 227 | True |
268 | 228 | >>> edited_bug.security_related = False | 228 | >>> changed = edited_bug.setSecurityRelated(False) |
269 | 229 | >>> bug_delta = BugDelta( | 229 | >>> bug_delta = BugDelta( |
270 | 230 | ... bug=edited_bug, | 230 | ... bug=edited_bug, |
271 | 231 | ... bugurl="http://www.example.com/bugs/2", | 231 | ... bugurl="http://www.example.com/bugs/2", |
272 | 232 | 232 | ||
273 | === modified file 'lib/lp/bugs/doc/bugnotification-sending.txt' | |||
274 | --- lib/lp/bugs/doc/bugnotification-sending.txt 2010-04-01 03:14:47 +0000 | |||
275 | +++ lib/lp/bugs/doc/bugnotification-sending.txt 2010-04-16 04:01:13 +0000 | |||
276 | @@ -1199,7 +1199,8 @@ | |||
277 | 1199 | The presence of the security flag on a bug is, surprise, denoted by a | 1199 | The presence of the security flag on a bug is, surprise, denoted by a |
278 | 1200 | simple "yes": | 1200 | simple "yes": |
279 | 1201 | 1201 | ||
281 | 1202 | >>> bug_three.security_related = True | 1202 | >>> bug_three.setSecurityRelated(True) |
282 | 1203 | True | ||
283 | 1203 | >>> bug_three.security_related | 1204 | >>> bug_three.security_related |
284 | 1204 | True | 1205 | True |
285 | 1205 | 1206 | ||
286 | 1206 | 1207 | ||
287 | === modified file 'lib/lp/bugs/interfaces/bug.py' | |||
288 | --- lib/lp/bugs/interfaces/bug.py 2010-04-12 14:48:34 +0000 | |||
289 | +++ lib/lp/bugs/interfaces/bug.py 2010-04-16 04:01:13 +0000 | |||
290 | @@ -207,7 +207,7 @@ | |||
291 | 207 | readonly=True)) | 207 | readonly=True)) |
292 | 208 | security_related = exported( | 208 | security_related = exported( |
293 | 209 | Bool(title=_("This bug is a security vulnerability"), | 209 | Bool(title=_("This bug is a security vulnerability"), |
295 | 210 | required=False, default=False)) | 210 | required=False, default=False, readonly=True)) |
296 | 211 | displayname = TextLine(title=_("Text of the form 'Bug #X"), | 211 | displayname = TextLine(title=_("Text of the form 'Bug #X"), |
297 | 212 | readonly=True) | 212 | readonly=True) |
298 | 213 | activity = Attribute('SQLObject.Multijoin of IBugActivity') | 213 | activity = Attribute('SQLObject.Multijoin of IBugActivity') |
299 | @@ -705,6 +705,17 @@ | |||
300 | 705 | Return True if a change is made, False otherwise. | 705 | Return True if a change is made, False otherwise. |
301 | 706 | """ | 706 | """ |
302 | 707 | 707 | ||
303 | 708 | @mutator_for(security_related) | ||
304 | 709 | @operation_parameters(security_related=copy_field(security_related)) | ||
305 | 710 | @export_write_operation() | ||
306 | 711 | def setSecurityRelated(security_related): | ||
307 | 712 | """Set bug security. | ||
308 | 713 | |||
309 | 714 | :security_related: True/False. | ||
310 | 715 | |||
311 | 716 | Return True if a change is made, False otherwise. | ||
312 | 717 | """ | ||
313 | 718 | |||
314 | 708 | def getBugTask(target): | 719 | def getBugTask(target): |
315 | 709 | """Return the bugtask with the specified target. | 720 | """Return the bugtask with the specified target. |
316 | 710 | 721 | ||
317 | 711 | 722 | ||
318 | === modified file 'lib/lp/bugs/model/bug.py' | |||
319 | --- lib/lp/bugs/model/bug.py 2010-04-12 14:48:34 +0000 | |||
320 | +++ lib/lp/bugs/model/bug.py 2010-04-16 04:01:13 +0000 | |||
321 | @@ -83,6 +83,7 @@ | |||
322 | 83 | from lp.bugs.interfaces.bugtracker import BugTrackerType | 83 | from lp.bugs.interfaces.bugtracker import BugTrackerType |
323 | 84 | from lp.bugs.interfaces.bugwatch import IBugWatchSet | 84 | from lp.bugs.interfaces.bugwatch import IBugWatchSet |
324 | 85 | from lp.bugs.interfaces.cve import ICveSet | 85 | from lp.bugs.interfaces.cve import ICveSet |
325 | 86 | from lp.bugs.scripts.bugheat import BugHeatConstants | ||
326 | 86 | from lp.bugs.model.bugattachment import BugAttachment | 87 | from lp.bugs.model.bugattachment import BugAttachment |
327 | 87 | from lp.bugs.model.bugbranch import BugBranch | 88 | from lp.bugs.model.bugbranch import BugBranch |
328 | 88 | from lp.bugs.model.bugcve import BugCve | 89 | from lp.bugs.model.bugcve import BugCve |
329 | @@ -1351,10 +1352,33 @@ | |||
330 | 1351 | self.who_made_private = None | 1352 | self.who_made_private = None |
331 | 1352 | self.date_made_private = None | 1353 | self.date_made_private = None |
332 | 1353 | 1354 | ||
333 | 1355 | # Correct the heat for the bug immediately, so that we don't have | ||
334 | 1356 | # to wait for the next calculation job for the adjusted heat. | ||
335 | 1357 | if private: | ||
336 | 1358 | self.setHeat(self.heat + BugHeatConstants.PRIVACY) | ||
337 | 1359 | else: | ||
338 | 1360 | self.setHeat(self.heat - BugHeatConstants.PRIVACY) | ||
339 | 1361 | |||
340 | 1354 | return True # Changed. | 1362 | return True # Changed. |
341 | 1355 | else: | 1363 | else: |
342 | 1356 | return False # Not changed. | 1364 | return False # Not changed. |
343 | 1357 | 1365 | ||
344 | 1366 | def setSecurityRelated(self, security_related): | ||
345 | 1367 | """Setter for the `security_related` property.""" | ||
346 | 1368 | if self.security_related != security_related: | ||
347 | 1369 | self.security_related = security_related | ||
348 | 1370 | |||
349 | 1371 | # Correct the heat for the bug immediately, so that we don't have | ||
350 | 1372 | # to wait for the next calculation job for the adjusted heat. | ||
351 | 1373 | if security_related: | ||
352 | 1374 | self.setHeat(self.heat + BugHeatConstants.SECURITY) | ||
353 | 1375 | else: | ||
354 | 1376 | self.setHeat(self.heat - BugHeatConstants.SECURITY) | ||
355 | 1377 | |||
356 | 1378 | return True # Changed | ||
357 | 1379 | else: | ||
358 | 1380 | return False # Unchanged | ||
359 | 1381 | |||
360 | 1358 | def getBugTask(self, target): | 1382 | def getBugTask(self, target): |
361 | 1359 | """See `IBug`.""" | 1383 | """See `IBug`.""" |
362 | 1360 | for bugtask in self.bugtasks: | 1384 | for bugtask in self.bugtasks: |
363 | @@ -1540,6 +1564,9 @@ | |||
364 | 1540 | if timestamp is None: | 1564 | if timestamp is None: |
365 | 1541 | timestamp = UTC_NOW | 1565 | timestamp = UTC_NOW |
366 | 1542 | 1566 | ||
367 | 1567 | if heat < 0: | ||
368 | 1568 | heat = 0 | ||
369 | 1569 | |||
370 | 1543 | self.heat = heat | 1570 | self.heat = heat |
371 | 1544 | self.heat_last_updated = timestamp | 1571 | self.heat_last_updated = timestamp |
372 | 1545 | for task in self.bugtasks: | 1572 | for task in self.bugtasks: |
373 | 1546 | 1573 | ||
374 | === modified file 'lib/lp/bugs/scripts/bugheat.py' | |||
375 | --- lib/lp/bugs/scripts/bugheat.py 2010-03-04 19:49:08 +0000 | |||
376 | +++ lib/lp/bugs/scripts/bugheat.py 2010-04-16 04:01:13 +0000 | |||
377 | @@ -6,6 +6,7 @@ | |||
378 | 6 | __metaclass__ = type | 6 | __metaclass__ = type |
379 | 7 | __all__ = [ | 7 | __all__ = [ |
380 | 8 | 'BugHeatCalculator', | 8 | 'BugHeatCalculator', |
381 | 9 | 'BugHeatConstants', | ||
382 | 9 | ] | 10 | ] |
383 | 10 | 11 | ||
384 | 11 | from datetime import datetime | 12 | from datetime import datetime |
385 | 12 | 13 | ||
386 | === modified file 'lib/lp/bugs/scripts/bugimport.py' | |||
387 | --- lib/lp/bugs/scripts/bugimport.py 2009-09-11 14:59:08 +0000 | |||
388 | +++ lib/lp/bugs/scripts/bugimport.py 2010-04-16 04:01:13 +0000 | |||
389 | @@ -328,7 +328,7 @@ | |||
390 | 328 | 328 | ||
391 | 329 | # set up bug | 329 | # set up bug |
392 | 330 | bug.setPrivate(get_value(bugnode, 'private') == 'True', owner) | 330 | bug.setPrivate(get_value(bugnode, 'private') == 'True', owner) |
394 | 331 | bug.security_related = ( | 331 | bug.setSecurityRelated( |
395 | 332 | get_value(bugnode, 'security_related') == 'True') | 332 | get_value(bugnode, 'security_related') == 'True') |
396 | 333 | bug.name = get_value(bugnode, 'nickname') | 333 | bug.name = get_value(bugnode, 'nickname') |
397 | 334 | description = get_value(bugnode, 'description') | 334 | description = get_value(bugnode, 'description') |
398 | 335 | 335 | ||
399 | === modified file 'lib/lp/bugs/scripts/tests/test_bugheat.py' | |||
400 | --- lib/lp/bugs/scripts/tests/test_bugheat.py 2010-03-03 16:05:57 +0000 | |||
401 | +++ lib/lp/bugs/scripts/tests/test_bugheat.py 2010-04-16 04:01:13 +0000 | |||
402 | @@ -155,7 +155,7 @@ | |||
403 | 155 | 155 | ||
404 | 156 | # If, on the other hand, the bug is security_related, | 156 | # If, on the other hand, the bug is security_related, |
405 | 157 | # _getHeatFromSecurity() will return BugHeatConstants.SECURITY | 157 | # _getHeatFromSecurity() will return BugHeatConstants.SECURITY |
407 | 158 | self.bug.security_related = True | 158 | self.bug.setSecurityRelated(True) |
408 | 159 | self.assertEqual( | 159 | self.assertEqual( |
409 | 160 | BugHeatConstants.SECURITY, self.calculator._getHeatFromSecurity()) | 160 | BugHeatConstants.SECURITY, self.calculator._getHeatFromSecurity()) |
410 | 161 | 161 | ||
411 | @@ -179,7 +179,7 @@ | |||
412 | 179 | dupe = self.factory.makeBug() | 179 | dupe = self.factory.makeBug() |
413 | 180 | dupe.duplicateof = self.bug | 180 | dupe.duplicateof = self.bug |
414 | 181 | self.bug.setPrivate(True, self.bug.owner) | 181 | self.bug.setPrivate(True, self.bug.owner) |
416 | 182 | self.bug.security_related = True | 182 | self.bug.setSecurityRelated(True) |
417 | 183 | 183 | ||
418 | 184 | expected_heat += ( | 184 | expected_heat += ( |
419 | 185 | BugHeatConstants.DUPLICATE + | 185 | BugHeatConstants.DUPLICATE + |
420 | 186 | 186 | ||
421 | === modified file 'lib/lp/bugs/tests/test_bugchanges.py' | |||
422 | --- lib/lp/bugs/tests/test_bugchanges.py 2009-12-05 18:37:28 +0000 | |||
423 | +++ lib/lp/bugs/tests/test_bugchanges.py 2010-04-16 04:01:13 +0000 | |||
424 | @@ -562,7 +562,7 @@ | |||
425 | 562 | def test_mark_as_security_vulnerability(self): | 562 | def test_mark_as_security_vulnerability(self): |
426 | 563 | # Marking a bug as a security vulnerability adds to the bug's | 563 | # Marking a bug as a security vulnerability adds to the bug's |
427 | 564 | # activity log and sends a notification. | 564 | # activity log and sends a notification. |
429 | 565 | self.bug.security_related = False | 565 | self.bug.setSecurityRelated(False) |
430 | 566 | self.changeAttribute(self.bug, 'security_related', True) | 566 | self.changeAttribute(self.bug, 'security_related', True) |
431 | 567 | 567 | ||
432 | 568 | security_change_activity = { | 568 | security_change_activity = { |
433 | @@ -586,7 +586,7 @@ | |||
434 | 586 | def test_unmark_as_security_vulnerability(self): | 586 | def test_unmark_as_security_vulnerability(self): |
435 | 587 | # Unmarking a bug as a security vulnerability adds to the | 587 | # Unmarking a bug as a security vulnerability adds to the |
436 | 588 | # bug's activity log and sends a notification. | 588 | # bug's activity log and sends a notification. |
438 | 589 | self.bug.security_related = True | 589 | self.bug.setSecurityRelated(True) |
439 | 590 | self.changeAttribute(self.bug, 'security_related', False) | 590 | self.changeAttribute(self.bug, 'security_related', False) |
440 | 591 | 591 | ||
441 | 592 | security_change_activity = { | 592 | security_change_activity = { |
442 | 593 | 593 | ||
443 | === modified file 'lib/lp/code/browser/branch.py' | |||
444 | --- lib/lp/code/browser/branch.py 2010-03-25 15:28:49 +0000 | |||
445 | +++ lib/lp/code/browser/branch.py 2010-04-16 04:01:13 +0000 | |||
446 | @@ -81,10 +81,12 @@ | |||
447 | 81 | from lp.code.browser.branchmergeproposal import ( | 81 | from lp.code.browser.branchmergeproposal import ( |
448 | 82 | latest_proposals_for_each_branch) | 82 | latest_proposals_for_each_branch) |
449 | 83 | from lp.code.enums import ( | 83 | from lp.code.enums import ( |
451 | 84 | BranchLifecycleStatus, BranchType, CodeImportJobState, | 84 | BranchLifecycleStatus, BranchType, |
452 | 85 | CodeImportResultStatus, CodeImportReviewStatus, RevisionControlSystems, | 85 | CodeImportResultStatus, CodeImportReviewStatus, RevisionControlSystems, |
453 | 86 | UICreatableBranchType) | 86 | UICreatableBranchType) |
455 | 87 | from lp.code.errors import InvalidBranchMergeProposal | 87 | from lp.code.errors import ( |
456 | 88 | CodeImportAlreadyRequested, CodeImportAlreadyRunning, | ||
457 | 89 | CodeImportNotInReviewedState, InvalidBranchMergeProposal) | ||
458 | 88 | from lp.code.interfaces.branch import ( | 90 | from lp.code.interfaces.branch import ( |
459 | 89 | BranchCreationForbidden, BranchExists, IBranch, | 91 | BranchCreationForbidden, BranchExists, IBranch, |
460 | 90 | user_has_special_branch_access) | 92 | user_has_special_branch_access) |
461 | @@ -1317,26 +1319,23 @@ | |||
462 | 1317 | 1319 | ||
463 | 1318 | @action('Import Now', name='request') | 1320 | @action('Import Now', name='request') |
464 | 1319 | def request_import_action(self, action, data): | 1321 | def request_import_action(self, action, data): |
466 | 1320 | if self.context.code_import.import_job is None: | 1322 | try: |
467 | 1323 | self.context.code_import.requestImport( | ||
468 | 1324 | self.user, error_if_already_requested=True) | ||
469 | 1325 | self.request.response.addNotification( | ||
470 | 1326 | "Import will run as soon as possible.") | ||
471 | 1327 | except CodeImportNotInReviewedState: | ||
472 | 1321 | self.request.response.addNotification( | 1328 | self.request.response.addNotification( |
473 | 1322 | "This import is no longer being updated automatically.") | 1329 | "This import is no longer being updated automatically.") |
478 | 1323 | elif (self.context.code_import.import_job.state != | 1330 | except CodeImportAlreadyRunning: |
475 | 1324 | CodeImportJobState.PENDING): | ||
476 | 1325 | assert (self.context.code_import.import_job.state == | ||
477 | 1326 | CodeImportJobState.RUNNING) | ||
479 | 1327 | self.request.response.addNotification( | 1331 | self.request.response.addNotification( |
480 | 1328 | "The import is already running.") | 1332 | "The import is already running.") |
483 | 1329 | elif self.context.code_import.import_job.requesting_user is not None: | 1333 | except CodeImportAlreadyRequested, e: |
484 | 1330 | user = self.context.code_import.import_job.requesting_user | 1334 | user = e.requesting_user |
485 | 1331 | adapter = queryAdapter(user, IPathAdapter, 'fmt') | 1335 | adapter = queryAdapter(user, IPathAdapter, 'fmt') |
486 | 1332 | self.request.response.addNotification( | 1336 | self.request.response.addNotification( |
487 | 1333 | structured("The import has already been requested by %s." % | 1337 | structured("The import has already been requested by %s." % |
488 | 1334 | adapter.link(None))) | 1338 | adapter.link(None))) |
489 | 1335 | else: | ||
490 | 1336 | getUtility(ICodeImportJobWorkflow).requestJob( | ||
491 | 1337 | self.context.code_import.import_job, self.user) | ||
492 | 1338 | self.request.response.addNotification( | ||
493 | 1339 | "Import will run as soon as possible.") | ||
494 | 1340 | 1339 | ||
495 | 1341 | @property | 1340 | @property |
496 | 1342 | def prefix(self): | 1341 | def prefix(self): |
497 | 1343 | 1342 | ||
498 | === modified file 'lib/lp/code/configure.zcml' | |||
499 | --- lib/lp/code/configure.zcml 2010-04-13 23:46:06 +0000 | |||
500 | +++ lib/lp/code/configure.zcml 2010-04-16 04:01:13 +0000 | |||
501 | @@ -840,7 +840,8 @@ | |||
502 | 840 | getImportDetailsForDisplay"/> | 840 | getImportDetailsForDisplay"/> |
503 | 841 | <require | 841 | <require |
504 | 842 | permission="launchpad.AnyPerson" | 842 | permission="launchpad.AnyPerson" |
506 | 843 | attributes="tryFailingImportAgain"/> | 843 | attributes="tryFailingImportAgain |
507 | 844 | requestImport"/> | ||
508 | 844 | <require | 845 | <require |
509 | 845 | permission="launchpad.Edit" | 846 | permission="launchpad.Edit" |
510 | 846 | attributes="updateFromData"/> | 847 | attributes="updateFromData"/> |
511 | 847 | 848 | ||
512 | === modified file 'lib/lp/code/errors.py' | |||
513 | --- lib/lp/code/errors.py 2010-04-05 21:38:40 +0000 | |||
514 | +++ lib/lp/code/errors.py 2010-04-16 04:01:13 +0000 | |||
515 | @@ -8,6 +8,9 @@ | |||
516 | 8 | 'BadBranchMergeProposalSearchContext', | 8 | 'BadBranchMergeProposalSearchContext', |
517 | 9 | 'BadStateTransition', | 9 | 'BadStateTransition', |
518 | 10 | 'BranchMergeProposalExists', | 10 | 'BranchMergeProposalExists', |
519 | 11 | 'CodeImportAlreadyRequested', | ||
520 | 12 | 'CodeImportAlreadyRunning', | ||
521 | 13 | 'CodeImportNotInReviewedState', | ||
522 | 11 | 'ClaimReviewFailed', | 14 | 'ClaimReviewFailed', |
523 | 12 | 'InvalidBranchMergeProposal', | 15 | 'InvalidBranchMergeProposal', |
524 | 13 | 'ReviewNotPending', | 16 | 'ReviewNotPending', |
525 | @@ -68,3 +71,23 @@ | |||
526 | 68 | 71 | ||
527 | 69 | class UnknownBranchTypeError(Exception): | 72 | class UnknownBranchTypeError(Exception): |
528 | 70 | """Raised when the user specifies an unrecognized branch type.""" | 73 | """Raised when the user specifies an unrecognized branch type.""" |
529 | 74 | |||
530 | 75 | |||
531 | 76 | class CodeImportNotInReviewedState(Exception): | ||
532 | 77 | """Raised when the user requests an import of a non-automatic import.""" | ||
533 | 78 | |||
534 | 79 | webservice_error(400) | ||
535 | 80 | |||
536 | 81 | |||
537 | 82 | class CodeImportAlreadyRequested(Exception): | ||
538 | 83 | """Raised when the user requests an import that is already requested.""" | ||
539 | 84 | |||
540 | 85 | def __init__(self, msg, requesting_user): | ||
541 | 86 | super(CodeImportAlreadyRequested, self).__init__(msg) | ||
542 | 87 | self.requesting_user = requesting_user | ||
543 | 88 | |||
544 | 89 | |||
545 | 90 | class CodeImportAlreadyRunning(Exception): | ||
546 | 91 | """Raised when the user requests an import that is already running.""" | ||
547 | 92 | |||
548 | 93 | webservice_error(400) | ||
549 | 71 | 94 | ||
550 | === modified file 'lib/lp/code/interfaces/branchmergeproposal.py' | |||
551 | --- lib/lp/code/interfaces/branchmergeproposal.py 2010-04-01 05:08:47 +0000 | |||
552 | +++ lib/lp/code/interfaces/branchmergeproposal.py 2010-04-16 04:01:13 +0000 | |||
553 | @@ -347,6 +347,9 @@ | |||
554 | 347 | If the proposal is not in the Approved state before this method | 347 | If the proposal is not in the Approved state before this method |
555 | 348 | is called, approveBranch is called with the reviewer and revision_id | 348 | is called, approveBranch is called with the reviewer and revision_id |
556 | 349 | specified. | 349 | specified. |
557 | 350 | |||
558 | 351 | If None is supplied as the revision_id, the proposals | ||
559 | 352 | reviewed_revision_id is used. | ||
560 | 350 | """ | 353 | """ |
561 | 351 | 354 | ||
562 | 352 | def dequeue(): | 355 | def dequeue(): |
563 | 353 | 356 | ||
564 | === modified file 'lib/lp/code/interfaces/codeimport.py' | |||
565 | --- lib/lp/code/interfaces/codeimport.py 2010-04-01 20:58:42 +0000 | |||
566 | +++ lib/lp/code/interfaces/codeimport.py 2010-04-16 04:01:13 +0000 | |||
567 | @@ -25,7 +25,8 @@ | |||
568 | 25 | from lp.code.interfaces.branch import IBranch | 25 | from lp.code.interfaces.branch import IBranch |
569 | 26 | 26 | ||
570 | 27 | from lazr.restful.declarations import ( | 27 | from lazr.restful.declarations import ( |
572 | 28 | export_as_webservice_entry, exported) | 28 | call_with, export_as_webservice_entry, exported, export_write_operation, |
573 | 29 | REQUEST_USER) | ||
574 | 29 | from lazr.restful.fields import ReferenceChoice | 30 | from lazr.restful.fields import ReferenceChoice |
575 | 30 | 31 | ||
576 | 31 | 32 | ||
577 | @@ -177,6 +178,33 @@ | |||
578 | 177 | :param user: the user who is requesting the import be tried again. | 178 | :param user: the user who is requesting the import be tried again. |
579 | 178 | """ | 179 | """ |
580 | 179 | 180 | ||
581 | 181 | @call_with(requester=REQUEST_USER) | ||
582 | 182 | @export_write_operation() | ||
583 | 183 | def requestImport(requester, error_if_already_requested=False): | ||
584 | 184 | """Request that an import be tried soon. | ||
585 | 185 | |||
586 | 186 | This method will schedule an import to happen soon for this branch. | ||
587 | 187 | |||
588 | 188 | The import must be in the Reviewed state, if not then a | ||
589 | 189 | CodeImportNotInReviewedState error will be thrown. If using the | ||
590 | 190 | API then a status code of 400 will result. | ||
591 | 191 | |||
592 | 192 | If the import is already running then a CodeImportAlreadyRunning | ||
593 | 193 | error will be thrown. If using the API then a status code of | ||
594 | 194 | 400 will result. | ||
595 | 195 | |||
596 | 196 | The two cases can be distinguished over the API by seeing if the | ||
597 | 197 | exception names appear in the body of the response. | ||
598 | 198 | |||
599 | 199 | If used over the API and the request has already been made then this | ||
600 | 200 | method will silently do nothing. | ||
601 | 201 | If called internally then the error_if_already_requested parameter | ||
602 | 202 | controls whether a CodeImportAlreadyRequested exception will be | ||
603 | 203 | thrown in that situation. | ||
604 | 204 | |||
605 | 205 | :return: None | ||
606 | 206 | """ | ||
607 | 207 | |||
608 | 180 | 208 | ||
609 | 181 | class ICodeImportSet(Interface): | 209 | class ICodeImportSet(Interface): |
610 | 182 | """Interface representing the set of code imports.""" | 210 | """Interface representing the set of code imports.""" |
611 | 183 | 211 | ||
612 | === modified file 'lib/lp/code/interfaces/webservice.py' | |||
613 | --- lib/lp/code/interfaces/webservice.py 2010-04-01 23:04:10 +0000 | |||
614 | +++ lib/lp/code/interfaces/webservice.py 2010-04-16 04:01:13 +0000 | |||
615 | @@ -3,12 +3,23 @@ | |||
616 | 3 | 3 | ||
617 | 4 | """All the interfaces that are exposed through the webservice.""" | 4 | """All the interfaces that are exposed through the webservice.""" |
618 | 5 | 5 | ||
619 | 6 | <<<<<<< TREE | ||
620 | 6 | # The exceptions are imported so that they can produce the special | 7 | # The exceptions are imported so that they can produce the special |
621 | 7 | # status code defined by webservice_error when they are raised. | 8 | # status code defined by webservice_error when they are raised. |
622 | 8 | from lp.code.errors import BranchMergeProposalExists | 9 | from lp.code.errors import BranchMergeProposalExists |
623 | 9 | from lp.code.interfaces.branch import ( | 10 | from lp.code.interfaces.branch import ( |
624 | 10 | IBranch, IBranchSet, BranchCreatorNotMemberOfOwnerTeam, | 11 | IBranch, IBranchSet, BranchCreatorNotMemberOfOwnerTeam, |
625 | 11 | BranchCreatorNotOwner, BranchExists) | 12 | BranchCreatorNotOwner, BranchExists) |
626 | 13 | ======= | ||
627 | 14 | # The exceptions are imported so that they can produce the special | ||
628 | 15 | # status code defined by webservice_error when they are raised. | ||
629 | 16 | from lp.code.errors import ( | ||
630 | 17 | BranchMergeProposalExists, CodeImportAlreadyRunning, | ||
631 | 18 | CodeImportNotInReviewedState) | ||
632 | 19 | from lp.code.interfaces.branch import ( | ||
633 | 20 | IBranch, IBranchSet, BranchCreatorNotMemberOfOwnerTeam, | ||
634 | 21 | BranchCreatorNotOwner, BranchExists) | ||
635 | 22 | >>>>>>> MERGE-SOURCE | ||
636 | 12 | from lp.code.interfaces.branchmergeproposal import IBranchMergeProposal | 23 | from lp.code.interfaces.branchmergeproposal import IBranchMergeProposal |
637 | 13 | from lp.code.interfaces.branchsubscription import IBranchSubscription | 24 | from lp.code.interfaces.branchsubscription import IBranchSubscription |
638 | 14 | from lp.code.interfaces.codeimport import ICodeImport | 25 | from lp.code.interfaces.codeimport import ICodeImport |
639 | 15 | 26 | ||
640 | === modified file 'lib/lp/code/model/branchmergeproposal.py' | |||
641 | --- lib/lp/code/model/branchmergeproposal.py 2010-04-01 04:48:01 +0000 | |||
642 | +++ lib/lp/code/model/branchmergeproposal.py 2010-04-16 04:01:13 +0000 | |||
643 | @@ -84,9 +84,10 @@ | |||
644 | 84 | if (next_state == rejected and not valid_reviewer): | 84 | if (next_state == rejected and not valid_reviewer): |
645 | 85 | return False | 85 | return False |
646 | 86 | # Non-reviewers can toggle between code_approved and queued, but not | 86 | # Non-reviewers can toggle between code_approved and queued, but not |
648 | 87 | # make anything else approved or queued. | 87 | # make anything else approved or queued. They can also take merge failed |
649 | 88 | # and requeue or bounce all the way out to approved again. | ||
650 | 88 | elif (next_state in (code_approved, queued) and | 89 | elif (next_state in (code_approved, queued) and |
652 | 89 | from_state not in (code_approved, queued) | 90 | from_state not in (code_approved, queued, merge_failed) |
653 | 90 | and not valid_reviewer): | 91 | and not valid_reviewer): |
654 | 91 | return False | 92 | return False |
655 | 92 | else: | 93 | else: |
656 | @@ -325,23 +326,23 @@ | |||
657 | 325 | # XXX - rockstar - 9 Oct 2008 - jml suggested in a review that this | 326 | # XXX - rockstar - 9 Oct 2008 - jml suggested in a review that this |
658 | 326 | # would be better as a dict mapping. | 327 | # would be better as a dict mapping. |
659 | 327 | # See bug #281060. | 328 | # See bug #281060. |
660 | 329 | if (self.queue_status == BranchMergeProposalStatus.QUEUED and | ||
661 | 330 | status != BranchMergeProposalStatus.QUEUED): | ||
662 | 331 | self.dequeue() | ||
663 | 328 | if status == BranchMergeProposalStatus.WORK_IN_PROGRESS: | 332 | if status == BranchMergeProposalStatus.WORK_IN_PROGRESS: |
664 | 329 | self.setAsWorkInProgress() | 333 | self.setAsWorkInProgress() |
665 | 330 | elif status == BranchMergeProposalStatus.NEEDS_REVIEW: | 334 | elif status == BranchMergeProposalStatus.NEEDS_REVIEW: |
666 | 331 | self.requestReview() | 335 | self.requestReview() |
667 | 332 | elif status == BranchMergeProposalStatus.CODE_APPROVED: | 336 | elif status == BranchMergeProposalStatus.CODE_APPROVED: |
674 | 333 | # Other half of the edge case. If the status is currently queued, | 337 | self.approveBranch(user, revision_id) |
669 | 334 | # we need to dequeue, otherwise we just approve the branch. | ||
670 | 335 | if self.queue_status == BranchMergeProposalStatus.QUEUED: | ||
671 | 336 | self.dequeue() | ||
672 | 337 | else: | ||
673 | 338 | self.approveBranch(user, revision_id) | ||
675 | 339 | elif status == BranchMergeProposalStatus.REJECTED: | 338 | elif status == BranchMergeProposalStatus.REJECTED: |
676 | 340 | self.rejectBranch(user, revision_id) | 339 | self.rejectBranch(user, revision_id) |
677 | 341 | elif status == BranchMergeProposalStatus.QUEUED: | 340 | elif status == BranchMergeProposalStatus.QUEUED: |
678 | 342 | self.enqueue(user, revision_id) | 341 | self.enqueue(user, revision_id) |
679 | 343 | elif status == BranchMergeProposalStatus.MERGED: | 342 | elif status == BranchMergeProposalStatus.MERGED: |
680 | 344 | self.markAsMerged(merge_reporter=user) | 343 | self.markAsMerged(merge_reporter=user) |
681 | 344 | elif status == BranchMergeProposalStatus.MERGE_FAILED: | ||
682 | 345 | self._transitionToState(status) | ||
683 | 345 | else: | 346 | else: |
684 | 346 | raise AssertionError('Unexpected queue status: ' % status) | 347 | raise AssertionError('Unexpected queue status: ' % status) |
685 | 347 | 348 | ||
686 | @@ -379,7 +380,7 @@ | |||
687 | 379 | 380 | ||
688 | 380 | def _reviewProposal(self, reviewer, next_state, revision_id, | 381 | def _reviewProposal(self, reviewer, next_state, revision_id, |
689 | 381 | _date_reviewed=None): | 382 | _date_reviewed=None): |
691 | 382 | """Set the proposal to one of the two review statuses.""" | 383 | """Set the proposal to next_state.""" |
692 | 383 | # Check the reviewer can review the code for the target branch. | 384 | # Check the reviewer can review the code for the target branch. |
693 | 384 | old_state = self.queue_status | 385 | old_state = self.queue_status |
694 | 385 | if not self.target_branch.isPersonTrustedReviewer(reviewer): | 386 | if not self.target_branch.isPersonTrustedReviewer(reviewer): |
695 | @@ -433,7 +434,7 @@ | |||
696 | 433 | self.queue_status = BranchMergeProposalStatus.QUEUED | 434 | self.queue_status = BranchMergeProposalStatus.QUEUED |
697 | 434 | self.queue_position = position | 435 | self.queue_position = position |
698 | 435 | self.queuer = queuer | 436 | self.queuer = queuer |
700 | 436 | self.queued_revision_id = revision_id | 437 | self.queued_revision_id = revision_id or self.reviewed_revision_id |
701 | 437 | self.date_queued = UTC_NOW | 438 | self.date_queued = UTC_NOW |
702 | 438 | self.syncUpdate() | 439 | self.syncUpdate() |
703 | 439 | 440 | ||
704 | 440 | 441 | ||
705 | === modified file 'lib/lp/code/model/branchtarget.py' | |||
706 | --- lib/lp/code/model/branchtarget.py 2010-04-14 17:44:00 +0000 | |||
707 | +++ lib/lp/code/model/branchtarget.py 2010-04-16 04:01:13 +0000 | |||
708 | @@ -337,6 +337,26 @@ | |||
709 | 337 | branch.sourcepackagename = None | 337 | branch.sourcepackagename = None |
710 | 338 | 338 | ||
711 | 339 | 339 | ||
712 | 340 | <<<<<<< TREE | ||
713 | 341 | ======= | ||
714 | 342 | class ProductSeriesBranchTarget(ProductBranchTarget): | ||
715 | 343 | |||
716 | 344 | def __init__(self, productseries): | ||
717 | 345 | ProductBranchTarget.__init__(self, productseries.product) | ||
718 | 346 | self.productseries = productseries | ||
719 | 347 | |||
720 | 348 | @property | ||
721 | 349 | def context(self): | ||
722 | 350 | """See `IBranchTarget`.""" | ||
723 | 351 | return self.productseries | ||
724 | 352 | |||
725 | 353 | @property | ||
726 | 354 | def supports_code_imports(self): | ||
727 | 355 | """See `IBranchTarget`.""" | ||
728 | 356 | return False | ||
729 | 357 | |||
730 | 358 | |||
731 | 359 | >>>>>>> MERGE-SOURCE | ||
732 | 340 | def get_canonical_url_data_for_target(branch_target): | 360 | def get_canonical_url_data_for_target(branch_target): |
733 | 341 | """Return the `ICanonicalUrlData` for an `IBranchTarget`.""" | 361 | """Return the `ICanonicalUrlData` for an `IBranchTarget`.""" |
734 | 342 | return ICanonicalUrlData(branch_target.context) | 362 | return ICanonicalUrlData(branch_target.context) |
735 | 343 | 363 | ||
736 | === modified file 'lib/lp/code/model/codeimport.py' | |||
737 | --- lib/lp/code/model/codeimport.py 2010-04-01 20:58:42 +0000 | |||
738 | +++ lib/lp/code/model/codeimport.py 2010-04-16 04:01:13 +0000 | |||
739 | @@ -37,6 +37,9 @@ | |||
740 | 37 | from lp.code.enums import ( | 37 | from lp.code.enums import ( |
741 | 38 | BranchType, CodeImportJobState, CodeImportResultStatus, | 38 | BranchType, CodeImportJobState, CodeImportResultStatus, |
742 | 39 | CodeImportReviewStatus, RevisionControlSystems) | 39 | CodeImportReviewStatus, RevisionControlSystems) |
743 | 40 | from lp.code.errors import ( | ||
744 | 41 | CodeImportAlreadyRequested, CodeImportAlreadyRunning, | ||
745 | 42 | CodeImportNotInReviewedState) | ||
746 | 40 | from lp.code.interfaces.codeimport import ICodeImport, ICodeImportSet | 43 | from lp.code.interfaces.codeimport import ICodeImport, ICodeImportSet |
747 | 41 | from lp.code.interfaces.codeimportevent import ICodeImportEventSet | 44 | from lp.code.interfaces.codeimportevent import ICodeImportEventSet |
748 | 42 | from lp.code.interfaces.codeimportjob import ICodeImportJobWorkflow | 45 | from lp.code.interfaces.codeimportjob import ICodeImportJobWorkflow |
749 | @@ -196,6 +199,27 @@ | |||
750 | 196 | {'review_status': CodeImportReviewStatus.REVIEWED}, user) | 199 | {'review_status': CodeImportReviewStatus.REVIEWED}, user) |
751 | 197 | getUtility(ICodeImportJobWorkflow).requestJob(self.import_job, user) | 200 | getUtility(ICodeImportJobWorkflow).requestJob(self.import_job, user) |
752 | 198 | 201 | ||
753 | 202 | def requestImport(self, requester, error_if_already_requested=False): | ||
754 | 203 | """See `ICodeImport`.""" | ||
755 | 204 | if self.import_job is None: # not in automatic mode | ||
756 | 205 | raise CodeImportNotInReviewedState("This code import is %s, and " | ||
757 | 206 | "must be Reviewed for you to call requestImport." | ||
758 | 207 | % self.review_status.name) | ||
759 | 208 | if (self.import_job.state != CodeImportJobState.PENDING): | ||
760 | 209 | assert (self.import_job.state == CodeImportJobState.RUNNING) | ||
761 | 210 | # Already running | ||
762 | 211 | raise CodeImportAlreadyRunning("This code import is already " | ||
763 | 212 | "running.") | ||
764 | 213 | elif self.import_job.requesting_user is not None: | ||
765 | 214 | if error_if_already_requested: | ||
766 | 215 | raise CodeImportAlreadyRequested("This code import has " | ||
767 | 216 | "already been requested to run.", | ||
768 | 217 | self.import_job.requesting_user) | ||
769 | 218 | else: | ||
770 | 219 | getUtility(ICodeImportJobWorkflow).requestJob( | ||
771 | 220 | self.import_job, requester) | ||
772 | 221 | return None | ||
773 | 222 | |||
774 | 199 | 223 | ||
775 | 200 | class CodeImportSet: | 224 | class CodeImportSet: |
776 | 201 | """See `ICodeImportSet`.""" | 225 | """See `ICodeImportSet`.""" |
777 | 202 | 226 | ||
778 | === modified file 'lib/lp/code/model/tests/test_branchmergeproposals.py' | |||
779 | --- lib/lp/code/model/tests/test_branchmergeproposals.py 2010-04-06 03:37:16 +0000 | |||
780 | +++ lib/lp/code/model/tests/test_branchmergeproposals.py 2010-04-16 04:01:13 +0000 | |||
781 | @@ -261,6 +261,20 @@ | |||
782 | 261 | """We can go from merge failed to any other state.""" | 261 | """We can go from merge failed to any other state.""" |
783 | 262 | self.assertAllTransitionsGood(BranchMergeProposalStatus.MERGE_FAILED) | 262 | self.assertAllTransitionsGood(BranchMergeProposalStatus.MERGE_FAILED) |
784 | 263 | 263 | ||
785 | 264 | def test_transition_from_merge_failed_to_queued_non_reviewer(self): | ||
786 | 265 | # Contributors can requeue to retry after environmental issues fail a | ||
787 | 266 | # merge. | ||
788 | 267 | proposal = self.factory.makeBranchMergeProposal() | ||
789 | 268 | self.assertFalse(proposal.target_branch.isPersonTrustedReviewer( | ||
790 | 269 | proposal.source_branch.owner)) | ||
791 | 270 | # It is always valid to go to the same state. | ||
792 | 271 | self.assertValidTransitions(set([ | ||
793 | 272 | BranchMergeProposalStatus.MERGE_FAILED, | ||
794 | 273 | BranchMergeProposalStatus.CODE_APPROVED, | ||
795 | 274 | BranchMergeProposalStatus.QUEUED]), | ||
796 | 275 | proposal, BranchMergeProposalStatus.QUEUED, | ||
797 | 276 | proposal.source_branch.owner) | ||
798 | 277 | |||
799 | 264 | def test_transitions_from_queued_dequeue(self): | 278 | def test_transitions_from_queued_dequeue(self): |
800 | 265 | # When a proposal is dequeued it is set to code approved, and the | 279 | # When a proposal is dequeued it is set to code approved, and the |
801 | 266 | # queue position is reset. | 280 | # queue position is reset. |
802 | @@ -322,6 +336,19 @@ | |||
803 | 322 | self.target_branch = self.factory.makeProductBranch() | 336 | self.target_branch = self.factory.makeProductBranch() |
804 | 323 | login_person(self.target_branch.owner) | 337 | login_person(self.target_branch.owner) |
805 | 324 | 338 | ||
806 | 339 | def test_set_status_approved_to_queued(self): | ||
807 | 340 | # setState can change an approved merge proposal to Work In Progress, | ||
808 | 341 | # which will set the revision id to the reviewed revision id if not | ||
809 | 342 | # supplied. | ||
810 | 343 | proposal = self.factory.makeBranchMergeProposal( | ||
811 | 344 | target_branch=self.target_branch, | ||
812 | 345 | set_state=BranchMergeProposalStatus.CODE_APPROVED) | ||
813 | 346 | proposal.approveBranch(proposal.target_branch.owner, '250') | ||
814 | 347 | proposal.setStatus(BranchMergeProposalStatus.QUEUED) | ||
815 | 348 | self.assertEqual(proposal.queue_status, | ||
816 | 349 | BranchMergeProposalStatus.QUEUED) | ||
817 | 350 | self.assertEqual(proposal.queued_revision_id, '250') | ||
818 | 351 | |||
819 | 325 | def test_set_status_approved_to_work_in_progress(self): | 352 | def test_set_status_approved_to_work_in_progress(self): |
820 | 326 | # setState can change an approved merge proposal to Work In Progress. | 353 | # setState can change an approved merge proposal to Work In Progress. |
821 | 327 | proposal = self.factory.makeBranchMergeProposal( | 354 | proposal = self.factory.makeBranchMergeProposal( |
822 | @@ -331,6 +358,18 @@ | |||
823 | 331 | self.assertEqual(proposal.queue_status, | 358 | self.assertEqual(proposal.queue_status, |
824 | 332 | BranchMergeProposalStatus.WORK_IN_PROGRESS) | 359 | BranchMergeProposalStatus.WORK_IN_PROGRESS) |
825 | 333 | 360 | ||
826 | 361 | def test_set_status_queued_to_merge_failed(self): | ||
827 | 362 | proposal = self.factory.makeBranchMergeProposal( | ||
828 | 363 | target_branch=self.target_branch, | ||
829 | 364 | set_state=BranchMergeProposalStatus.QUEUED) | ||
830 | 365 | proposal.setStatus(BranchMergeProposalStatus.MERGE_FAILED) | ||
831 | 366 | self.assertEqual(proposal.queue_status, | ||
832 | 367 | BranchMergeProposalStatus.MERGE_FAILED) | ||
833 | 368 | self.assertEqual(proposal.queuer, None) | ||
834 | 369 | self.assertEqual(proposal.queued_revision_id, None) | ||
835 | 370 | self.assertEqual(proposal.date_queued, None) | ||
836 | 371 | self.assertEqual(proposal.queue_position, None) | ||
837 | 372 | |||
838 | 334 | def test_set_status_wip_to_needs_review(self): | 373 | def test_set_status_wip_to_needs_review(self): |
839 | 335 | # setState can change the merge proposal to Needs Review. | 374 | # setState can change the merge proposal to Needs Review. |
840 | 336 | proposal = self.factory.makeBranchMergeProposal( | 375 | proposal = self.factory.makeBranchMergeProposal( |
841 | 337 | 376 | ||
842 | === modified file 'lib/lp/code/model/tests/test_codeimport.py' | |||
843 | --- lib/lp/code/model/tests/test_codeimport.py 2010-04-01 20:58:42 +0000 | |||
844 | +++ lib/lp/code/model/tests/test_codeimport.py 2010-04-16 04:01:13 +0000 | |||
845 | @@ -12,6 +12,11 @@ | |||
846 | 12 | from zope.component import getUtility | 12 | from zope.component import getUtility |
847 | 13 | from zope.security.proxy import removeSecurityProxy | 13 | from zope.security.proxy import removeSecurityProxy |
848 | 14 | 14 | ||
849 | 15 | from canonical.launchpad.testing.codeimporthelpers import ( | ||
850 | 16 | make_running_import) | ||
851 | 17 | from lp.code.errors import ( | ||
852 | 18 | CodeImportAlreadyRequested, CodeImportAlreadyRunning, | ||
853 | 19 | CodeImportNotInReviewedState) | ||
854 | 15 | from lp.code.model.codeimport import CodeImportSet | 20 | from lp.code.model.codeimport import CodeImportSet |
855 | 16 | from lp.code.model.codeimportevent import CodeImportEvent | 21 | from lp.code.model.codeimportevent import CodeImportEvent |
856 | 17 | from lp.code.model.codeimportjob import CodeImportJob, CodeImportJobSet | 22 | from lp.code.model.codeimportjob import CodeImportJob, CodeImportJobSet |
857 | @@ -620,5 +625,67 @@ | |||
858 | 620 | requester, code_import.import_job.requesting_user) | 625 | requester, code_import.import_job.requesting_user) |
859 | 621 | 626 | ||
860 | 622 | 627 | ||
861 | 628 | class TestRequestImport(TestCaseWithFactory): | ||
862 | 629 | """Tests for `ICodeImport.requestImport`.""" | ||
863 | 630 | |||
864 | 631 | layer = DatabaseFunctionalLayer | ||
865 | 632 | |||
866 | 633 | def setUp(self): | ||
867 | 634 | # We have to be logged in to request imports | ||
868 | 635 | TestCaseWithFactory.setUp(self, user='no-priv@canonical.com') | ||
869 | 636 | |||
870 | 637 | def test_requestsJob(self): | ||
871 | 638 | code_import = self.factory.makeCodeImport( | ||
872 | 639 | git_repo_url=self.factory.getUniqueURL()) | ||
873 | 640 | requester = self.factory.makePerson() | ||
874 | 641 | old_date = code_import.import_job.date_due | ||
875 | 642 | code_import.requestImport(requester) | ||
876 | 643 | self.assertEqual(requester, code_import.import_job.requesting_user) | ||
877 | 644 | self.assertTrue(code_import.import_job.date_due <= old_date) | ||
878 | 645 | |||
879 | 646 | def test_noop_if_already_requested(self): | ||
880 | 647 | code_import = self.factory.makeCodeImport( | ||
881 | 648 | git_repo_url=self.factory.getUniqueURL()) | ||
882 | 649 | requester = self.factory.makePerson() | ||
883 | 650 | code_import.requestImport(requester) | ||
884 | 651 | old_date = code_import.import_job.date_due | ||
885 | 652 | code_import.requestImport(requester) | ||
886 | 653 | # The checks don't matter so much, it's more that we don't get | ||
887 | 654 | # an exception. | ||
888 | 655 | self.assertEqual(requester, code_import.import_job.requesting_user) | ||
889 | 656 | self.assertEqual(old_date, code_import.import_job.date_due) | ||
890 | 657 | |||
891 | 658 | def test_optional_error_if_already_requested(self): | ||
892 | 659 | code_import = self.factory.makeCodeImport( | ||
893 | 660 | git_repo_url=self.factory.getUniqueURL()) | ||
894 | 661 | requester = self.factory.makePerson() | ||
895 | 662 | code_import.requestImport(requester) | ||
896 | 663 | old_date = code_import.import_job.date_due | ||
897 | 664 | e = self.assertRaises( | ||
898 | 665 | CodeImportAlreadyRequested, code_import.requestImport, requester, | ||
899 | 666 | error_if_already_requested=True) | ||
900 | 667 | self.assertEqual(requester, e.requesting_user) | ||
901 | 668 | |||
902 | 669 | def test_exception_on_disabled(self): | ||
903 | 670 | # get an SVN request, which isn't reviewed by default | ||
904 | 671 | code_import = self.factory.makeCodeImport( | ||
905 | 672 | svn_branch_url=self.factory.getUniqueURL()) | ||
906 | 673 | requester = self.factory.makePerson() | ||
907 | 674 | # which leads to an exception if we try and ask for an import | ||
908 | 675 | self.assertRaises( | ||
909 | 676 | CodeImportNotInReviewedState, code_import.requestImport, | ||
910 | 677 | requester) | ||
911 | 678 | |||
912 | 679 | def test_exception_if_already_running(self): | ||
913 | 680 | code_import = self.factory.makeCodeImport( | ||
914 | 681 | git_repo_url=self.factory.getUniqueURL()) | ||
915 | 682 | code_import = make_running_import(factory=self.factory, | ||
916 | 683 | code_import=code_import) | ||
917 | 684 | requester = self.factory.makePerson() | ||
918 | 685 | self.assertRaises( | ||
919 | 686 | CodeImportAlreadyRunning, code_import.requestImport, | ||
920 | 687 | requester) | ||
921 | 688 | |||
922 | 689 | |||
923 | 623 | def test_suite(): | 690 | def test_suite(): |
924 | 624 | return unittest.TestLoader().loadTestsFromName(__name__) | 691 | return unittest.TestLoader().loadTestsFromName(__name__) |
925 | 625 | 692 | ||
926 | === modified file 'lib/lp/code/stories/webservice/xx-code-import.txt' | |||
927 | --- lib/lp/code/stories/webservice/xx-code-import.txt 2010-04-14 17:44:00 +0000 | |||
928 | +++ lib/lp/code/stories/webservice/xx-code-import.txt 2010-04-16 04:01:13 +0000 | |||
929 | @@ -17,9 +17,15 @@ | |||
930 | 17 | >>> other_person = factory.makePerson(name='other-person') | 17 | >>> other_person = factory.makePerson(name='other-person') |
931 | 18 | >>> removeSecurityProxy(person).join(team) | 18 | >>> removeSecurityProxy(person).join(team) |
932 | 19 | >>> product = factory.makeProduct(name='scruff') | 19 | >>> product = factory.makeProduct(name='scruff') |
933 | 20 | >>> svn_branch_url = "http://svn.domain.com/source" | ||
934 | 20 | >>> code_import = removeSecurityProxy(factory.makeProductCodeImport( | 21 | >>> code_import = removeSecurityProxy(factory.makeProductCodeImport( |
935 | 22 | <<<<<<< TREE | ||
936 | 21 | ... registrant=person, product=product, branch_name='import', | 23 | ... registrant=person, product=product, branch_name='import', |
937 | 22 | ... svn_branch_url="http://svn.domain.com/source")) | 24 | ... svn_branch_url="http://svn.domain.com/source")) |
938 | 25 | ======= | ||
939 | 26 | ... registrant=person, product=product, branch_name='import', | ||
940 | 27 | ... svn_branch_url=svn_branch_url)) | ||
941 | 28 | >>>>>>> MERGE-SOURCE | ||
942 | 23 | >>> no_import_branch = removeSecurityProxy(factory.makeProductBranch( | 29 | >>> no_import_branch = removeSecurityProxy(factory.makeProductBranch( |
943 | 24 | ... owner=person, product=product, name='no-import')) | 30 | ... owner=person, product=product, name='no-import')) |
944 | 25 | >>> logout() | 31 | >>> logout() |
945 | @@ -58,171 +64,365 @@ | |||
946 | 58 | >>> print representation['rcs_type'] | 64 | >>> print representation['rcs_type'] |
947 | 59 | Subversion via CSCVS | 65 | Subversion via CSCVS |
948 | 60 | >>> print representation['url'] | 66 | >>> print representation['url'] |
1117 | 61 | http://svn.domain.com/source | 67 | <<<<<<< TREE |
1118 | 62 | >>> print representation['cvs_root'] | 68 | http://svn.domain.com/source |
1119 | 63 | None | 69 | >>> print representation['cvs_root'] |
1120 | 64 | >>> print representation['cvs_module'] | 70 | None |
1121 | 65 | None | 71 | >>> print representation['cvs_module'] |
1122 | 66 | >>> print representation['date_last_successful'] | 72 | None |
1123 | 67 | None | 73 | >>> print representation['date_last_successful'] |
1124 | 68 | 74 | None | |
1125 | 69 | 75 | ||
1126 | 70 | Package Branches | 76 | |
1127 | 71 | ---------------- | 77 | Package Branches |
1128 | 72 | 78 | ---------------- | |
1129 | 73 | The same is true for package branches. | 79 | |
1130 | 74 | 80 | The same is true for package branches. | |
1131 | 75 | >>> login(ANONYMOUS) | 81 | |
1132 | 76 | >>> distribution = factory.makeDistribution(name='scruffbuntu') | 82 | >>> login(ANONYMOUS) |
1133 | 77 | >>> distroseries = factory.makeDistroSeries( | 83 | >>> distribution = factory.makeDistribution(name='scruffbuntu') |
1134 | 78 | ... name='manic', distribution=distribution) | 84 | >>> distroseries = factory.makeDistroSeries( |
1135 | 79 | >>> source_package = factory.makeSourcePackage( | 85 | ... name='manic', distribution=distribution) |
1136 | 80 | ... sourcepackagename='scruff', distroseries=distroseries) | 86 | >>> source_package = factory.makeSourcePackage( |
1137 | 81 | >>> code_import = removeSecurityProxy(factory.makePackageCodeImport( | 87 | ... sourcepackagename='scruff', distroseries=distroseries) |
1138 | 82 | ... registrant=person, sourcepackage=source_package, | 88 | >>> code_import = removeSecurityProxy(factory.makePackageCodeImport( |
1139 | 83 | ... branch_name='import', | 89 | ... registrant=person, sourcepackage=source_package, |
1140 | 84 | ... svn_branch_url="http://svn.domain.com/package_source")) | 90 | ... branch_name='import', |
1141 | 85 | >>> logout() | 91 | ... svn_branch_url="http://svn.domain.com/package_source")) |
1142 | 86 | 92 | >>> logout() | |
1143 | 87 | There is a link on the branch object | 93 | |
1144 | 88 | 94 | There is a link on the branch object | |
1145 | 89 | >>> branch_url = '/' + code_import.branch.unique_name | 95 | |
1146 | 90 | >>> response = import_webservice.get(branch_url) | 96 | >>> branch_url = '/' + code_import.branch.unique_name |
1147 | 91 | >>> representation = response.jsonBody() | 97 | >>> response = import_webservice.get(branch_url) |
1148 | 92 | >>> print representation['code_import_link'] | 98 | >>> representation = response.jsonBody() |
1149 | 93 | http://.../~import-owner/scruffbuntu/manic/scruff/import/+code-import | 99 | >>> print representation['code_import_link'] |
1150 | 94 | 100 | http://.../~import-owner/scruffbuntu/manic/scruff/import/+code-import | |
1151 | 95 | and there is information available about the import itsef. | 101 | |
1152 | 96 | 102 | and there is information available about the import itsef. | |
1153 | 97 | >>> import_url = representation['code_import_link'] | 103 | |
1154 | 98 | >>> response = import_webservice.get(import_url) | 104 | >>> import_url = representation['code_import_link'] |
1155 | 99 | >>> representation = response.jsonBody() | 105 | >>> response = import_webservice.get(import_url) |
1156 | 100 | >>> print representation['self_link'] == import_url | 106 | >>> representation = response.jsonBody() |
1157 | 101 | True | 107 | >>> print representation['self_link'] == import_url |
1158 | 102 | >>> print representation['branch_link'] | 108 | True |
1159 | 103 | http://.../~import-owner/scruffbuntu/manic/scruff/import | 109 | >>> print representation['branch_link'] |
1160 | 104 | >>> print representation['review_status'] | 110 | http://.../~import-owner/scruffbuntu/manic/scruff/import |
1161 | 105 | Pending Review | 111 | >>> print representation['review_status'] |
1162 | 106 | >>> print representation['rcs_type'] | 112 | Pending Review |
1163 | 107 | Subversion via CSCVS | 113 | >>> print representation['rcs_type'] |
1164 | 108 | >>> print representation['url'] | 114 | Subversion via CSCVS |
1165 | 109 | http://svn.domain.com/package_source | 115 | >>> print representation['url'] |
1166 | 110 | >>> print representation['cvs_root'] | 116 | http://svn.domain.com/package_source |
1167 | 111 | None | 117 | >>> print representation['cvs_root'] |
1168 | 112 | >>> print representation['cvs_module'] | 118 | None |
1169 | 113 | None | 119 | >>> print representation['cvs_module'] |
1170 | 114 | >>> print representation['date_last_successful'] | 120 | None |
1171 | 115 | None | 121 | >>> print representation['date_last_successful'] |
1172 | 116 | 122 | None | |
1173 | 117 | 123 | ||
1174 | 118 | == Creating Imports == | 124 | |
1175 | 119 | 125 | == Creating Imports == | |
1176 | 120 | We can create an import using the API by calling a method on the project. | 126 | |
1177 | 121 | 127 | We can create an import using the API by calling a method on the project. | |
1178 | 122 | >>> product_url = '/' + product.name | 128 | |
1179 | 123 | >>> new_remote_url = factory.getUniqueURL() | 129 | >>> product_url = '/' + product.name |
1180 | 124 | >>> response = import_webservice.named_post(product_url, 'newCodeImport', | 130 | >>> new_remote_url = factory.getUniqueURL() |
1181 | 125 | ... branch_name='new-import', rcs_type='Git', | 131 | >>> response = import_webservice.named_post(product_url, 'newCodeImport', |
1182 | 126 | ... url=new_remote_url) | 132 | ... branch_name='new-import', rcs_type='Git', |
1183 | 127 | >>> print response.status | 133 | ... url=new_remote_url) |
1184 | 128 | 201 | 134 | >>> print response.status |
1185 | 129 | >>> location = response.getHeader('Location') | 135 | 201 |
1186 | 130 | >>> response = import_webservice.get(location) | 136 | >>> location = response.getHeader('Location') |
1187 | 131 | >>> representation = response.jsonBody() | 137 | >>> response = import_webservice.get(location) |
1188 | 132 | >>> print representation['self_link'] | 138 | >>> representation = response.jsonBody() |
1189 | 133 | http://.../~import-owner/scruff/new-import/+code-import | 139 | >>> print representation['self_link'] |
1190 | 134 | >>> print representation['branch_link'] | 140 | http://.../~import-owner/scruff/new-import/+code-import |
1191 | 135 | http://.../~import-owner/scruff/new-import | 141 | >>> print representation['branch_link'] |
1192 | 136 | >>> print representation['rcs_type'] | 142 | http://.../~import-owner/scruff/new-import |
1193 | 137 | Git | 143 | >>> print representation['rcs_type'] |
1194 | 138 | >>> print representation['url'] == new_remote_url | 144 | Git |
1195 | 139 | True | 145 | >>> print representation['url'] == new_remote_url |
1196 | 140 | >>> print representation['cvs_root'] | 146 | True |
1197 | 141 | None | 147 | >>> print representation['cvs_root'] |
1198 | 142 | >>> print representation['cvs_module'] | 148 | None |
1199 | 143 | None | 149 | >>> print representation['cvs_module'] |
1200 | 144 | >>> print representation['date_last_successful'] | 150 | None |
1201 | 145 | None | 151 | >>> print representation['date_last_successful'] |
1202 | 146 | 152 | None | |
1203 | 147 | If we must we can create a CVS import. | 153 | |
1204 | 148 | 154 | If we must we can create a CVS import. | |
1205 | 149 | >>> product_url = '/' + product.name | 155 | |
1206 | 150 | >>> new_remote_url = factory.getUniqueURL() | 156 | >>> product_url = '/' + product.name |
1207 | 151 | >>> response = import_webservice.named_post(product_url, 'newCodeImport', | 157 | >>> new_remote_url = factory.getUniqueURL() |
1208 | 152 | ... branch_name='cvs-import', rcs_type='Concurrent Versions System', | 158 | >>> response = import_webservice.named_post(product_url, 'newCodeImport', |
1209 | 153 | ... cvs_root=new_remote_url, cvs_module="foo") | 159 | ... branch_name='cvs-import', rcs_type='Concurrent Versions System', |
1210 | 154 | >>> print response.status | 160 | ... cvs_root=new_remote_url, cvs_module="foo") |
1211 | 155 | 201 | 161 | >>> print response.status |
1212 | 156 | >>> location = response.getHeader('Location') | 162 | 201 |
1213 | 157 | >>> response = import_webservice.get(location) | 163 | >>> location = response.getHeader('Location') |
1214 | 158 | >>> representation = response.jsonBody() | 164 | >>> response = import_webservice.get(location) |
1215 | 159 | >>> print representation['self_link'] | 165 | >>> representation = response.jsonBody() |
1216 | 160 | http://.../~import-owner/scruff/cvs-import/+code-import | 166 | >>> print representation['self_link'] |
1217 | 161 | >>> print representation['branch_link'] | 167 | http://.../~import-owner/scruff/cvs-import/+code-import |
1218 | 162 | http://.../~import-owner/scruff/cvs-import | 168 | >>> print representation['branch_link'] |
1219 | 163 | >>> print representation['rcs_type'] | 169 | http://.../~import-owner/scruff/cvs-import |
1220 | 164 | Concurrent Versions System | 170 | >>> print representation['rcs_type'] |
1221 | 165 | >>> print representation['url'] | 171 | Concurrent Versions System |
1222 | 166 | None | 172 | >>> print representation['url'] |
1223 | 167 | >>> print representation['cvs_root'] == new_remote_url | 173 | None |
1224 | 168 | True | 174 | >>> print representation['cvs_root'] == new_remote_url |
1225 | 169 | >>> print representation['cvs_module'] == "foo" | 175 | True |
1226 | 170 | True | 176 | >>> print representation['cvs_module'] == "foo" |
1227 | 171 | >>> print representation['date_last_successful'] | 177 | True |
1228 | 172 | None | 178 | >>> print representation['date_last_successful'] |
1229 | 173 | 179 | None | |
1230 | 174 | We can also create an import targetting a source package. | 180 | |
1231 | 175 | 181 | We can also create an import targetting a source package. | |
1232 | 176 | >>> source_package_url = ( | 182 | |
1233 | 177 | ... '/' + distribution.name + '/' + distroseries.name + '/+source/' | 183 | >>> source_package_url = ( |
1234 | 178 | ... + source_package.name) | 184 | ... '/' + distribution.name + '/' + distroseries.name + '/+source/' |
1235 | 179 | >>> new_remote_url = factory.getUniqueURL() | 185 | ... + source_package.name) |
1236 | 180 | >>> response = import_webservice.named_post(source_package_url, | 186 | >>> new_remote_url = factory.getUniqueURL() |
1237 | 181 | ... 'newCodeImport', branch_name='new-import', rcs_type='Mercurial', | 187 | >>> response = import_webservice.named_post(source_package_url, |
1238 | 182 | ... url=new_remote_url) | 188 | ... 'newCodeImport', branch_name='new-import', rcs_type='Mercurial', |
1239 | 183 | >>> print response.status | 189 | ... url=new_remote_url) |
1240 | 184 | 201 | 190 | >>> print response.status |
1241 | 185 | >>> location = response.getHeader('Location') | 191 | 201 |
1242 | 186 | >>> response = import_webservice.get(location) | 192 | >>> location = response.getHeader('Location') |
1243 | 187 | >>> representation = response.jsonBody() | 193 | >>> response = import_webservice.get(location) |
1244 | 188 | >>> print representation['self_link'] | 194 | >>> representation = response.jsonBody() |
1245 | 189 | http://.../~import-owner/scruffbuntu/manic/scruff/new-import/+code-import | 195 | >>> print representation['self_link'] |
1246 | 190 | >>> print representation['branch_link'] | 196 | http://.../~import-owner/scruffbuntu/manic/scruff/new-import/+code-import |
1247 | 191 | http://.../~import-owner/scruffbuntu/manic/scruff/new-import | 197 | >>> print representation['branch_link'] |
1248 | 192 | >>> print representation['rcs_type'] | 198 | http://.../~import-owner/scruffbuntu/manic/scruff/new-import |
1249 | 193 | Mercurial | 199 | >>> print representation['rcs_type'] |
1250 | 194 | >>> print representation['url'] == new_remote_url | 200 | Mercurial |
1251 | 195 | True | 201 | >>> print representation['url'] == new_remote_url |
1252 | 196 | >>> print representation['cvs_root'] | 202 | True |
1253 | 197 | None | 203 | >>> print representation['cvs_root'] |
1254 | 198 | >>> print representation['cvs_module'] | 204 | None |
1255 | 199 | None | 205 | >>> print representation['cvs_module'] |
1256 | 200 | >>> print representation['date_last_successful'] | 206 | None |
1257 | 201 | None | 207 | >>> print representation['date_last_successful'] |
1258 | 202 | 208 | None | |
1259 | 203 | If we wish to create a branch owned by a team we are part of then we can. | 209 | |
1260 | 204 | 210 | If we wish to create a branch owned by a team we are part of then we can. | |
1261 | 205 | >>> team_url = import_webservice.getAbsoluteUrl('/~import-owner-team') | 211 | |
1262 | 206 | >>> new_remote_url = factory.getUniqueURL() | 212 | >>> team_url = import_webservice.getAbsoluteUrl('/~import-owner-team') |
1263 | 207 | >>> response = import_webservice.named_post(product_url, 'newCodeImport', | 213 | >>> new_remote_url = factory.getUniqueURL() |
1264 | 208 | ... branch_name='team-import', rcs_type='Git', | 214 | >>> response = import_webservice.named_post(product_url, 'newCodeImport', |
1265 | 209 | ... url=new_remote_url, owner=team_url) | 215 | ... branch_name='team-import', rcs_type='Git', |
1266 | 210 | >>> print response.status | 216 | ... url=new_remote_url, owner=team_url) |
1267 | 211 | 201 | 217 | >>> print response.status |
1268 | 212 | >>> location = response.getHeader('Location') | 218 | 201 |
1269 | 213 | >>> response = import_webservice.get(location) | 219 | >>> location = response.getHeader('Location') |
1270 | 214 | >>> representation = response.jsonBody() | 220 | >>> response = import_webservice.get(location) |
1271 | 215 | >>> print representation['self_link'] | 221 | >>> representation = response.jsonBody() |
1272 | 216 | http://.../~import-owner-team/scruff/team-import/+code-import | 222 | >>> print representation['self_link'] |
1273 | 217 | >>> print representation['branch_link'] | 223 | http://.../~import-owner-team/scruff/team-import/+code-import |
1274 | 218 | http://.../~import-owner-team/scruff/team-import | 224 | >>> print representation['branch_link'] |
1275 | 219 | >>> print representation['rcs_type'] | 225 | http://.../~import-owner-team/scruff/team-import |
1276 | 220 | Git | 226 | >>> print representation['rcs_type'] |
1277 | 221 | >>> print representation['url'] == new_remote_url | 227 | Git |
1278 | 222 | True | 228 | >>> print representation['url'] == new_remote_url |
1279 | 223 | >>> print representation['cvs_root'] | 229 | True |
1280 | 224 | None | 230 | >>> print representation['cvs_root'] |
1281 | 225 | >>> print representation['cvs_module'] | 231 | None |
1282 | 226 | None | 232 | >>> print representation['cvs_module'] |
1283 | 227 | >>> print representation['date_last_successful'] | 233 | None |
1284 | 228 | None | 234 | >>> print representation['date_last_successful'] |
1285 | 235 | None | ||
1286 | 236 | ======= | ||
1287 | 237 | http://svn.domain.com/source | ||
1288 | 238 | >>> print representation['cvs_root'] | ||
1289 | 239 | None | ||
1290 | 240 | >>> print representation['cvs_module'] | ||
1291 | 241 | None | ||
1292 | 242 | >>> print representation['date_last_successful'] | ||
1293 | 243 | None | ||
1294 | 244 | |||
1295 | 245 | |||
1296 | 246 | Package Branches | ||
1297 | 247 | ---------------- | ||
1298 | 248 | |||
1299 | 249 | The same is true for package branches. | ||
1300 | 250 | |||
1301 | 251 | >>> login(ANONYMOUS) | ||
1302 | 252 | >>> distribution = factory.makeDistribution(name='scruffbuntu') | ||
1303 | 253 | >>> distroseries = factory.makeDistroSeries( | ||
1304 | 254 | ... name='manic', distribution=distribution) | ||
1305 | 255 | >>> source_package = factory.makeSourcePackage( | ||
1306 | 256 | ... sourcepackagename='scruff', distroseries=distroseries) | ||
1307 | 257 | >>> code_import = removeSecurityProxy(factory.makePackageCodeImport( | ||
1308 | 258 | ... registrant=person, sourcepackage=source_package, | ||
1309 | 259 | ... branch_name='import', | ||
1310 | 260 | ... svn_branch_url="http://svn.domain.com/package_source")) | ||
1311 | 261 | >>> logout() | ||
1312 | 262 | >>> import_webservice = webservice_for_person( | ||
1313 | 263 | ... person, permission=OAuthPermission.WRITE_PUBLIC) | ||
1314 | 264 | |||
1315 | 265 | There is a link on the branch object | ||
1316 | 266 | |||
1317 | 267 | >>> branch_url = '/' + code_import.branch.unique_name | ||
1318 | 268 | >>> response = import_webservice.get(branch_url) | ||
1319 | 269 | >>> representation = response.jsonBody() | ||
1320 | 270 | >>> print representation['code_import_link'] | ||
1321 | 271 | http://.../~import-owner/scruffbuntu/manic/scruff/import/+code-import | ||
1322 | 272 | |||
1323 | 273 | and there is information available about the import itsef. | ||
1324 | 274 | |||
1325 | 275 | >>> import_url = representation['code_import_link'] | ||
1326 | 276 | >>> response = import_webservice.get(import_url) | ||
1327 | 277 | >>> representation = response.jsonBody() | ||
1328 | 278 | >>> print representation['self_link'] == import_url | ||
1329 | 279 | True | ||
1330 | 280 | >>> print representation['branch_link'] | ||
1331 | 281 | http://.../~import-owner/scruffbuntu/manic/scruff/import | ||
1332 | 282 | >>> print representation['review_status'] | ||
1333 | 283 | Pending Review | ||
1334 | 284 | >>> print representation['rcs_type'] | ||
1335 | 285 | Subversion via CSCVS | ||
1336 | 286 | >>> print representation['url'] | ||
1337 | 287 | http://svn.domain.com/package_source | ||
1338 | 288 | >>> print representation['cvs_root'] | ||
1339 | 289 | None | ||
1340 | 290 | >>> print representation['cvs_module'] | ||
1341 | 291 | None | ||
1342 | 292 | >>> print representation['date_last_successful'] | ||
1343 | 293 | None | ||
1344 | 294 | |||
1345 | 295 | == Creating Imports == | ||
1346 | 296 | |||
1347 | 297 | We can create an import using the API by calling a method on the project. | ||
1348 | 298 | |||
1349 | 299 | >>> product_url = '/' + product.name | ||
1350 | 300 | >>> new_remote_url = factory.getUniqueURL() | ||
1351 | 301 | >>> response = import_webservice.named_post(product_url, 'newCodeImport', | ||
1352 | 302 | ... branch_name='new-import', rcs_type='Git', | ||
1353 | 303 | ... url=new_remote_url) | ||
1354 | 304 | >>> print response.status | ||
1355 | 305 | 201 | ||
1356 | 306 | >>> location = response.getHeader('Location') | ||
1357 | 307 | >>> response = import_webservice.get(location) | ||
1358 | 308 | >>> representation = response.jsonBody() | ||
1359 | 309 | >>> print representation['self_link'] | ||
1360 | 310 | http://.../~import-owner/scruff/new-import/+code-import | ||
1361 | 311 | >>> print representation['branch_link'] | ||
1362 | 312 | http://.../~import-owner/scruff/new-import | ||
1363 | 313 | >>> print representation['rcs_type'] | ||
1364 | 314 | Git | ||
1365 | 315 | >>> print representation['url'] == new_remote_url | ||
1366 | 316 | True | ||
1367 | 317 | >>> print representation['cvs_root'] | ||
1368 | 318 | None | ||
1369 | 319 | >>> print representation['cvs_module'] | ||
1370 | 320 | None | ||
1371 | 321 | >>> print representation['date_last_successful'] | ||
1372 | 322 | None | ||
1373 | 323 | |||
1374 | 324 | If we must we can create a CVS import. | ||
1375 | 325 | |||
1376 | 326 | >>> product_url = '/' + product.name | ||
1377 | 327 | >>> new_remote_url = factory.getUniqueURL() | ||
1378 | 328 | >>> response = import_webservice.named_post(product_url, 'newCodeImport', | ||
1379 | 329 | ... branch_name='cvs-import', rcs_type='Concurrent Versions System', | ||
1380 | 330 | ... cvs_root=new_remote_url, cvs_module="foo") | ||
1381 | 331 | >>> print response.status | ||
1382 | 332 | 201 | ||
1383 | 333 | >>> location = response.getHeader('Location') | ||
1384 | 334 | >>> response = import_webservice.get(location) | ||
1385 | 335 | >>> representation = response.jsonBody() | ||
1386 | 336 | >>> print representation['self_link'] | ||
1387 | 337 | http://.../~import-owner/scruff/cvs-import/+code-import | ||
1388 | 338 | >>> print representation['branch_link'] | ||
1389 | 339 | http://.../~import-owner/scruff/cvs-import | ||
1390 | 340 | >>> print representation['rcs_type'] | ||
1391 | 341 | Concurrent Versions System | ||
1392 | 342 | >>> print representation['url'] | ||
1393 | 343 | None | ||
1394 | 344 | >>> print representation['cvs_root'] == new_remote_url | ||
1395 | 345 | True | ||
1396 | 346 | >>> print representation['cvs_module'] == "foo" | ||
1397 | 347 | True | ||
1398 | 348 | >>> print representation['date_last_successful'] | ||
1399 | 349 | None | ||
1400 | 350 | |||
1401 | 351 | We can also create an import targetting a source package. | ||
1402 | 352 | |||
1403 | 353 | >>> source_package_url = ( | ||
1404 | 354 | ... '/' + distribution.name + '/' + distroseries.name + '/+source/' | ||
1405 | 355 | ... + source_package.name) | ||
1406 | 356 | >>> new_remote_url = factory.getUniqueURL() | ||
1407 | 357 | >>> response = import_webservice.named_post(source_package_url, | ||
1408 | 358 | ... 'newCodeImport', branch_name='new-import', rcs_type='Mercurial', | ||
1409 | 359 | ... url=new_remote_url) | ||
1410 | 360 | >>> print response.status | ||
1411 | 361 | 201 | ||
1412 | 362 | >>> location = response.getHeader('Location') | ||
1413 | 363 | >>> response = import_webservice.get(location) | ||
1414 | 364 | >>> representation = response.jsonBody() | ||
1415 | 365 | >>> print representation['self_link'] | ||
1416 | 366 | http://.../~import-owner/scruffbuntu/manic/scruff/new-import/+code-import | ||
1417 | 367 | >>> print representation['branch_link'] | ||
1418 | 368 | http://.../~import-owner/scruffbuntu/manic/scruff/new-import | ||
1419 | 369 | >>> print representation['rcs_type'] | ||
1420 | 370 | Mercurial | ||
1421 | 371 | >>> print representation['url'] == new_remote_url | ||
1422 | 372 | True | ||
1423 | 373 | >>> print representation['cvs_root'] | ||
1424 | 374 | None | ||
1425 | 375 | >>> print representation['cvs_module'] | ||
1426 | 376 | None | ||
1427 | 377 | >>> print representation['date_last_successful'] | ||
1428 | 378 | None | ||
1429 | 379 | |||
1430 | 380 | If we wish to create a branch owned by a team we are part of then we can. | ||
1431 | 381 | |||
1432 | 382 | >>> team_url = import_webservice.getAbsoluteUrl('/~import-owner-team') | ||
1433 | 383 | >>> new_remote_url = factory.getUniqueURL() | ||
1434 | 384 | >>> response = import_webservice.named_post(product_url, 'newCodeImport', | ||
1435 | 385 | ... branch_name='team-import', rcs_type='Git', | ||
1436 | 386 | ... url=new_remote_url, owner=team_url) | ||
1437 | 387 | >>> print response.status | ||
1438 | 388 | 201 | ||
1439 | 389 | >>> location = response.getHeader('Location') | ||
1440 | 390 | >>> response = import_webservice.get(location) | ||
1441 | 391 | >>> representation = response.jsonBody() | ||
1442 | 392 | >>> print representation['self_link'] | ||
1443 | 393 | http://.../~import-owner-team/scruff/team-import/+code-import | ||
1444 | 394 | >>> print representation['branch_link'] | ||
1445 | 395 | http://.../~import-owner-team/scruff/team-import | ||
1446 | 396 | >>> print representation['rcs_type'] | ||
1447 | 397 | Git | ||
1448 | 398 | >>> print representation['url'] == new_remote_url | ||
1449 | 399 | True | ||
1450 | 400 | >>> print representation['cvs_root'] | ||
1451 | 401 | None | ||
1452 | 402 | >>> print representation['cvs_module'] | ||
1453 | 403 | None | ||
1454 | 404 | >>> print representation['date_last_successful'] | ||
1455 | 405 | None | ||
1456 | 406 | |||
1457 | 407 | |||
1458 | 408 | == Requesting an Import == | ||
1459 | 409 | |||
1460 | 410 | You can request that an approved, working import happen soon over the | ||
1461 | 411 | API using the requestImport() method. | ||
1462 | 412 | |||
1463 | 413 | >>> login(ANONYMOUS) | ||
1464 | 414 | >>> git_import = factory.makeProductCodeImport( | ||
1465 | 415 | ... registrant=person, product=product, branch_name='git-import', | ||
1466 | 416 | ... git_repo_url=factory.getUniqueURL()) | ||
1467 | 417 | >>> git_import_url = ( | ||
1468 | 418 | ... '/' + git_import.branch.unique_name + '/+code-import') | ||
1469 | 419 | >>> logout() | ||
1470 | 420 | >>> import_webservice = webservice_for_person( | ||
1471 | 421 | ... person, permission=OAuthPermission.WRITE_PUBLIC) | ||
1472 | 422 | >>> response = import_webservice.named_post( | ||
1473 | 423 | ... git_import_url, 'requestImport') | ||
1474 | 424 | >>> print response.status | ||
1475 | 425 | 200 | ||
1476 | 426 | >>> print response.jsonBody() | ||
1477 | 427 | None | ||
1478 | 428 | >>>>>>> MERGE-SOURCE | ||
1479 | 229 | 429 | ||
1480 | === modified file 'lib/lp/services/worlddata/doc/language.txt' | |||
1481 | --- lib/lp/services/worlddata/doc/language.txt 2010-02-17 10:39:16 +0000 | |||
1482 | +++ lib/lp/services/worlddata/doc/language.txt 2010-04-16 04:01:13 +0000 | |||
1483 | @@ -260,8 +260,40 @@ | |||
1484 | 260 | Serbian ("Latn" variant) | 260 | Serbian ("Latn" variant) |
1485 | 261 | 261 | ||
1486 | 262 | 262 | ||
1487 | 263 | translators | ||
1488 | 264 | =========== | ||
1489 | 265 | |||
1490 | 266 | Property `translators` contains the list of `Person`s who are considered | ||
1491 | 267 | translators for this language. | ||
1492 | 268 | |||
1493 | 269 | >>> sr = language_set.getLanguageByCode('sr') | ||
1494 | 270 | >>> list(sr.translators) | ||
1495 | 271 | [] | ||
1496 | 272 | |||
1497 | 273 | To be considered a translator, they must have done some translations and | ||
1498 | 274 | have the language among their preferred languages. | ||
1499 | 275 | |||
1500 | 276 | >>> translator = factory.makePerson(name=u'serbian-translator') | ||
1501 | 277 | >>> translator.addLanguage(sr) | ||
1502 | 278 | >>> from canonical.testing import LaunchpadZopelessLayer | ||
1503 | 279 | >>> LaunchpadZopelessLayer.commit() | ||
1504 | 280 | |||
1505 | 281 | # We need to fake some Karma. | ||
1506 | 282 | >>> from lp.registry.model.karma import KarmaCategory, KarmaCache | ||
1507 | 283 | >>> LaunchpadZopelessLayer.switchDbUser('karma') | ||
1508 | 284 | >>> translations_category = KarmaCategory.selectOne( | ||
1509 | 285 | ... KarmaCategory.name=='translations') | ||
1510 | 286 | >>> karma = KarmaCache(person=translator, | ||
1511 | 287 | ... category=translations_category, | ||
1512 | 288 | ... karmavalue=1) | ||
1513 | 289 | >>> LaunchpadZopelessLayer.commit() | ||
1514 | 290 | >>> LaunchpadZopelessLayer.switchDbUser('launchpad') | ||
1515 | 291 | >>> [translator.name for translator in sr.translators] | ||
1516 | 292 | [u'serbian-translator'] | ||
1517 | 293 | |||
1518 | 294 | |||
1519 | 263 | ========= | 295 | ========= |
1521 | 264 | countries | 296 | Countries |
1522 | 265 | ========= | 297 | ========= |
1523 | 266 | 298 | ||
1524 | 267 | Property holding a list of countries a language is spoken in, and allowing | 299 | Property holding a list of countries a language is spoken in, and allowing |
1525 | 268 | 300 | ||
1526 | === modified file 'lib/lp/services/worlddata/interfaces/language.py' | |||
1527 | --- lib/lp/services/worlddata/interfaces/language.py 2010-03-05 14:02:05 +0000 | |||
1528 | +++ lib/lp/services/worlddata/interfaces/language.py 2010-04-16 04:01:13 +0000 | |||
1529 | @@ -16,6 +16,7 @@ | |||
1530 | 16 | from zope.schema import TextLine, Int, Choice, Bool, Field, Set | 16 | from zope.schema import TextLine, Int, Choice, Bool, Field, Set |
1531 | 17 | from zope.interface import Interface, Attribute | 17 | from zope.interface import Interface, Attribute |
1532 | 18 | from lazr.enum import DBEnumeratedType, DBItem | 18 | from lazr.enum import DBEnumeratedType, DBItem |
1533 | 19 | from lazr.lifecycle.snapshot import doNotSnapshot | ||
1534 | 19 | 20 | ||
1535 | 20 | from lazr.restful.declarations import ( | 21 | from lazr.restful.declarations import ( |
1536 | 21 | collection_default_content, exported, export_as_webservice_collection, | 22 | collection_default_content, exported, export_as_webservice_collection, |
1537 | @@ -75,9 +76,9 @@ | |||
1538 | 75 | required=False), | 76 | required=False), |
1539 | 76 | exported_as='plural_expression') | 77 | exported_as='plural_expression') |
1540 | 77 | 78 | ||
1542 | 78 | translators = Field( | 79 | translators = doNotSnapshot(Field( |
1543 | 79 | title=u'List of Person/Team that translate into this language.', | 80 | title=u'List of Person/Team that translate into this language.', |
1545 | 80 | required=True) | 81 | required=True)) |
1546 | 81 | 82 | ||
1547 | 82 | translators_count = exported( | 83 | translators_count = exported( |
1548 | 83 | Int( | 84 | Int( |
1549 | 84 | 85 | ||
1550 | === modified file 'lib/lp/services/worlddata/tests/test_doc.py' | |||
1551 | --- lib/lp/services/worlddata/tests/test_doc.py 2009-06-30 16:56:07 +0000 | |||
1552 | +++ lib/lp/services/worlddata/tests/test_doc.py 2010-04-16 04:01:13 +0000 | |||
1553 | @@ -6,9 +6,20 @@ | |||
1554 | 6 | """ | 6 | """ |
1555 | 7 | 7 | ||
1556 | 8 | import os | 8 | import os |
1557 | 9 | |||
1558 | 10 | from canonical.launchpad.testing.systemdocs import ( | ||
1559 | 11 | LayeredDocFileSuite, setUp, tearDown) | ||
1560 | 12 | from canonical.testing import LaunchpadZopelessLayer | ||
1561 | 13 | |||
1562 | 9 | from lp.services.testing import build_test_suite | 14 | from lp.services.testing import build_test_suite |
1563 | 10 | 15 | ||
1564 | 11 | here = os.path.dirname(os.path.realpath(__file__)) | 16 | here = os.path.dirname(os.path.realpath(__file__)) |
1565 | 17 | special = { | ||
1566 | 18 | 'language.txt': LayeredDocFileSuite( | ||
1567 | 19 | '../doc/language.txt', | ||
1568 | 20 | layer=LaunchpadZopelessLayer, | ||
1569 | 21 | setUp=setUp, tearDown=tearDown), | ||
1570 | 22 | } | ||
1571 | 12 | 23 | ||
1572 | 13 | def test_suite(): | 24 | def test_suite(): |
1574 | 14 | return build_test_suite(here) | 25 | return build_test_suite(here, special) |
1575 | 15 | 26 | ||
1576 | === added file 'lib/lp/services/worlddata/tests/test_language.py' | |||
1577 | --- lib/lp/services/worlddata/tests/test_language.py 1970-01-01 00:00:00 +0000 | |||
1578 | +++ lib/lp/services/worlddata/tests/test_language.py 2010-04-16 04:01:13 +0000 | |||
1579 | @@ -0,0 +1,21 @@ | |||
1580 | 1 | # Copyright 2010 Canonical Ltd. This software is licensed under the | ||
1581 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | ||
1582 | 3 | |||
1583 | 4 | __metaclass__ = type | ||
1584 | 5 | |||
1585 | 6 | from canonical.testing import FunctionalLayer | ||
1586 | 7 | from lazr.lifecycle.interfaces import IDoNotSnapshot | ||
1587 | 8 | from lp.services.worlddata.interfaces.language import ILanguage | ||
1588 | 9 | from lp.testing import TestCaseWithFactory | ||
1589 | 10 | |||
1590 | 11 | |||
1591 | 12 | class TestLanguageWebservice(TestCaseWithFactory): | ||
1592 | 13 | """Test Language web service API.""" | ||
1593 | 14 | |||
1594 | 15 | layer = FunctionalLayer | ||
1595 | 16 | |||
1596 | 17 | def test_translators(self): | ||
1597 | 18 | self.failUnless( | ||
1598 | 19 | IDoNotSnapshot.providedBy(ILanguage['translators']), | ||
1599 | 20 | "ILanguage.translators should not be included in snapshots, " | ||
1600 | 21 | "see bug 553093.") | ||
1601 | 0 | 22 | ||
1602 | === modified file 'lib/lp/soyuz/doc/archive.txt' | |||
1603 | --- lib/lp/soyuz/doc/archive.txt 2010-04-08 02:35:06 +0000 | |||
1604 | +++ lib/lp/soyuz/doc/archive.txt 2010-04-16 04:01:13 +0000 | |||
1605 | @@ -1407,10 +1407,10 @@ | |||
1606 | 1407 | ppa | 1407 | ppa |
1607 | 1408 | 1408 | ||
1608 | 1409 | We can take the opportunity to check if the default 'authorized_size' | 1409 | We can take the opportunity to check if the default 'authorized_size' |
1610 | 1410 | corresponds to what we state in our policy, 1024 MiB: | 1410 | corresponds to what we state in our policy, 2048 MiB: |
1611 | 1411 | 1411 | ||
1612 | 1412 | >>> name16.archive.authorized_size | 1412 | >>> name16.archive.authorized_size |
1614 | 1413 | 1024 | 1413 | 2048 |
1615 | 1414 | 1414 | ||
1616 | 1415 | An archive is also associated with a distribution. This can be found on | 1415 | An archive is also associated with a distribution. This can be found on |
1617 | 1416 | the distribution property. The default distribution is "ubuntu": | 1416 | the distribution property. The default distribution is "ubuntu": |
1618 | 1417 | 1417 | ||
1619 | === modified file 'lib/lp/soyuz/doc/buildd-slavescanner.txt' | |||
1620 | === modified file 'lib/lp/soyuz/doc/distroseriesqueue-translations.txt' | |||
1621 | --- lib/lp/soyuz/doc/distroseriesqueue-translations.txt 2010-03-12 13:39:33 +0000 | |||
1622 | +++ lib/lp/soyuz/doc/distroseriesqueue-translations.txt 2010-04-16 04:01:13 +0000 | |||
1623 | @@ -297,8 +297,35 @@ | |||
1624 | 297 | 297 | ||
1625 | 298 | >>> queue_item.customfiles[0].publish(mock_logger) | 298 | >>> queue_item.customfiles[0].publish(mock_logger) |
1626 | 299 | DEBUG: Publishing custom pmount, pmount_0.9.7-2ubuntu2_amd64_translations.tar.gz to ubuntu/dapper | 299 | DEBUG: Publishing custom pmount, pmount_0.9.7-2ubuntu2_amd64_translations.tar.gz to ubuntu/dapper |
1629 | 300 | DEBUG: Skipping translations since it is a PPA. | 300 | DEBUG: Skipping translations since its purpose is not in |
1630 | 301 | 301 | MAIN_ARCHIVE_PURPOSES. | |
1631 | 302 | |||
1632 | 303 | # And this time, we see that there are no entries imported in the queue. | ||
1633 | 304 | >>> translation_import_queue.getAllEntries(target=ubuntu).count() | ||
1634 | 305 | 0 | ||
1635 | 306 | >>> transaction.abort() | ||
1636 | 307 | |||
1637 | 308 | |||
1638 | 309 | == Translations from a rebuild == | ||
1639 | 310 | |||
1640 | 311 | Translations coming from rebuilt packages are also ignored. | ||
1641 | 312 | |||
1642 | 313 | >>> from lp.registry.interfaces.person import IPersonSet | ||
1643 | 314 | >>> from lp.soyuz.interfaces.archive import ArchivePurpose, IArchiveSet | ||
1644 | 315 | |||
1645 | 316 | >>> foobar_archive = getUtility(IArchiveSet).new( | ||
1646 | 317 | ... purpose=ArchivePurpose.COPY, | ||
1647 | 318 | ... owner=getUtility(IPersonSet).getByName('name16'), | ||
1648 | 319 | ... name='rebuilds') | ||
1649 | 320 | |||
1650 | 321 | >>> dapper = getUtility(IDistributionSet)['ubuntu']['dapper'] | ||
1651 | 322 | >>> queue_item = dapper.getQueueItems(status=PackageUploadStatus.NEW)[0] | ||
1652 | 323 | >>> queue_item.archive = foobar_archive | ||
1653 | 324 | |||
1654 | 325 | >>> queue_item.customfiles[0].publish(mock_logger) | ||
1655 | 326 | DEBUG: Publishing custom pmount, pmount_0.9.7-2ubuntu2_amd64_translations.tar.gz to ubuntu/dapper | ||
1656 | 327 | DEBUG: Skipping translations since its purpose is not in | ||
1657 | 328 | MAIN_ARCHIVE_PURPOSES. | ||
1658 | 302 | 329 | ||
1659 | 303 | # And this time, we see that there are no entries imported in the queue. | 330 | # And this time, we see that there are no entries imported in the queue. |
1660 | 304 | >>> translation_import_queue.getAllEntries(target=ubuntu).count() | 331 | >>> translation_import_queue.getAllEntries(target=ubuntu).count() |
1661 | @@ -314,6 +341,8 @@ | |||
1662 | 314 | >>> from zope.interface import implements | 341 | >>> from zope.interface import implements |
1663 | 315 | >>> from canonical.launchpad.interfaces import ILaunchpadCelebrities | 342 | >>> from canonical.launchpad.interfaces import ILaunchpadCelebrities |
1664 | 316 | >>> from lp.soyuz.model.queue import PackageUploadCustom | 343 | >>> from lp.soyuz.model.queue import PackageUploadCustom |
1665 | 344 | >>> from lp.soyuz.interfaces.archive import ( | ||
1666 | 345 | ... IArchive, ArchivePurpose) | ||
1667 | 317 | >>> from lp.soyuz.interfaces.queue import ( | 346 | >>> from lp.soyuz.interfaces.queue import ( |
1668 | 318 | ... IPackageUpload, IPackageUploadCustom) | 347 | ... IPackageUpload, IPackageUploadCustom) |
1669 | 319 | >>> from lp.soyuz.interfaces.queue import PackageUploadCustomFormat | 348 | >>> from lp.soyuz.interfaces.queue import PackageUploadCustomFormat |
1670 | @@ -323,6 +352,11 @@ | |||
1671 | 323 | ... ISourcePackageRelease) | 352 | ... ISourcePackageRelease) |
1672 | 324 | >>> from lp.registry.interfaces.pocket import PackagePublishingPocket | 353 | >>> from lp.registry.interfaces.pocket import PackagePublishingPocket |
1673 | 325 | 354 | ||
1674 | 355 | >>> class MockArchive: | ||
1675 | 356 | ... implements(IArchive) | ||
1676 | 357 | ... def __init__(self, purpose): | ||
1677 | 358 | ... self.purpose = purpose | ||
1678 | 359 | |||
1679 | 326 | >>> class MockSourcePackageRelease: | 360 | >>> class MockSourcePackageRelease: |
1680 | 327 | ... implements(ISourcePackageRelease) | 361 | ... implements(ISourcePackageRelease) |
1681 | 328 | ... def __init__(self, component, creator): | 362 | ... def __init__(self, component, creator): |
1682 | @@ -338,14 +372,13 @@ | |||
1683 | 338 | 372 | ||
1684 | 339 | >>> class MockPackageUpload: | 373 | >>> class MockPackageUpload: |
1685 | 340 | ... implements(IPackageUpload) | 374 | ... implements(IPackageUpload) |
1687 | 341 | ... def __init__(self, pocket, auto_sync, sourcepackagerelease): | 375 | ... def __init__(self, pocket, auto_sync, sourcepackagerelease, |
1688 | 376 | ... archive): | ||
1689 | 342 | ... self.id = 1 | 377 | ... self.id = 1 |
1690 | 343 | ... self.pocket = pocket | 378 | ... self.pocket = pocket |
1691 | 344 | ... self.auto_sync = auto_sync | 379 | ... self.auto_sync = auto_sync |
1692 | 345 | ... self.sourcepackagerelease = sourcepackagerelease | 380 | ... self.sourcepackagerelease = sourcepackagerelease |
1696 | 346 | ... | 381 | ... self.archive = archive |
1694 | 347 | ... def isPPA(self): | ||
1695 | 348 | ... return False | ||
1697 | 349 | ... | 382 | ... |
1698 | 350 | ... def isAutoSyncUpload(self, changed_by_email=None): | 383 | ... def isAutoSyncUpload(self, changed_by_email=None): |
1699 | 351 | ... return self.auto_sync | 384 | ... return self.auto_sync |
1700 | @@ -363,10 +396,11 @@ | |||
1701 | 363 | 396 | ||
1702 | 364 | >>> katie = getUtility(ILaunchpadCelebrities).katie | 397 | >>> katie = getUtility(ILaunchpadCelebrities).katie |
1703 | 365 | >>> release_pocket = PackagePublishingPocket.RELEASE | 398 | >>> release_pocket = PackagePublishingPocket.RELEASE |
1704 | 399 | >>> archive = MockArchive(ArchivePurpose.PRIMARY) | ||
1705 | 366 | 400 | ||
1706 | 367 | >>> katie_sourcepackagerelease = MockSourcePackageRelease('main', katie) | 401 | >>> katie_sourcepackagerelease = MockSourcePackageRelease('main', katie) |
1707 | 368 | >>> sync_package_upload = MockPackageUpload( | 402 | >>> sync_package_upload = MockPackageUpload( |
1709 | 369 | ... release_pocket, True, katie_sourcepackagerelease) | 403 | ... release_pocket, True, katie_sourcepackagerelease, archive) |
1710 | 370 | >>> sync_package_upload.isAutoSyncUpload() | 404 | >>> sync_package_upload.isAutoSyncUpload() |
1711 | 371 | True | 405 | True |
1712 | 372 | >>> translations_upload = MockPackageUploadCustom() | 406 | >>> translations_upload = MockPackageUploadCustom() |
1713 | @@ -377,7 +411,7 @@ | |||
1714 | 377 | Non-auto-sync uploads by 'katie' still indicate 'katie' as the uploader. | 411 | Non-auto-sync uploads by 'katie' still indicate 'katie' as the uploader. |
1715 | 378 | 412 | ||
1716 | 379 | >>> non_sync_package_upload = MockPackageUpload( | 413 | >>> non_sync_package_upload = MockPackageUpload( |
1718 | 380 | ... release_pocket, False, katie_sourcepackagerelease) | 414 | ... release_pocket, False, katie_sourcepackagerelease, archive) |
1719 | 381 | >>> non_sync_package_upload.isAutoSyncUpload() | 415 | >>> non_sync_package_upload.isAutoSyncUpload() |
1720 | 382 | False | 416 | False |
1721 | 383 | >>> translations_upload.packageupload = non_sync_package_upload | 417 | >>> translations_upload.packageupload = non_sync_package_upload |
1722 | @@ -390,7 +424,7 @@ | |||
1723 | 390 | >>> carlos = person_set.getByName('carlos') | 424 | >>> carlos = person_set.getByName('carlos') |
1724 | 391 | >>> carlos_sourcepackagerelease = MockSourcePackageRelease('main', carlos) | 425 | >>> carlos_sourcepackagerelease = MockSourcePackageRelease('main', carlos) |
1725 | 392 | >>> carlos_package_upload = MockPackageUpload( | 426 | >>> carlos_package_upload = MockPackageUpload( |
1727 | 393 | ... release_pocket, False, carlos_sourcepackagerelease) | 427 | ... release_pocket, False, carlos_sourcepackagerelease, archive) |
1728 | 394 | >>> carlos_package_upload.isAutoSyncUpload() | 428 | >>> carlos_package_upload.isAutoSyncUpload() |
1729 | 395 | False | 429 | False |
1730 | 396 | >>> translations_upload.packageupload = carlos_package_upload | 430 | >>> translations_upload.packageupload = carlos_package_upload |
1731 | 397 | 431 | ||
1732 | === modified file 'lib/lp/soyuz/doc/gina.txt' | |||
1733 | === modified file 'lib/lp/soyuz/model/archive.py' | |||
1734 | --- lib/lp/soyuz/model/archive.py 2010-04-12 08:29:02 +0000 | |||
1735 | +++ lib/lp/soyuz/model/archive.py 2010-04-16 04:01:13 +0000 | |||
1736 | @@ -164,7 +164,7 @@ | |||
1737 | 164 | dbName='require_virtualized', notNull=True, default=True) | 164 | dbName='require_virtualized', notNull=True, default=True) |
1738 | 165 | 165 | ||
1739 | 166 | authorized_size = IntCol( | 166 | authorized_size = IntCol( |
1741 | 167 | dbName='authorized_size', notNull=False, default=1024) | 167 | dbName='authorized_size', notNull=False, default=2048) |
1742 | 168 | 168 | ||
1743 | 169 | sources_cached = IntCol( | 169 | sources_cached = IntCol( |
1744 | 170 | dbName='sources_cached', notNull=False, default=0) | 170 | dbName='sources_cached', notNull=False, default=0) |
1745 | 171 | 171 | ||
1746 | === modified file 'lib/lp/soyuz/model/queue.py' | |||
1747 | --- lib/lp/soyuz/model/queue.py 2010-04-12 11:37:48 +0000 | |||
1748 | +++ lib/lp/soyuz/model/queue.py 2010-04-16 04:01:13 +0000 | |||
1749 | @@ -43,6 +43,7 @@ | |||
1750 | 43 | from canonical.database.sqlbase import SQLBase, sqlvalues | 43 | from canonical.database.sqlbase import SQLBase, sqlvalues |
1751 | 44 | from canonical.encoding import guess as guess_encoding, ascii_smash | 44 | from canonical.encoding import guess as guess_encoding, ascii_smash |
1752 | 45 | from canonical.launchpad.helpers import get_email_template | 45 | from canonical.launchpad.helpers import get_email_template |
1753 | 46 | from lp.soyuz.interfaces.archive import MAIN_ARCHIVE_PURPOSES | ||
1754 | 46 | from lp.soyuz.interfaces.binarypackagerelease import ( | 47 | from lp.soyuz.interfaces.binarypackagerelease import ( |
1755 | 47 | BinaryPackageFormat) | 48 | BinaryPackageFormat) |
1756 | 48 | from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities | 49 | from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities |
1757 | @@ -1701,9 +1702,11 @@ | |||
1758 | 1701 | """See `IPackageUploadCustom`.""" | 1702 | """See `IPackageUploadCustom`.""" |
1759 | 1702 | sourcepackagerelease = self.packageupload.sourcepackagerelease | 1703 | sourcepackagerelease = self.packageupload.sourcepackagerelease |
1760 | 1703 | 1704 | ||
1764 | 1704 | # Ignore translation coming from PPA. | 1705 | # Ignore translations not with main distribution purposes. |
1765 | 1705 | if self.packageupload.isPPA(): | 1706 | if self.packageupload.archive.purpose not in MAIN_ARCHIVE_PURPOSES: |
1766 | 1706 | debug(logger, "Skipping translations since it is a PPA.") | 1707 | debug(logger, |
1767 | 1708 | "Skipping translations since its purpose is not " | ||
1768 | 1709 | "in MAIN_ARCHIVE_PURPOSES.") | ||
1769 | 1707 | return | 1710 | return |
1770 | 1708 | 1711 | ||
1771 | 1709 | valid_pockets = ( | 1712 | valid_pockets = ( |
1772 | 1710 | 1713 | ||
1773 | === modified file 'lib/lp/testing/factory.py' | |||
1774 | === modified file 'lib/lp/translations/templates/language-index.pt' | |||
1775 | --- lib/lp/translations/templates/language-index.pt 2009-11-27 14:18:05 +0000 | |||
1776 | +++ lib/lp/translations/templates/language-index.pt 2010-04-16 04:01:13 +0000 | |||
1777 | @@ -66,36 +66,17 @@ | |||
1778 | 66 | being experts in | 66 | being experts in |
1779 | 67 | <tal:language replace="view/language_name"> | 67 | <tal:language replace="view/language_name"> |
1780 | 68 | Español | 68 | Español |
1783 | 69 | </tal:language> | 69 | </tal:language>: |
1782 | 70 | : | ||
1784 | 71 | </p> | 70 | </p> |
1812 | 72 | <table> | 71 | <div tal:repeat="expert_info view/translation_teams"> |
1813 | 73 | <tr tal:repeat="expert_info view/translation_teams"> | 72 | <a tal:replace="structure expert_info/expert/fmt:link">Person</a> |
1814 | 74 | <td> | 73 | (<tal:groups repeat="group expert_info/groups" |
1815 | 75 | <img tal:condition="expert_info/expert/isTeam" | 74 | ><a tal:replace="structure group/fmt:link" |
1816 | 76 | src="/@@/team" /> | 75 | >Translation group title</a |
1817 | 77 | <img tal:condition="not:expert_info/expert/isTeam" | 76 | ><tal:comma |
1818 | 78 | src="/@@/person" /> | 77 | condition="not:repeat/group/end">, </tal:comma |
1819 | 79 | </td> | 78 | ></tal:groups>) |
1820 | 80 | <td> | 79 | </div> |
1794 | 81 | <div> | ||
1795 | 82 | <a | ||
1796 | 83 | tal:attributes=" | ||
1797 | 84 | href expert_info/expert/fmt:url; | ||
1798 | 85 | title expert_info/expert/displayname/fmt:shorten/80" | ||
1799 | 86 | tal:content="expert_info/expert/displayname" | ||
1800 | 87 | >Expert name</a>( | ||
1801 | 88 | <tal:groups repeat="group expert_info/groups"> | ||
1802 | 89 | <a tal:attributes=" | ||
1803 | 90 | href group/fmt:url; | ||
1804 | 91 | title group/title/fmt:shorten/80" | ||
1805 | 92 | tal:content="group/title" | ||
1806 | 93 | >Translation group title</a> | ||
1807 | 94 | </tal:groups>) | ||
1808 | 95 | </div> | ||
1809 | 96 | </td> | ||
1810 | 97 | </tr> | ||
1811 | 98 | </table> | ||
1821 | 99 | <p tal:condition="not:view/translation_teams"> | 80 | <p tal:condition="not:view/translation_teams"> |
1822 | 100 | <tal:language replace="view/language_name"> | 81 | <tal:language replace="view/language_name"> |
1823 | 101 | Español | 82 | Español |