Merge lp:~rockstar/launchpad/branch-index-redesign into lp:launchpad
- branch-index-redesign
- Merge into devel
Status: | Merged |
---|---|
Approved by: | Aaron Bentley |
Approved revision: | no longer in the source branch. |
Merged at revision: | not available |
Proposed branch: | lp:~rockstar/launchpad/branch-index-redesign |
Merge into: | lp:launchpad |
Diff against target: | None lines |
To merge this branch: | bzr merge lp:~rockstar/launchpad/branch-index-redesign |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Edwin Grubbs (community) | ui | Approve | |
Barry Warsaw (community) | ui* | Approve | |
Aaron Bentley (community) | Approve | ||
Review via email: mp+12061@code.launchpad.net |
Commit message
Description of the change
Paul Hummer (rockstar) wrote : | # |
Aaron Bentley (abentley) wrote : | # |
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
status approved
Paul Hummer wrote:
> This branch is ...the re-design of the branch index
> page.
(The review diff is wildly inaccurate.)
As discussed on IRC, please make the following changes:
Add enabled_
Please remove the lolspeak comment from lib/lp/
Please stop hiding merges into import branches.
Please remove the outer div of the nested tal:condition from the top of
branch-
Aside from that, this is good to land.
Aaron
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://
iEYEARECAAYFAkq
XZUAn1yJBLbVkHv
=jkPY
-----END PGP SIGNATURE-----
Paul Hummer (rockstar) wrote : | # |
Snapshots of the hosted and import branches are here:
https:/
https:/
Barry Warsaw (barry) wrote : | # |
As we discussed on IRC, the wrapping of the H1 heading is ugly and should be fixed, but is outside the scope of this branch. Likely the extras slot is in the wrong place. You agreed to file a bug on that issue before you land this branch so that we can address that post-3.0.
Other than that, it looks great!
Edwin Grubbs (edwin-grubbs) wrote : | # |
Hi Paul,
This looks nice. I only have two small changes that I would like.
1. Move the public/private info to the top of the sidebar, so its location is more similar to bugs and teams.
2. For private branches, add a second line to the public/private box to explain what the privacy means. For example, private teams now say "Viewable by team members", and I assume the branch subscriber and owner can view a private branch.
-Edwin
Jonathan Lange (jml) wrote : | # |
On Sat, Sep 19, 2009 at 12:50 AM, Paul Hummer <email address hidden> wrote:
> Paul Hummer has proposed merging lp:~rockstar/launchpad/branch-index-redesign into lp:launchpad/devel.
>
> Requested reviews:
> Aaron Bentley (abentley)
>
> Hi Aaron-
>
> First of all, I apologize for the size of this branch. I couldn't see a sane
> way of splitting it up.
>
> This branch is a long time coming. It's the re-design of the branch index
> page. I'm 99% sure I got all the failing tests and fixed them (it's gone
> through ec2 at least 5 times). While working on that though, I found that our
> tests are far too dependent on the html layout of pages, instead of the actual
> content. I plan to fix this soon by moving a lot of what we're testing out
> into unittests (and kill the page tests).
>
> There are some flakes errors being raised about not being able to import lazr
> packages, but I think flakes is just stupid.
>
> reviewer abentley
I've got a few questions that I can't figure out from the screenshots.
- If it's a mirrored branch, and the mirror has failed, then what
does the page look like?
- Where does the "Branch content" link go to? I hope it's to file
browsing on loggerhead
- I think it's a negative that there's absolutely no information
about the last revision on this page.
- There should also be a link to the series of the branch, if the
branch is linked to a series.
- How do I delete a branch?
- How do I change the status?
My flight's leaving now, but I'd really like to talk about this
further. Sorry for being so terse.
jml
Preview Diff
1 | === modified file '.bzrignore' |
2 | --- .bzrignore 2009-08-05 23:08:37 +0000 |
3 | +++ .bzrignore 2009-09-10 06:11:21 +0000 |
4 | @@ -50,3 +50,4 @@ |
5 | ./_pythonpath.py |
6 | ./production-configs |
7 | bzr.dev |
8 | +_trial_temp |
9 | |
10 | === modified file 'Makefile' |
11 | --- Makefile 2009-08-21 17:50:58 +0000 |
12 | +++ Makefile 2009-09-17 12:56:07 +0000 |
13 | @@ -90,6 +90,15 @@ |
14 | @echo |
15 | @echo "Running the JavaScript integration test suite" |
16 | @echo |
17 | + bin/test $(VERBOSITY) --layer=BugsWindmillLayer |
18 | + bin/test $(VERBOSITY) --layer=CodeWindmillLayer |
19 | + |
20 | +jscheck_functest: build |
21 | + # Run the old functest Windmill integration tests. The test runner |
22 | + # takes care of setting up the test environment. |
23 | + @echo |
24 | + @echo "Running Windmill funtest integration test suite" |
25 | + @echo |
26 | bin/jstest |
27 | |
28 | check_mailman: build |
29 | |
30 | === modified file 'buildmailman.py' |
31 | --- buildmailman.py 2009-07-17 00:26:05 +0000 |
32 | +++ buildmailman.py 2009-09-11 02:17:29 +0000 |
33 | @@ -41,6 +41,14 @@ |
34 | else: |
35 | return 0 |
36 | |
37 | + # sys.path_importer_cache is a mapping of elements of sys.path to importer |
38 | + # objects used to handle them. In Python2.5+ when an element of sys.path is |
39 | + # found to not exist on disk, a NullImporter is created and cached - this |
40 | + # causes Python to never bother re-inspecting the disk for that path |
41 | + # element. We must clear that cache element so that our second attempt to |
42 | + # import MailMan after building it will actually check the disk. |
43 | + del sys.path_importer_cache[mailman_path] |
44 | + |
45 | # Make sure the target directories exist and have the correct |
46 | # permissions, otherwise configure will complain. |
47 | user, group = as_username_groupname(config.mailman.build_user_group) |
48 | |
49 | === modified file 'buildout-templates/bin/test.in' |
50 | --- buildout-templates/bin/test.in 2009-08-10 22:08:05 +0000 |
51 | +++ buildout-templates/bin/test.in 2009-09-17 17:42:25 +0000 |
52 | @@ -134,14 +134,13 @@ |
53 | from zope.testing import testrunner |
54 | from zope.testing.testrunner import options |
55 | |
56 | -defaults = [ |
57 | +defaults = { |
58 | # Find tests in the tests and ftests directories |
59 | - '--tests-pattern=^f?tests$', |
60 | - '--test-path=${buildout:directory}/lib', |
61 | - '--package=canonical', |
62 | - '--package=lp', |
63 | - '--layer=!MailmanLayer', |
64 | - ] |
65 | + 'tests_pattern': '^f?tests$', |
66 | + 'test_path': ['${buildout:directory}/lib'], |
67 | + 'package': ['canonical', 'lp', 'devscripts'], |
68 | + 'layer': ['!(MailmanLayer|WindmillLayer)'], |
69 | + } |
70 | |
71 | # Monkey-patch os.listdir to randomise the results |
72 | original_listdir = os.listdir |
73 | @@ -202,7 +201,22 @@ |
74 | options.parser.add_option( |
75 | '--subunit', action='callback', callback=use_subunit) |
76 | |
77 | - local_options = options.get_options(args=args, defaults=defaults) |
78 | + # tests_pattern is a regexp, so the parsed value is hard to compare |
79 | + # with the default value in the loop below. |
80 | + options.parser.defaults['tests_pattern'] = defaults['tests_pattern'] |
81 | + local_options = options.get_options(args=args) |
82 | + # Set our default options, if the options aren't specified. |
83 | + for name, value in defaults.items(): |
84 | + parsed_option = getattr(local_options, name) |
85 | + if ((parsed_option == []) or |
86 | + (parsed_option == options.parser.defaults.get(name))): |
87 | + # The option probably wasn't specified on the command line, |
88 | + # let's replace it with our default value. It could be that |
89 | + # the real default (as specified in |
90 | + # zope.testing.testrunner.options) was specified, and we |
91 | + # shouldn't replace it with our default, but it's such and |
92 | + # edge case, so we don't have to care about it. |
93 | + options.parser.defaults[name] = value |
94 | |
95 | # Turn on Layer profiling if requested. |
96 | from canonical.testing import profiled |
97 | @@ -216,7 +230,7 @@ |
98 | try: |
99 | there = os.getcwd() |
100 | os.chdir('${buildout:directory}') |
101 | - result = testrunner.run(defaults) |
102 | + result = testrunner.run([]) |
103 | finally: |
104 | os.chdir(there) |
105 | # Cribbed from sourcecode/zope/test.py - avoid spurious error during exit. |
106 | |
107 | === modified file 'configs/development/launchpad-lazr.conf' |
108 | --- configs/development/launchpad-lazr.conf 2009-08-19 12:28:32 +0000 |
109 | +++ configs/development/launchpad-lazr.conf 2009-09-14 19:32:10 +0000 |
110 | @@ -145,6 +145,7 @@ |
111 | restricted_upload_port: 58095 |
112 | restricted_download_port: 58085 |
113 | restricted_download_url: http://launchpad.dev:58085/ |
114 | +use_https = False |
115 | |
116 | [librarian_server] |
117 | root: /var/tmp/fatsam |
118 | |
119 | === modified file 'configs/development/launchpad.conf' |
120 | --- configs/development/launchpad.conf 2009-06-12 16:36:02 +0000 |
121 | +++ configs/development/launchpad.conf 2009-09-11 18:14:50 +0000 |
122 | @@ -66,7 +66,7 @@ |
123 | </eventlog> |
124 | |
125 | <logger> |
126 | - name zc.zservertracelog |
127 | + name zc.tracelog |
128 | propagate false |
129 | |
130 | <logfile> |
131 | |
132 | === modified file 'configs/test-playground/launchpad.conf' |
133 | --- configs/test-playground/launchpad.conf 2008-11-10 16:12:10 +0000 |
134 | +++ configs/test-playground/launchpad.conf 2009-09-11 18:14:50 +0000 |
135 | @@ -66,7 +66,7 @@ |
136 | </eventlog> |
137 | |
138 | <logger> |
139 | - name zc.zservertracelog |
140 | + name zc.tracelog |
141 | propagate false |
142 | |
143 | <logfile> |
144 | |
145 | === modified file 'configs/testrunner-appserver/launchpad.conf' |
146 | --- configs/testrunner-appserver/launchpad.conf 2009-05-12 21:22:02 +0000 |
147 | +++ configs/testrunner-appserver/launchpad.conf 2009-09-11 18:14:50 +0000 |
148 | @@ -39,7 +39,7 @@ |
149 | </eventlog> |
150 | |
151 | <logger> |
152 | - name zc.zservertracelog |
153 | + name zc.tracelog |
154 | propagate false |
155 | |
156 | <logfile> |
157 | |
158 | === modified file 'configs/testrunner/launchpad-lazr.conf' |
159 | --- configs/testrunner/launchpad-lazr.conf 2009-08-15 03:43:17 +0000 |
160 | +++ configs/testrunner/launchpad-lazr.conf 2009-08-28 21:02:50 +0000 |
161 | @@ -159,6 +159,9 @@ |
162 | oops_prefix: TMPCJ |
163 | error_dir: /var/tmp/codehosting.test |
164 | |
165 | +[update_preview_diffs] |
166 | +oops_prefix: TUPD |
167 | +error_dir: /var/tmp/codehosting.test |
168 | |
169 | [personalpackagearchive] |
170 | root: /var/tmp/ppa.test/ |
171 | |
172 | === modified file 'cronscripts/create_merge_proposals.py' |
173 | --- cronscripts/create_merge_proposals.py 2009-06-24 20:52:01 +0000 |
174 | +++ cronscripts/create_merge_proposals.py 2009-09-03 19:46:42 +0000 |
175 | @@ -26,7 +26,7 @@ |
176 | def main(self): |
177 | globalErrorUtility.configure('create_merge_proposals') |
178 | job_source = getUtility(ICreateMergeProposalJobSource) |
179 | - runner = JobRunner.fromReady(job_source) |
180 | + runner = JobRunner.fromReady(job_source, self.logger) |
181 | runner.runAll() |
182 | self.logger.info( |
183 | 'Ran %d CreateMergeProposalJobs.' % len(runner.completed_jobs)) |
184 | |
185 | === modified file 'cronscripts/mpcreationjobs.py' |
186 | --- cronscripts/mpcreationjobs.py 2009-06-24 20:52:01 +0000 |
187 | +++ cronscripts/mpcreationjobs.py 2009-09-03 19:04:28 +0000 |
188 | @@ -31,7 +31,7 @@ |
189 | def main(self): |
190 | globalErrorUtility.configure('mpcreationjobs') |
191 | job_source = getUtility(IMergeProposalCreatedJobSource) |
192 | - runner = JobRunner.fromReady(job_source) |
193 | + runner = JobRunner.fromReady(job_source, self.logger) |
194 | server = get_scanner_server() |
195 | server.setUp() |
196 | try: |
197 | |
198 | === modified file 'cronscripts/parse-librarian-apache-access-logs.py' |
199 | --- cronscripts/parse-librarian-apache-access-logs.py 2009-08-31 15:01:54 +0000 |
200 | +++ cronscripts/parse-librarian-apache-access-logs.py 2009-09-11 12:11:04 +0000 |
201 | @@ -16,8 +16,6 @@ |
202 | |
203 | __metaclass__ = type |
204 | |
205 | -import os |
206 | - |
207 | # pylint: disable-msg=W0403 |
208 | import _pythonpath |
209 | |
210 | @@ -26,55 +24,36 @@ |
211 | from storm.sqlobject import SQLObjectNotFound |
212 | |
213 | from canonical.config import config |
214 | -from lp.services.worlddata.interfaces.country import ICountrySet |
215 | from canonical.launchpad.interfaces.librarian import ILibraryFileAliasSet |
216 | -from lp.services.scripts.base import LaunchpadCronScript |
217 | -from lp.services.apachelogparser.base import ( |
218 | - create_or_update_parsedlog_entry, get_files_to_parse, parse_file) |
219 | from canonical.launchpad.scripts.librarian_apache_log_parser import ( |
220 | DBUSER, get_library_file_id) |
221 | -from canonical.launchpad.webapp.interfaces import NotFoundError |
222 | - |
223 | - |
224 | -class ParseLibrarianApacheLogs(LaunchpadCronScript): |
225 | - |
226 | - def main(self): |
227 | - root = config.librarianlogparser.logs_root |
228 | - files_to_parse = get_files_to_parse(root, os.listdir(root)) |
229 | - |
230 | - libraryfilealias_set = getUtility(ILibraryFileAliasSet) |
231 | - country_set = getUtility(ICountrySet) |
232 | - for fd, position in files_to_parse.items(): |
233 | - downloads, parsed_bytes = parse_file( |
234 | - fd, position, self.logger, get_library_file_id) |
235 | - # Use a while loop here because we want to pop items from the dict |
236 | - # in order to free some memory as we go along. This is a good |
237 | - # thing here because the downloads dict may get really huge. |
238 | - while downloads: |
239 | - file_id, daily_downloads = downloads.popitem() |
240 | - try: |
241 | - lfa = libraryfilealias_set[file_id] |
242 | - except SQLObjectNotFound: |
243 | - # This file has been deleted from the librarian, so don't |
244 | - # try to store download counters for it. |
245 | - continue |
246 | - for day, country_downloads in daily_downloads.items(): |
247 | - for country_code, count in country_downloads.items(): |
248 | - try: |
249 | - country = country_set[country_code] |
250 | - except NotFoundError: |
251 | - # We don't know the country for the IP address |
252 | - # where this request originated. |
253 | - country = None |
254 | - lfa.updateDownloadCount(day, country, count) |
255 | - fd.seek(0) |
256 | - first_line = fd.readline() |
257 | - fd.close() |
258 | - create_or_update_parsedlog_entry(first_line, parsed_bytes) |
259 | - self.txn.commit() |
260 | - self.logger.info('Finished parsing %s' % fd) |
261 | - |
262 | - self.logger.info('Done parsing apache log files for librarian') |
263 | +from lp.services.apachelogparser.script import ParseApacheLogs |
264 | + |
265 | + |
266 | +class ParseLibrarianApacheLogs(ParseApacheLogs): |
267 | + """An Apache log parser for LibraryFileAlias downloads.""" |
268 | + |
269 | + def setUpUtilities(self): |
270 | + """See `ParseApacheLogs`.""" |
271 | + self.libraryfilealias_set = getUtility(ILibraryFileAliasSet) |
272 | + |
273 | + @property |
274 | + def root(self): |
275 | + """See `ParseApacheLogs`.""" |
276 | + return config.librarianlogparser.logs_root |
277 | + |
278 | + def getDownloadKey(self, path): |
279 | + """See `ParseApacheLogs`.""" |
280 | + return get_library_file_id(path) |
281 | + |
282 | + def getDownloadCountUpdater(self, file_id): |
283 | + """See `ParseApacheLogs`.""" |
284 | + try: |
285 | + return self.libraryfilealias_set[file_id].updateDownloadCount |
286 | + except SQLObjectNotFound: |
287 | + # This file has been deleted from the librarian, so don't |
288 | + # try to store download counters for it. |
289 | + return None |
290 | |
291 | |
292 | if __name__ == '__main__': |
293 | |
294 | === modified file 'cronscripts/reclaimbranchspace.py' |
295 | --- cronscripts/reclaimbranchspace.py 2009-07-17 02:25:09 +0000 |
296 | +++ cronscripts/reclaimbranchspace.py 2009-09-03 20:06:45 +0000 |
297 | @@ -26,7 +26,7 @@ |
298 | def main(self): |
299 | globalErrorUtility.configure('reclaimbranchspace') |
300 | job_source = getUtility(IReclaimBranchSpaceJobSource) |
301 | - runner = JobRunner.fromReady(job_source) |
302 | + runner = JobRunner.fromReady(job_source, self.logger) |
303 | runner.runAll() |
304 | self.logger.info( |
305 | 'Reclaimed space for %s branches.', len(runner.completed_jobs)) |
306 | |
307 | === modified file 'cronscripts/rosetta-branches.py' |
308 | --- cronscripts/rosetta-branches.py 2009-06-24 20:52:01 +0000 |
309 | +++ cronscripts/rosetta-branches.py 2009-09-03 20:29:25 +0000 |
310 | @@ -29,7 +29,8 @@ |
311 | |
312 | def main(self): |
313 | globalErrorUtility.configure('rosettabranches') |
314 | - runner = JobRunner.fromReady(getUtility(IRosettaUploadJobSource)) |
315 | + runner = JobRunner.fromReady( |
316 | + getUtility(IRosettaUploadJobSource), self.logger) |
317 | server = get_scanner_server() |
318 | server.setUp() |
319 | try: |
320 | |
321 | === modified file 'cronscripts/sendbranchmail.py' |
322 | --- cronscripts/sendbranchmail.py 2009-06-24 20:52:01 +0000 |
323 | +++ cronscripts/sendbranchmail.py 2009-09-03 19:25:57 +0000 |
324 | @@ -31,7 +31,7 @@ |
325 | globalErrorUtility.configure('sendbranchmail') |
326 | jobs = list(getUtility(IRevisionMailJobSource).iterReady()) |
327 | jobs.extend(getUtility(IRevisionsAddedJobSource).iterReady()) |
328 | - runner = JobRunner(jobs) |
329 | + runner = JobRunner(jobs, self.logger) |
330 | server = get_scanner_server() |
331 | server.setUp() |
332 | try: |
333 | |
334 | === added file 'cronscripts/update_preview_diffs.py' |
335 | --- cronscripts/update_preview_diffs.py 1970-01-01 00:00:00 +0000 |
336 | +++ cronscripts/update_preview_diffs.py 2009-09-01 19:00:46 +0000 |
337 | @@ -0,0 +1,34 @@ |
338 | +#!/usr/bin/python2.4 |
339 | +# |
340 | +# Copyright 2009 Canonical Ltd. This software is licensed under the |
341 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
342 | + |
343 | +# pylint: disable-msg=W0403 |
344 | + |
345 | +"""Update or create previews diffs for branch merge proposals.""" |
346 | + |
347 | +__metaclass__ = type |
348 | + |
349 | +import _pythonpath |
350 | + |
351 | +from lp.codehosting.vfs import get_scanner_server |
352 | +from lp.services.job.runner import JobCronScript |
353 | +from lp.code.interfaces.branchmergeproposal import ( |
354 | + IUpdatePreviewDiffJobSource,) |
355 | + |
356 | + |
357 | +class RunUpdatePreviewDiffJobs(JobCronScript): |
358 | + """Run UpdatePreviewDiff jobs.""" |
359 | + |
360 | + config_name = 'update_preview_diffs' |
361 | + source_interface = IUpdatePreviewDiffJobSource |
362 | + |
363 | + def setUp(self): |
364 | + server = get_scanner_server() |
365 | + server.setUp() |
366 | + return [server.tearDown] |
367 | + |
368 | + |
369 | +if __name__ == '__main__': |
370 | + script = RunUpdatePreviewDiffJobs() |
371 | + script.lock_and_run() |
372 | |
373 | === modified file 'database/schema/security.cfg' |
374 | --- database/schema/security.cfg 2009-08-29 19:37:54 +0000 |
375 | +++ database/schema/security.cfg 2009-09-02 19:06:19 +0000 |
376 | @@ -579,6 +579,7 @@ |
377 | public.branch = SELECT, UPDATE |
378 | public.branchjob = SELECT, INSERT, UPDATE, DELETE |
379 | public.branchmergeproposal = SELECT, UPDATE |
380 | +public.branchmergeproposaljob = SELECT, INSERT |
381 | public.branchrevision = SELECT, INSERT, UPDATE, DELETE |
382 | public.branchsubscription = SELECT |
383 | public.branchvisibilitypolicy = SELECT |
384 | @@ -1590,6 +1591,18 @@ |
385 | public.teamparticipation = SELECT |
386 | public.validpersoncache = SELECT |
387 | |
388 | +[update-preview-diffs] |
389 | +type=user |
390 | +groups=script |
391 | +public.branch = SELECT |
392 | +public.branchmergeproposal = SELECT, UPDATE |
393 | +public.branchmergeproposaljob = SELECT |
394 | +public.diff = SELECT, INSERT |
395 | +public.job = SELECT, UPDATE |
396 | +public.libraryfilealias = SELECT, INSERT |
397 | +public.libraryfilecontent = SELECT, INSERT |
398 | +public.previewdiff = SELECT, INSERT |
399 | + |
400 | [send-branch-mail] |
401 | type=user |
402 | groups=script |
403 | |
404 | === modified file 'lib/canonical/config/schema-lazr.conf' |
405 | --- lib/canonical/config/schema-lazr.conf 2009-08-29 19:37:54 +0000 |
406 | +++ lib/canonical/config/schema-lazr.conf 2009-09-14 19:32:10 +0000 |
407 | @@ -1144,6 +1144,8 @@ |
408 | # datatype: urlbase |
409 | restricted_download_url: http://restricted-librarian.launchpad.net/ |
410 | |
411 | +use_https = True |
412 | + |
413 | [librarian_gc] |
414 | # The database user which will be used by this process. |
415 | # datatype: string |
416 | @@ -1346,6 +1348,14 @@ |
417 | comments_list_max_length: 100 |
418 | comments_list_truncate_to: 80 |
419 | |
420 | +# Should +filebug be disabled for Ubuntu ? |
421 | +ubuntu_disable_filebug: false |
422 | + |
423 | +# Redirect to this URL when users try to file a bug on Ubuntu without |
424 | +# using apport. |
425 | +ubuntu_bug_filing_url: https://help.ubuntu.com/community/ReportingBugs |
426 | + |
427 | + |
428 | [mpcreationjobs] |
429 | # The database user which will be used by this process. |
430 | # datatype: string |
431 | @@ -1362,6 +1372,18 @@ |
432 | # See [error_reports]. |
433 | copy_to_zlog: false |
434 | |
435 | +[update_preview_diffs] |
436 | +dbuser: update-preview-diffs |
437 | + |
438 | +# See [error_reports]. |
439 | +error_dir: none |
440 | + |
441 | +# See [error_reports]. |
442 | +oops_prefix: none |
443 | + |
444 | +# See [error_reports]. |
445 | +copy_to_zlog: false |
446 | + |
447 | [person_notification] |
448 | # User for person notification db access |
449 | # datatype: string |
450 | @@ -1409,6 +1431,10 @@ |
451 | storm_cache: generational |
452 | storm_cache_size: 500 |
453 | |
454 | +# Statement timeout (in seconds), limited to super-fast-imports query. |
455 | +# Set to 'timeout' to make it timeout every time (for tests). |
456 | +statement_timeout: 300 |
457 | + |
458 | [processmail] |
459 | # The database user which will be used by this process. |
460 | # datatype: string |
461 | |
462 | === modified file 'lib/canonical/launchpad/browser/launchpad.py' |
463 | --- lib/canonical/launchpad/browser/launchpad.py 2009-08-28 06:38:41 +0000 |
464 | +++ lib/canonical/launchpad/browser/launchpad.py 2009-09-16 19:56:45 +0000 |
465 | @@ -9,12 +9,11 @@ |
466 | 'ApplicationButtons', |
467 | 'BrowserWindowDimensions', |
468 | 'DoesNotExistView', |
469 | - 'get_launchpad_views', |
470 | 'Hierarchy', |
471 | 'IcingContribFolder', |
472 | 'IcingFolder', |
473 | + 'LaunchpadImageFolder', |
474 | 'LaunchpadRootNavigation', |
475 | - 'LaunchpadImageFolder', |
476 | 'LinkView', |
477 | 'LoginStatus', |
478 | 'MaintenanceMessage', |
479 | @@ -23,6 +22,7 @@ |
480 | 'SoftTimeoutView', |
481 | 'StructuralHeaderPresentation', |
482 | 'StructuralObjectPresentation', |
483 | + 'get_launchpad_views', |
484 | ] |
485 | |
486 | |
487 | @@ -34,6 +34,7 @@ |
488 | import urllib |
489 | from datetime import timedelta, datetime |
490 | |
491 | +from zope.app import zapi |
492 | from zope.datetime import parseDatetimetz, tzinfo, DateTimeError |
493 | from zope.component import getUtility, queryAdapter |
494 | from zope.interface import implements |
495 | @@ -95,6 +96,7 @@ |
496 | LaunchpadFormView, LaunchpadView, Link, Navigation, |
497 | StandardLaunchpadFacets, canonical_name, canonical_url, custom_widget, |
498 | stepto) |
499 | +from canonical.launchpad.webapp.breadcrumb import Breadcrumb |
500 | from canonical.launchpad.webapp.interfaces import ( |
501 | IBreadcrumb, ILaunchBag, ILaunchpadRoot, INavigationMenu, |
502 | NotFoundError, POSTToNonCanonicalURL) |
503 | @@ -233,25 +235,48 @@ |
504 | breadcrumbs.append(breadcrumb) |
505 | |
506 | host = URI(self.request.getURL()).host |
507 | - if (len(breadcrumbs) == 0 |
508 | - or host == allvhosts.configs['mainsite'].hostname): |
509 | - return breadcrumbs |
510 | - |
511 | - # If we got this far it means we have breadcrumbs and we're not on the |
512 | - # mainsite, so we'll sneak an extra breadcrumb for the vhost we're on. |
513 | - vhost = host.split('.')[0] |
514 | - |
515 | - # Iterate over the context of our breadcrumbs in reverse order and for |
516 | - # the first one we find an adapter named after the vhost we're on, |
517 | - # generate an extra breadcrumb and insert it in our list. |
518 | - for idx, breadcrumb in reversed(list(enumerate(breadcrumbs))): |
519 | - extra_breadcrumb = queryAdapter( |
520 | - breadcrumb.context, IBreadcrumb, name=vhost) |
521 | - if extra_breadcrumb is not None: |
522 | - breadcrumbs.insert(idx + 1, extra_breadcrumb) |
523 | - break |
524 | + mainhost = allvhosts.configs['mainsite'].hostname |
525 | + if len(breadcrumbs) != 0 and host != mainhost: |
526 | + # We have breadcrumbs and we're not on the mainsite, so we'll |
527 | + # sneak an extra breadcrumb for the vhost we're on. |
528 | + vhost = host.split('.')[0] |
529 | + |
530 | + # Iterate over the context of our breadcrumbs in reverse order and |
531 | + # for the first one we find an adapter named after the vhost we're |
532 | + # on, generate an extra breadcrumb and insert it in our list. |
533 | + for idx, breadcrumb in reversed(list(enumerate(breadcrumbs))): |
534 | + extra_breadcrumb = queryAdapter( |
535 | + breadcrumb.context, IBreadcrumb, name=vhost) |
536 | + if extra_breadcrumb is not None: |
537 | + breadcrumbs.insert(idx + 1, extra_breadcrumb) |
538 | + break |
539 | + if len(breadcrumbs) > 0: |
540 | + page_crumb = self.makeBreadcrumbForRequestedPage() |
541 | + if page_crumb: |
542 | + breadcrumbs.append(page_crumb) |
543 | return breadcrumbs |
544 | |
545 | + def makeBreadcrumbForRequestedPage(self): |
546 | + """Return an `IBreadcrumb` for the requested page. |
547 | + |
548 | + The `IBreadcrumb` for the requested page is created using the current |
549 | + URL and the page's name (i.e. the last path segment of the URL). |
550 | + |
551 | + If the requested page (as specified in self.request) is the default |
552 | + one for the last traversed object, return None. |
553 | + """ |
554 | + url = self.request.getURL() |
555 | + last_segment = URI(url).path.split('/')[-1] |
556 | + default_view_name = zapi.getDefaultViewName( |
557 | + self.request.traversed_objects[-1], self.request) |
558 | + if last_segment.startswith('+') and last_segment != default_view_name: |
559 | + breadcrumb = Breadcrumb(None) |
560 | + breadcrumb._url = url |
561 | + breadcrumb.text = last_segment |
562 | + return breadcrumb |
563 | + else: |
564 | + return None |
565 | + |
566 | @property |
567 | def display_breadcrumbs(self): |
568 | """Return whether the breadcrumbs should be displayed.""" |
569 | @@ -259,6 +284,7 @@ |
570 | # to display it as it will simply repeat the context.title. |
571 | return len(self.items) > 1 |
572 | |
573 | + |
574 | class MaintenanceMessage: |
575 | """Display a maintenance message if the control file is present and |
576 | it contains a valid iso format time. |
577 | |
578 | === modified file 'lib/canonical/launchpad/browser/logintoken.py' |
579 | --- lib/canonical/launchpad/browser/logintoken.py 2009-07-20 15:27:26 +0000 |
580 | +++ lib/canonical/launchpad/browser/logintoken.py 2009-09-18 01:09:10 +0000 |
581 | @@ -93,6 +93,8 @@ |
582 | } |
583 | login_token_pages.update(auth_token_pages) |
584 | PAGES = login_token_pages |
585 | + page_title = 'You have already done this' |
586 | + label = 'Confirmation already concluded' |
587 | |
588 | def render(self): |
589 | if self.context.date_consumed is None: |
590 | @@ -109,6 +111,11 @@ |
591 | expected_token_types = () |
592 | successfullyProcessed = False |
593 | |
594 | + @property |
595 | + def page_title(self): |
596 | + """The page title.""" |
597 | + return self.label |
598 | + |
599 | def redirectIfInvalidOrConsumedToken(self): |
600 | """If this is a consumed or invalid token redirect to the LoginToken |
601 | default view and return True. |
602 | @@ -354,6 +361,15 @@ |
603 | expected_token_types = (LoginTokenType.VALIDATEGPG, |
604 | LoginTokenType.VALIDATESIGNONLYGPG) |
605 | |
606 | + @property |
607 | + def label(self): |
608 | + if self.context.tokentype == LoginTokenType.VALIDATESIGNONLYGPG: |
609 | + return 'Confirm sign-only OpenPGP key' |
610 | + else: |
611 | + assert self.context.tokentype == LoginTokenType.VALIDATEGPG, ( |
612 | + 'unexpected token type: %r' % self.context.tokentype) |
613 | + return 'Confirm OpenPGP key' |
614 | + |
615 | def initialize(self): |
616 | if not self.redirectIfInvalidOrConsumedToken(): |
617 | if self.context.tokentype == LoginTokenType.VALIDATESIGNONLYGPG: |
618 | @@ -571,6 +587,7 @@ |
619 | schema = Interface |
620 | field_names = [] |
621 | expected_token_types = (LoginTokenType.VALIDATEEMAIL,) |
622 | + label = 'Confirm e-mail address' |
623 | |
624 | def initialize(self): |
625 | self.redirectIfInvalidOrConsumedToken() |
626 | @@ -670,6 +687,7 @@ |
627 | class ValidateTeamEmailView(ValidateEmailView): |
628 | |
629 | expected_token_types = (LoginTokenType.VALIDATETEAMEMAIL,) |
630 | + # The desired label is the same as ValidateEmailView. |
631 | |
632 | def markEmailAsValid(self, email): |
633 | """See `ValidateEmailView`""" |
634 | @@ -679,6 +697,7 @@ |
635 | class MergePeopleView(BaseTokenView, LaunchpadView): |
636 | expected_token_types = (LoginTokenType.ACCOUNTMERGE,) |
637 | mergeCompleted = False |
638 | + label = 'Merge Launchpad accounts' |
639 | |
640 | def initialize(self): |
641 | self.redirectIfInvalidOrConsumedToken() |
642 | |
643 | === modified file 'lib/canonical/launchpad/browser/packaging.py' |
644 | --- lib/canonical/launchpad/browser/packaging.py 2009-06-25 05:30:52 +0000 |
645 | +++ lib/canonical/launchpad/browser/packaging.py 2009-09-12 06:11:08 +0000 |
646 | @@ -18,9 +18,15 @@ |
647 | |
648 | class PackagingAddView(LaunchpadFormView): |
649 | schema = IPackaging |
650 | - label = 'Add distribution packaging record' |
651 | field_names = ['distroseries', 'sourcepackagename', 'packaging'] |
652 | |
653 | + @property |
654 | + def label(self): |
655 | + """See `LaunchpadFormView`.""" |
656 | + return 'Packaging of %s in distributions' % self.context.displayname |
657 | + |
658 | + page_title = label |
659 | + |
660 | def validate(self, data): |
661 | productseries = self.context |
662 | sourcepackagename = data['sourcepackagename'] |
663 | |
664 | === modified file 'lib/canonical/launchpad/browser/structuralsubscription.py' |
665 | --- lib/canonical/launchpad/browser/structuralsubscription.py 2009-08-24 01:09:07 +0000 |
666 | +++ lib/canonical/launchpad/browser/structuralsubscription.py 2009-09-17 17:12:58 +0000 |
667 | @@ -34,6 +34,16 @@ |
668 | custom_widget('subscriptions_team', LabeledMultiCheckBoxWidget) |
669 | custom_widget('remove_other_subscriptions', LabeledMultiCheckBoxWidget) |
670 | |
671 | + override_title_breadcrumbs = True |
672 | + |
673 | + @property |
674 | + def page_title(self): |
675 | + return 'Subscribe to Bugs in %s' % self.context.title |
676 | + |
677 | + @property |
678 | + def label(self): |
679 | + return self.page_title |
680 | + |
681 | def setUpFields(self): |
682 | """See LaunchpadFormView.""" |
683 | LaunchpadFormView.setUpFields(self) |
684 | @@ -167,7 +177,7 @@ |
685 | 'e-mail each time someone reports or changes one of ' |
686 | 'its public bugs.' % target.displayname) |
687 | elif is_subscribed and not subscribe: |
688 | - target.removeBugSubscription(self.user) |
689 | + target.removeBugSubscription(self.user, self.user) |
690 | self.request.response.addNotification( |
691 | 'You have unsubscribed from "%s". You ' |
692 | 'will no longer automatically receive e-mail about ' |
693 | @@ -197,7 +207,7 @@ |
694 | team.displayname, self.context.displayname)) |
695 | |
696 | for team in subscriptions - form_selected_teams: |
697 | - target.removeBugSubscription(team) |
698 | + target.removeBugSubscription(team, self.user) |
699 | self.request.response.addNotification( |
700 | 'The %s team will no longer automatically receive ' |
701 | 'e-mail about changes to public bugs in "%s".' % ( |
702 | @@ -220,7 +230,7 @@ |
703 | |
704 | subscriptions_to_remove = data.get('remove_other_subscriptions', []) |
705 | for subscription in subscriptions_to_remove: |
706 | - target.removeBugSubscription(subscription) |
707 | + target.removeBugSubscription(subscription, self.user) |
708 | self.request.response.addNotification( |
709 | '%s will no longer automatically receive e-mail about ' |
710 | 'public bugs in "%s".' % ( |
711 | |
712 | === modified file 'lib/canonical/launchpad/database/librarian.py' |
713 | --- lib/canonical/launchpad/database/librarian.py 2009-08-27 01:32:01 +0000 |
714 | +++ lib/canonical/launchpad/database/librarian.py 2009-09-14 19:32:10 +0000 |
715 | @@ -98,7 +98,7 @@ |
716 | |
717 | def getURL(self): |
718 | """See ILibraryFileAlias.getURL""" |
719 | - if config.vhosts.use_https: |
720 | + if config.librarian.use_https: |
721 | return self.https_url |
722 | else: |
723 | return self.http_url |
724 | |
725 | === modified file 'lib/canonical/launchpad/database/structuralsubscription.py' |
726 | --- lib/canonical/launchpad/database/structuralsubscription.py 2009-07-23 13:44:13 +0000 |
727 | +++ lib/canonical/launchpad/database/structuralsubscription.py 2009-08-25 12:04:58 +0000 |
728 | @@ -5,6 +5,7 @@ |
729 | __all__ = ['StructuralSubscription', |
730 | 'StructuralSubscriptionTargetMixin'] |
731 | |
732 | +from zope.component import getUtility |
733 | from zope.interface import implements |
734 | |
735 | from sqlobject import ForeignKey |
736 | @@ -16,9 +17,10 @@ |
737 | |
738 | from canonical.launchpad.interfaces import ( |
739 | BlueprintNotificationLevel, BugNotificationLevel, DeleteSubscriptionError, |
740 | - IDistribution, IDistributionSourcePackage, IDistroSeries, IMilestone, |
741 | - IProduct, IProductSeries, IProject, IStructuralSubscription, |
742 | - IStructuralSubscriptionTarget) |
743 | + IDistribution, IDistributionSourcePackage, IDistroSeries, |
744 | + ILaunchpadCelebrities, IMilestone, IProduct, IProductSeries, IProject, |
745 | + IStructuralSubscription, IStructuralSubscriptionTarget, |
746 | + UserCannotSubscribePerson) |
747 | from lp.registry.interfaces.person import ( |
748 | validate_public_person, validate_person_not_private_membership) |
749 | |
750 | @@ -129,8 +131,35 @@ |
751 | '%s is not a valid structural subscription target.') |
752 | return args |
753 | |
754 | + def _userCanAlterSubscription(self, subscriber, subscribed_by): |
755 | + """Check if a user can change a subscription for a person.""" |
756 | + # A Launchpad administrator or the user can subscribe a user. |
757 | + # A Launchpad or team admin can subscribe a team. |
758 | + |
759 | + # Nobody else can, unless the context is a IDistributionSourcePackage, |
760 | + # in which case the drivers or owner can. |
761 | + if IDistributionSourcePackage.providedBy(self): |
762 | + for driver in self.distribution.drivers: |
763 | + if subscribed_by.inTeam(driver): |
764 | + return True |
765 | + if subscribed_by.inTeam(self.distribution.owner): |
766 | + return True |
767 | + |
768 | + admins = getUtility(ILaunchpadCelebrities).admin |
769 | + return (subscriber is subscribed_by or |
770 | + subscriber in subscribed_by.getAdministratedTeams() or |
771 | + subscribed_by.inTeam(admins)) |
772 | + |
773 | def addSubscription(self, subscriber, subscribed_by): |
774 | """See `IStructuralSubscriptionTarget`.""" |
775 | + if subscriber is None: |
776 | + subscriber = subscribed_by |
777 | + |
778 | + if not self._userCanAlterSubscription(subscriber, subscribed_by): |
779 | + raise UserCannotSubscribePerson( |
780 | + '%s does not have permission to subscribe %s.' % ( |
781 | + subscribed_by.name, subscriber.name)) |
782 | + |
783 | existing_subscription = self.getSubscription(subscriber) |
784 | |
785 | if existing_subscription is not None: |
786 | @@ -151,20 +180,28 @@ |
787 | sub.bug_notification_level = BugNotificationLevel.COMMENTS |
788 | return sub |
789 | |
790 | - def removeBugSubscription(self, person): |
791 | + def removeBugSubscription(self, subscriber, unsubscribed_by): |
792 | """See `IStructuralSubscriptionTarget`.""" |
793 | + if subscriber is None: |
794 | + subscriber = unsubscribed_by |
795 | + |
796 | + if not self._userCanAlterSubscription(subscriber, unsubscribed_by): |
797 | + raise UserCannotSubscribePerson( |
798 | + '%s does not have permission to unsubscribe %s.' % ( |
799 | + unsubscribed_by.name, subscriber.name)) |
800 | + |
801 | subscription_to_remove = None |
802 | for subscription in self.getSubscriptions( |
803 | min_bug_notification_level=BugNotificationLevel.METADATA): |
804 | # Only search for bug subscriptions |
805 | - if subscription.subscriber == person: |
806 | + if subscription.subscriber == subscriber: |
807 | subscription_to_remove = subscription |
808 | break |
809 | |
810 | if subscription_to_remove is None: |
811 | raise DeleteSubscriptionError( |
812 | "%s is not subscribed to %s." % ( |
813 | - person.name, self.displayname)) |
814 | + subscriber.name, self.displayname)) |
815 | else: |
816 | if (subscription_to_remove.blueprint_notification_level > |
817 | BlueprintNotificationLevel.NOTHING): |
818 | |
819 | === modified file 'lib/canonical/launchpad/doc/canonical_url_examples.txt' |
820 | --- lib/canonical/launchpad/doc/canonical_url_examples.txt 2009-08-25 22:27:24 +0000 |
821 | +++ lib/canonical/launchpad/doc/canonical_url_examples.txt 2009-09-10 10:27:20 +0000 |
822 | @@ -22,7 +22,7 @@ |
823 | |
824 | >>> from canonical.launchpad.interfaces import ( |
825 | ... IMaloneApplication, IBazaarApplication, |
826 | - ... IRosettaApplication, ILaunchpadRoot, IQuestionSet |
827 | + ... ILaunchpadRoot, IQuestionSet |
828 | ... ) |
829 | |
830 | The Launchpad homepage. |
831 | @@ -35,11 +35,6 @@ |
832 | >>> canonical_url(getUtility(IMaloneApplication)) |
833 | u'http://launchpad.dev/bugs' |
834 | |
835 | -The Rosetta homepage. |
836 | - |
837 | - >>> canonical_url(getUtility(IRosettaApplication)) |
838 | - u'http://launchpad.dev/translations' |
839 | - |
840 | The Bazaar homepage. |
841 | |
842 | >>> canonical_url(getUtility(IBazaarApplication)) |
843 | @@ -56,6 +51,9 @@ |
844 | >>> canonical_url(getUtility(IMailingListSet)) |
845 | u'http://launchpad.dev/+mailinglists' |
846 | |
847 | +Launchpad Translations (Rosetta) canonical_url examples are in |
848 | +lib/lp/translations/doc/canonical_url_examples.txt. |
849 | + |
850 | |
851 | == Persons and Teams == |
852 | |
853 | @@ -300,20 +298,20 @@ |
854 | An IBugTrackerSet. |
855 | |
856 | >>> canonical_url(getUtility(IBugTrackerSet)) |
857 | - u'http://launchpad.dev/bugs/bugtrackers' |
858 | + u'http://bugs.launchpad.dev/bugs/bugtrackers' |
859 | |
860 | A remote bug tracker. |
861 | |
862 | >>> mozilla_bugtracker = getUtility(IBugTrackerSet)['mozilla.org'] |
863 | >>> canonical_url(mozilla_bugtracker) |
864 | - u'http://launchpad.dev/bugs/bugtrackers/mozilla.org' |
865 | + u'http://bugs.launchpad.dev/bugs/bugtrackers/mozilla.org' |
866 | |
867 | A bug from a remote bug tracker. |
868 | |
869 | >>> remote_bug = RemoteBug(mozilla_bugtracker, '42', |
870 | ... mozilla_bugtracker.getBugsWatching('42')) |
871 | >>> canonical_url(remote_bug) |
872 | - u'http://launchpad.dev/bugs/bugtrackers/mozilla.org/42' |
873 | + u'http://bugs.launchpad.dev/bugs/bugtrackers/mozilla.org/42' |
874 | |
875 | |
876 | == Branches == |
877 | @@ -363,97 +361,6 @@ |
878 | http://code.launchpad.dev/~name12/gnome-terminal/main/+merge/.../comments/... |
879 | |
880 | |
881 | -== POTemplates and so on == |
882 | - |
883 | - >>> from lp.translations.interfaces.potemplate import IPOTemplateSet |
884 | - >>> from lp.translations.interfaces.translationgroup import ( |
885 | - ... ITranslationGroupSet) |
886 | - |
887 | -Most Rosetta pages hang off IPOTemplateSubset objects, of which there are two |
888 | -varieties: distribution and upstream. |
889 | - |
890 | -First, the distribution kind. We'll need the source package name. |
891 | - |
892 | - >>> sourcepackagename = sourcepackagenameset['evolution'] |
893 | - |
894 | -And here's our subset. |
895 | - |
896 | - >>> potemplateset = getUtility(IPOTemplateSet) |
897 | - >>> potemplatesubset = potemplateset.getSubset( |
898 | - ... distroseries=hoary, sourcepackagename=sourcepackagename) |
899 | - |
900 | - >>> canonical_url(potemplatesubset) |
901 | - u'http://launchpad.dev/ubuntu/hoary/+source/evolution/+pots' |
902 | - |
903 | -We can get a particular PO template for this source package by its PO template |
904 | -name. |
905 | - |
906 | - >>> potemplate = potemplatesubset['evolution-2.2'] |
907 | - >>> canonical_url(potemplate) |
908 | - u'http://launchpad.dev/ubuntu/hoary/+source/evolution/+pots/evolution-2.2' |
909 | - |
910 | -And we can get a particular PO file for this PO template by its language code. |
911 | - |
912 | - >>> pofile = potemplate.getPOFileByLang('es') |
913 | - >>> canonical_url(pofile) |
914 | - u'http://launchpad.dev/ubuntu/hoary/+source/evolution/+pots/evolution-2.2/es' |
915 | - |
916 | -Also, we can get the url to a translation message. |
917 | - |
918 | - >>> potmsgset = potemplate.getPOTMsgSetBySequence(1) |
919 | - >>> translationmessage = potmsgset.getCurrentTranslationMessage( |
920 | - ... pofile.potemplate, pofile.language) |
921 | - >>> translationmessage.setPOFile(pofile) |
922 | - >>> print canonical_url(translationmessage) |
923 | - http://launchpad.dev/ubuntu/hoary/+source/evolution/+pots/evolution-2.2/es/1 |
924 | - |
925 | -Even for a dummy one. |
926 | - |
927 | - >>> potmsgset = potemplate.getPOTMsgSetBySequence(20) |
928 | - >>> translationmessage = potmsgset.getCurrentDummyTranslationMessage( |
929 | - ... pofile.potemplate, pofile.language) |
930 | - >>> print canonical_url(translationmessage) |
931 | - http://launchpad.dev/ubuntu/hoary/+source/evolution/+pots/evolution-2.2/es/20 |
932 | - |
933 | -Upstream POTemplateSubsets work in much the same way, except they hang off a |
934 | -product series. Let's get a product series. |
935 | - |
936 | -Now we can get an upstream subset and do the same sorts of thing as we did |
937 | -with the distro subset. |
938 | - |
939 | - >>> potemplatesubset = potemplateset.getSubset( |
940 | - ... productseries=evolution_trunk_series) |
941 | - >>> potemplate = potemplatesubset['evolution-2.2'] |
942 | - >>> canonical_url(potemplate) |
943 | - u'http://launchpad.dev/evolution/trunk/+pots/evolution-2.2' |
944 | - |
945 | - >>> pofile = potemplate.getPOFileByLang('es') |
946 | - >>> canonical_url(pofile) |
947 | - u'http://launchpad.dev/evolution/trunk/+pots/evolution-2.2/es' |
948 | - |
949 | -Also, we can get the url to a dummy one |
950 | - |
951 | - >>> potmsgset = potemplate.getPOTMsgSetBySequence(1) |
952 | - >>> translationmessage = potmsgset.getCurrentTranslationMessage( |
953 | - ... pofile.potemplate, pofile.language) |
954 | - >>> translationmessage.setPOFile(pofile) |
955 | - >>> print canonical_url(translationmessage) |
956 | - http://launchpad.dev/evolution/trunk/+pots/evolution-2.2/es/1 |
957 | - |
958 | -Even for a dummy PO msgset |
959 | - |
960 | - >>> potmsgset = potemplate.getPOTMsgSetBySequence(20) |
961 | - >>> translationmessage = potmsgset.getCurrentDummyTranslationMessage( |
962 | - ... pofile.potemplate, pofile.language) |
963 | - >>> print canonical_url(translationmessage) |
964 | - http://launchpad.dev/evolution/trunk/+pots/evolution-2.2/es/20 |
965 | - |
966 | -Rosetta also has translation groups. |
967 | - |
968 | - >>> canonical_url(getUtility(ITranslationGroupSet)) |
969 | - u'http://translations.launchpad.dev/+groups' |
970 | - |
971 | - |
972 | == Specifications == |
973 | |
974 | >>> from canonical.launchpad.interfaces import ISpecificationSet |
975 | |
976 | === modified file 'lib/canonical/launchpad/doc/distribution-soyuz.txt' |
977 | --- lib/canonical/launchpad/doc/distribution-soyuz.txt 2009-08-14 12:59:56 +0000 |
978 | +++ lib/canonical/launchpad/doc/distribution-soyuz.txt 2009-08-30 23:57:41 +0000 |
979 | @@ -13,9 +13,10 @@ |
980 | distribution: |
981 | |
982 | >>> from lp.registry.interfaces.distribution import IDistributionSet |
983 | + >>> from lp.registry.interfaces.pocket import PackagePublishingPocket |
984 | >>> from canonical.launchpad.interfaces import ( |
985 | ... ISourcePackageName, IBinaryPackageName, |
986 | - ... PackagePublishingPocket, PackagePublishingStatus) |
987 | + ... PackagePublishingStatus) |
988 | |
989 | >>> distroset = getUtility(IDistributionSet) |
990 | >>> gentoo = distroset.getByName("gentoo") |
991 | |
992 | === modified file 'lib/canonical/launchpad/doc/hierarchical-menu.txt' |
993 | --- lib/canonical/launchpad/doc/hierarchical-menu.txt 2009-08-26 09:33:33 +0000 |
994 | +++ lib/canonical/launchpad/doc/hierarchical-menu.txt 2009-09-17 20:04:28 +0000 |
995 | @@ -170,46 +170,6 @@ |
996 | url='http://launchpad.dev/joy-of-cooking' |
997 | text='Joy of cooking'> |
998 | |
999 | -Breadcrumbs may have icons. The icon is only set for a breadcrumb if |
1000 | -the builder's context has an IPathAdapter registration. |
1001 | - |
1002 | - >>> from zope.traversing.interfaces import IPathAdapter |
1003 | - >>> from canonical.launchpad.webapp.tales import ( |
1004 | - ... ObjectImageDisplayAPI) |
1005 | - |
1006 | - # We need a custom image display adapter that overrides the |
1007 | - # the icon() method and returns an <img> tag. |
1008 | - >>> class RecipeImageDisplayAPI(ObjectImageDisplayAPI): |
1009 | - ... def icon(self): |
1010 | - ... return '<img src="/@@/recipe"/>' |
1011 | - |
1012 | - >>> provideAdapter( |
1013 | - ... RecipeImageDisplayAPI, [IRecipe], IPathAdapter, 'image') |
1014 | - |
1015 | - >>> breadcrumb = DynamicBreadcrumb(recipe) |
1016 | - >>> breadcrumb |
1017 | - <DynamicBreadcrumb |
1018 | - url='http://launchpad.dev/joy-of-cooking/spam' |
1019 | - text='Spam' |
1020 | - icon='<img src="/@@/recipe"/>'> |
1021 | - |
1022 | -The icon is not set if the default image adapter can not find an |
1023 | -icon for the object. |
1024 | - |
1025 | - # We'll use the default image adapter, which doesn't know about |
1026 | - # ICookbook objects. |
1027 | - >>> provideAdapter( |
1028 | - ... ObjectImageDisplayAPI, [ICookbook], IPathAdapter, 'image') |
1029 | - |
1030 | - >>> print queryAdapter(cookbook, IPathAdapter, name='image').icon() |
1031 | - None |
1032 | - |
1033 | - >>> breadcrumb = DynamicBreadcrumb(cookbook) |
1034 | - >>> breadcrumb |
1035 | - <DynamicBreadcrumb |
1036 | - url='http://launchpad.dev/joy-of-cooking' |
1037 | - text='Joy of cooking'> |
1038 | - |
1039 | |
1040 | == Customizing the hierarchy == |
1041 | |
1042 | @@ -230,8 +190,7 @@ |
1043 | >>> spammy_hierarchy.items |
1044 | [<TextualBreadcrumb |
1045 | url='http://launchpad.dev/joy-of-cooking/spam' |
1046 | - text='Spam' |
1047 | - icon='<img src="/@@/recipe"/>'>] |
1048 | + text='Spam'>] |
1049 | |
1050 | |
1051 | == Rendering the list == |
1052 | @@ -267,26 +226,3 @@ |
1053 | |
1054 | >>> print_hierarchy(homepage_hierarchy.render()) |
1055 | Location: |
1056 | - |
1057 | -Breadcrumbs in the hierarchy that have icons are rendered with an <img> |
1058 | -tag. Breadcrumbs without icons are not. |
1059 | - |
1060 | - >>> breadcrumb_no_icon, breadcrumb_with_icon = hierarchy.items |
1061 | - |
1062 | - >>> breadcrumb_no_icon |
1063 | - <TextualBreadcrumb |
1064 | - url='http://launchpad.dev/joy-of-cooking' |
1065 | - text='Joy of cooking'> |
1066 | - |
1067 | - >>> breadcrumb_with_icon |
1068 | - <TextualBreadcrumb |
1069 | - url='http://launchpad.dev/joy-of-cooking/spam' |
1070 | - text='Spam' |
1071 | - icon='<img src="/@@/recipe"/>'> |
1072 | - |
1073 | - >>> soup = BeautifulSoup(hierarchy.render()) |
1074 | - >>> img_elems = soup.findAll('img') |
1075 | - >>> for img in img_elems: |
1076 | - ... print img |
1077 | - <img src="/@@/recipe" /> |
1078 | - |
1079 | |
1080 | === modified file 'lib/canonical/launchpad/doc/launchbag.txt' |
1081 | --- lib/canonical/launchpad/doc/launchbag.txt 2009-05-12 08:10:20 +0000 |
1082 | +++ lib/canonical/launchpad/doc/launchbag.txt 2009-09-03 00:08:57 +0000 |
1083 | @@ -57,6 +57,11 @@ |
1084 | >>> print launchbag.login |
1085 | None |
1086 | |
1087 | +'user' will also be set to None: |
1088 | + |
1089 | +>>> print launchbag.user |
1090 | +None |
1091 | + |
1092 | Let's do a cookie auth principal identification. In this case, the login |
1093 | will be cookie@example.com. |
1094 | |
1095 | |
1096 | === modified file 'lib/canonical/launchpad/doc/launchpadview.txt' |
1097 | --- lib/canonical/launchpad/doc/launchpadview.txt 2009-04-17 10:32:16 +0000 |
1098 | +++ lib/canonical/launchpad/doc/launchpadview.txt 2009-09-16 08:08:56 +0000 |
1099 | @@ -101,3 +101,13 @@ |
1100 | >>> view.error_message = structured('Information overload.') |
1101 | >>> view.error_message.escapedtext |
1102 | u'Information overload.' |
1103 | + |
1104 | +Every Launchpad view also knows whether edge redirection has been inhibited. |
1105 | + |
1106 | + >>> view.isRedirectInhibited() |
1107 | + False |
1108 | + >>> new_request = TestRequest(HTTP_COOKIE="inhibit_beta_redirect=1") |
1109 | + >>> view = MyView(context, new_request) |
1110 | + >>> view.isRedirectInhibited() |
1111 | + True |
1112 | + |
1113 | |
1114 | === modified file 'lib/canonical/launchpad/doc/librarian.txt' |
1115 | --- lib/canonical/launchpad/doc/librarian.txt 2009-08-07 12:54:05 +0000 |
1116 | +++ lib/canonical/launchpad/doc/librarian.txt 2009-09-15 02:18:25 +0000 |
1117 | @@ -78,7 +78,7 @@ |
1118 | |
1119 | >>> from textwrap import dedent |
1120 | >>> test_data = dedent(""" |
1121 | - ... [vhosts] |
1122 | + ... [librarian] |
1123 | ... use_https: true |
1124 | ... """) |
1125 | >>> config.push('test', test_data) |
1126 | @@ -689,8 +689,8 @@ |
1127 | |
1128 | == Time to last download == |
1129 | |
1130 | -The .last_downloaded property gives us the time delta from today to the day |
1131 | -that file was last downloaded, or None if it's never been downloaded. |
1132 | +The .last_downloaded property gives us the time delta from today to the day |
1133 | +that file was last downloaded, or None if it's never been downloaded. |
1134 | |
1135 | >>> today = datetime.now(utc).date() |
1136 | >>> public_file.last_downloaded == today - last_downloaded_date |
1137 | |
1138 | === modified file 'lib/canonical/launchpad/doc/structural-subscriptions.txt' |
1139 | --- lib/canonical/launchpad/doc/structural-subscriptions.txt 2009-04-17 10:32:16 +0000 |
1140 | +++ lib/canonical/launchpad/doc/structural-subscriptions.txt 2009-08-25 11:21:05 +0000 |
1141 | @@ -160,8 +160,8 @@ |
1142 | |
1143 | >>> evolution_sub.blueprint_notification_level = ( |
1144 | ... BlueprintNotificationLevel.METADATA) |
1145 | - >>> evolution_package.removeBugSubscription(sampleperson) |
1146 | - >>> ubuntu.removeBugSubscription(sampleperson) |
1147 | + >>> evolution_package.removeBugSubscription(sampleperson, sampleperson) |
1148 | + >>> ubuntu.removeBugSubscription(sampleperson, sampleperson) |
1149 | >>> syncUpdate(evolution_sub) |
1150 | |
1151 | Sample Person is no longer a subscriber to the package, but Foo Bar |
1152 | |
1153 | === modified file 'lib/canonical/launchpad/doc/tales.txt' |
1154 | --- lib/canonical/launchpad/doc/tales.txt 2009-08-20 12:24:29 +0000 |
1155 | +++ lib/canonical/launchpad/doc/tales.txt 2009-09-10 10:49:44 +0000 |
1156 | @@ -214,6 +214,19 @@ |
1157 | >>> test_tales('foo/fmt:shorten/8', foo='abcdefghij') |
1158 | 'abcde...' |
1159 | |
1160 | +To ellipsize the middle of a string. use fmt:ellipsize and pass the max |
1161 | +length. |
1162 | + |
1163 | + >>> print test_tales('foo/fmt:ellipsize/25', |
1164 | + ... foo='foo-bar-baz-bazoo_22.443.tar.gz') |
1165 | + foo-bar-baz....443.tar.gz |
1166 | + |
1167 | +The string is not ellipsized if it is less than the max length. |
1168 | + |
1169 | + >>> print test_tales('foo/fmt:ellipsize/25', |
1170 | + ... foo='firefox-0.9.2.orig.tar.gz') |
1171 | + firefox-0.9.2.orig.tar.gz |
1172 | + |
1173 | To preserve newlines in text when displaying as HTML, use fmt:nl_to_br: |
1174 | |
1175 | >>> test_tales('foo/fmt:nl_to_br', |
1176 | @@ -333,6 +346,8 @@ |
1177 | * blueprint-branch links |
1178 | * projects |
1179 | * questions |
1180 | + * distributions |
1181 | + * distroseries |
1182 | |
1183 | |
1184 | Person entries |
1185 | @@ -634,6 +649,22 @@ |
1186 | u'<a... class="sprite question">1:...</a>' |
1187 | |
1188 | |
1189 | +Distributions |
1190 | +............. |
1191 | + |
1192 | + >>> distribution = factory.makeDistribution() |
1193 | + >>> test_tales("distribution/fmt:link", distribution=distribution) |
1194 | + u'<a... class="sprite distribution">...</a>' |
1195 | + |
1196 | + |
1197 | +Distribution Series |
1198 | +................... |
1199 | + |
1200 | + >>> distroseries = factory.makeDistroArchSeries().distroseries |
1201 | + >>> test_tales("distroseries/fmt:link", distroseries=distroseries) |
1202 | + u'<a href="...">...</a>' |
1203 | + |
1204 | + |
1205 | The fmt: namespace for specially formatted object info |
1206 | ------------------------------------------------------ |
1207 | |
1208 | @@ -650,7 +681,7 @@ |
1209 | The "standard" 'url' name is supported: |
1210 | |
1211 | >>> test_tales("bugtracker/fmt:url", bugtracker=bugtracker) |
1212 | - u'/bugs/bugtrackers/email' |
1213 | + u'http://bugs.launchpad.dev/bugs/bugtrackers/email' |
1214 | |
1215 | (The url is relative if possible, and our test request claims to be from |
1216 | launchpad.dev, so the url is relative.) |
1217 | |
1218 | === modified file 'lib/canonical/launchpad/icing/style-3-0.css' |
1219 | --- lib/canonical/launchpad/icing/style-3-0.css 2009-09-01 16:19:19 +0000 |
1220 | +++ lib/canonical/launchpad/icing/style-3-0.css 2009-09-18 03:48:00 +0000 |
1221 | @@ -7,7 +7,7 @@ |
1222 | #maincontent { |
1223 | float: left; |
1224 | width: 100%; |
1225 | - margin-right: -25em; |
1226 | + margin-right: -25%; |
1227 | } |
1228 | #maincontent ol, #maincontent ul { |
1229 | padding-left: auto; |
1230 | @@ -207,7 +207,7 @@ |
1231 | } |
1232 | .footer { |
1233 | clear: both; |
1234 | - margin-top: 2em; |
1235 | + margin-top: 4em; |
1236 | padding-top: 0.5em; |
1237 | } |
1238 | .footer .lp-arcana { |
1239 | @@ -230,14 +230,20 @@ |
1240 | } |
1241 | .portlet, .aside { |
1242 | clear: both; |
1243 | - border-top: 1px solid #d6d6d6; |
1244 | - padding: 0.5em 0; |
1245 | + border-top: 1px solid #EBEBEB; |
1246 | + padding: 1em 0; |
1247 | } |
1248 | .top-portlet { |
1249 | padding: 0 0 0.5em 0; |
1250 | margin: 0 0 1em; |
1251 | } |
1252 | - |
1253 | +.full-page-width { |
1254 | + z-index: 10; |
1255 | + width: 131%; |
1256 | + } |
1257 | +.warning.message { |
1258 | + margin-top: 17px; |
1259 | + } |
1260 | /* |
1261 | |
1262 | Use percentages when setting font-size. |
1263 | @@ -397,6 +403,9 @@ |
1264 | color: #666; |
1265 | font-style: italic; |
1266 | } |
1267 | +li .registered { |
1268 | + font-style: normal; |
1269 | + } |
1270 | .description { |
1271 | clear: both; |
1272 | font-size: 100%; |
1273 | @@ -523,6 +532,15 @@ |
1274 | color: #747474; |
1275 | } |
1276 | |
1277 | +/* Registering slot */ |
1278 | +.registering { |
1279 | + float: right; |
1280 | + padding-top: 10px; |
1281 | + font-size: 85%; |
1282 | + color: #666; |
1283 | + font-style: italic; |
1284 | +} |
1285 | + |
1286 | /* Side content exceptions */ |
1287 | .side { |
1288 | padding: 0.5em; |
1289 | @@ -546,9 +564,6 @@ |
1290 | background: #fbfbfb; |
1291 | } |
1292 | |
1293 | -.downloads a { |
1294 | - color: #4f843c; |
1295 | - } |
1296 | .downloads li { |
1297 | margin: 0; |
1298 | padding: 2px 0 0; |
1299 | @@ -564,17 +579,20 @@ |
1300 | border-radius: 3px; |
1301 | background: #4f843c url(/@@/bg-project-downloads.png) center right no-repeat; |
1302 | padding: 6%; |
1303 | - padding-right: 50px; |
1304 | + padding-right: 40px; |
1305 | color: #fff; |
1306 | - font-size: 108%; |
1307 | - text-decoration: underline; |
1308 | + font-size: 93%; |
1309 | + } |
1310 | +.downloads .version { |
1311 | + -moz-border-radius: 5px 5px 0 0; |
1312 | + -webkit-border-radius: 5px 5px 0 0; |
1313 | + -khtml-border-radius: 5px 5px 0 0; |
1314 | + border-radius: 5px 5px 0 0; |
1315 | + background: #d3e3c7; |
1316 | + padding: 0.2em 1em; |
1317 | } |
1318 | .downloads .released { |
1319 | - margin: .3em 0 1em 0; |
1320 | - padding: 0 .2em 0 0; |
1321 | - text-align: right; |
1322 | - } |
1323 | -.downloads .released span { |
1324 | + margin: .3em 0 .5em 0; |
1325 | -moz-border-radius: 0 0 5px 5px; |
1326 | -webkit-border-radius: 0 0 5px 5px; |
1327 | -khtml-border-radius: 0 0 5px 5px; |
1328 | @@ -583,7 +601,7 @@ |
1329 | padding: 0.2em 1em; |
1330 | } |
1331 | .downloads .alternate { |
1332 | - text-align: right; |
1333 | + padding: 0 0 0 1em; |
1334 | } |
1335 | |
1336 | ul.super-add-action { |
1337 | @@ -610,11 +628,8 @@ |
1338 | text-decoration: underline; |
1339 | } |
1340 | |
1341 | -.involvement ul { |
1342 | - border-top: 1px solid #d0d0d0; |
1343 | - } |
1344 | .involvement li { |
1345 | - border-bottom: 1px solid #d0d0d0; |
1346 | + border-top: 1px solid #d0d0d0; |
1347 | padding: 0; |
1348 | font-size: 108%; |
1349 | font-weight: bold; |
1350 | @@ -643,6 +658,10 @@ |
1351 | color: #5ba4c6; |
1352 | background: url(/@@/blueprints-arrow-right.png) right center no-repeat; |
1353 | } |
1354 | +.involvement a:hover { |
1355 | + text-decoration: none; |
1356 | + background-color: #eee; |
1357 | + } |
1358 | |
1359 | .announcements li { |
1360 | margin-bottom: 0.5em; |
1361 | @@ -662,6 +681,39 @@ |
1362 | margin-top: -2px; |
1363 | } |
1364 | |
1365 | +/* For the Latest updates portlet |
1366 | + * at https://launchpad.dev/~cprov/+archive/ppa */ |
1367 | +ul.latest-ppa-updates .duration { |
1368 | + font-size: 75%; |
1369 | +} |
1370 | +ul.latest-ppa-updates li { |
1371 | + padding: 3px; |
1372 | + background-repeat: no-repeat; |
1373 | + background-position:right center; |
1374 | +} |
1375 | +/* The following could be generalised for use to the following selector: |
1376 | + * .side .portlet li.nth-child(odd) |
1377 | + * if needed. */ |
1378 | +ul.latest-ppa-updates li:nth-child(odd) { |
1379 | + border-top: 1px solid #dedede; |
1380 | + border-bottom: 1px solid #dedede; |
1381 | + background-color: #eeeeff; |
1382 | +} |
1383 | +ul.latest-ppa-updates li.FULLYBUILT { |
1384 | + background-image: url('/@@/yes'); |
1385 | +} |
1386 | +ul.latest-ppa-updates li.FULLYBUILT_PENDING { |
1387 | + background-image: url('/@@/build-success-publishing'); |
1388 | +} |
1389 | +ul.latest-ppa-updates li.NEEDSBUILD { |
1390 | + background-image: url('/@@/build-needed'); |
1391 | +} |
1392 | +ul.latest-ppa-updates li.FAILEDTOBUILD { |
1393 | + background-image: url('/@@/build-failed'); |
1394 | +} |
1395 | +ul.latest-ppa-updates li.BUILDING { |
1396 | + background-image: url('/@@/processing'); |
1397 | +} |
1398 | /* From nice_pre in tales.py */ |
1399 | pre.wrap { |
1400 | white-space: -moz-pre-wrap; |
1401 | @@ -672,11 +724,22 @@ |
1402 | |
1403 | /* ==== Listing tables ==== */ |
1404 | |
1405 | -table.listing thead { |
1406 | - font-size: 116%; |
1407 | -} |
1408 | table.listing th { |
1409 | - font-weight: bold; |
1410 | + font-weight: bold; |
1411 | +} |
1412 | +table.narrow-listing { |
1413 | + width: 45em; |
1414 | +} |
1415 | +/* ~person/+karma */ |
1416 | +table.cozy-listing { |
1417 | + width: 20em; |
1418 | + background-color: #fff; |
1419 | + border: 1px solid #d2d2d2; |
1420 | + border-bottom: 1px solid #d2d2d2; |
1421 | +} |
1422 | +table.cozy-listing td { |
1423 | + border: 1px #d2d2d2; |
1424 | + border-style: dotted none none none; |
1425 | } |
1426 | |
1427 | ul.language, li.language { |
1428 | @@ -688,57 +751,89 @@ |
1429 | /* ==== Translations hand-made forms ==== */ |
1430 | |
1431 | form.translations div.fields { |
1432 | - padding: 1em; |
1433 | + padding: 1em; |
1434 | } |
1435 | |
1436 | form.translations div.actions { |
1437 | - padding: 1em; |
1438 | - text-align: right; |
1439 | + padding: 1em; |
1440 | + text-align: right; |
1441 | + clear:both; |
1442 | } |
1443 | |
1444 | form.translations input { |
1445 | - padding-left: 0.5em; |
1446 | - padding-right: 0.5em; |
1447 | + padding-left: 0.5em; |
1448 | + padding-right: 0.5em; |
1449 | } |
1450 | |
1451 | form.translations select { |
1452 | - margin-left: 0.5em; |
1453 | - padding-right: 0.5em; |
1454 | + margin-left: 0.5em; |
1455 | + padding-right: 0.5em; |
1456 | } |
1457 | |
1458 | form.translations label { |
1459 | - padding-left: 0.5em; |
1460 | - padding-right: 1em; |
1461 | + padding-left: 0.5em; |
1462 | + padding-right: 1em; |
1463 | } |
1464 | |
1465 | form.translations .listbox label { |
1466 | - padding: 2px 1em 2px 2px; |
1467 | + padding: 2px 1em 2px 2px; |
1468 | } |
1469 | |
1470 | /* Provide top-alignment for radio boxes and longer explanations |
1471 | * without using tables. |
1472 | + * |
1473 | + * Examples: |
1474 | + * https://translations.launchpad.dev/evolution/trunk/+pots/evolution-2.2/es/+upload |
1475 | + * https://translations.launchpad.dev/evolution/trunk/+pots/evolution-2.2/+export |
1476 | */ |
1477 | -form.translations div.fields div.alignment { |
1478 | - position: relative; |
1479 | -} |
1480 | -form.translations div.alignment div.top-alignment { |
1481 | - position: absolute; |
1482 | - top: 0px; |
1483 | -} |
1484 | -form.translations div.alignment .spacer { |
1485 | - visibility: hidden; |
1486 | -} |
1487 | form.translations div.alignment .content { |
1488 | - display: inline-block; |
1489 | - margin-left: 0px; |
1490 | -} |
1491 | - |
1492 | + float:left; |
1493 | +} |
1494 | +form.translations div.alignment .selector { |
1495 | + margin-right: 0.5em; |
1496 | + float: left; |
1497 | + clear: both; |
1498 | +} |
1499 | form.translations div.alignment .content label { |
1500 | - padding: 0px; |
1501 | - margin: 0px; |
1502 | - font-weight: bold; |
1503 | -} |
1504 | - |
1505 | -table.narrow-listing { |
1506 | - width: 45em; |
1507 | + padding: 0px; |
1508 | + margin: 0px; |
1509 | + font-weight: bold; |
1510 | +} |
1511 | +form.translations div.alignment .secondary label { |
1512 | + font-weight: normal; |
1513 | + padding: 2px 1em 2px 2px; |
1514 | +} |
1515 | + |
1516 | +/* Translations statistics and legend. |
1517 | + * |
1518 | + * Examples: |
1519 | + * https://translations.launchpad.dev/ubuntu/hoary/+lang/es |
1520 | + * https://translations.launchpad.dev/evolution/trunk/+lang/es |
1521 | + */ |
1522 | + |
1523 | +div.translations-legend { |
1524 | + padding-top: 2em; |
1525 | + padding-bottom: 1em; |
1526 | +} |
1527 | +table.translation-stats td { |
1528 | + text-align:center; |
1529 | +} |
1530 | +table.translation-stats td.template-name { |
1531 | + text-align:left; |
1532 | +} |
1533 | +table.translation-stats tfoot td, |
1534 | +table.translation-stats tfoot th { |
1535 | + background-color: #f7f7f7; |
1536 | + border: 0px; |
1537 | + border-top: 2px solid #d2d2d2; |
1538 | + border-bottom: 2px solid #d2d2d2; |
1539 | + padding-top: 5px; |
1540 | + padding-bottom: 5px; |
1541 | + font-weight: bold; |
1542 | +} |
1543 | +table.translation-stats tfoot th { |
1544 | + text-align:left; |
1545 | +} |
1546 | +table.translation-stats tfoot td { |
1547 | + text-align:center; |
1548 | } |
1549 | |
1550 | === modified file 'lib/canonical/launchpad/icing/style.css' |
1551 | --- lib/canonical/launchpad/icing/style.css 2009-08-31 00:06:29 +0000 |
1552 | +++ lib/canonical/launchpad/icing/style.css 2009-09-18 13:59:17 +0000 |
1553 | @@ -141,7 +141,7 @@ |
1554 | .bullet {background:url(icon-sprites-2) 0 -1024px no-repeat;} |
1555 | .zoom-in {background:url(icon-sprites-2) 0 -1056px no-repeat;} |
1556 | .zoom-out {background:url(icon-sprites-2) 0 -1088px no-repeat;} |
1557 | -.arquitecture {background:url(icon-sprites-2) 0 -1120px no-repeat;} |
1558 | +.architecture {background:url(icon-sprites-2) 0 -1120px no-repeat;} |
1559 | .ppa-icon {background:url(icon-sprites-2) 0 -1147px no-repeat;} |
1560 | .ppa-icon-inactive {background:url(icon-sprites-2) 0 -1171px no-repeat;} |
1561 | |
1562 | @@ -308,7 +308,6 @@ |
1563 | margin: 0 0 0.5em; |
1564 | /* Content headings are highlighted using a different color (the color is |
1565 | overriden per-application in subsequent rules) instead of bold text. */ |
1566 | - color: #83ad23; |
1567 | font-weight: normal; |
1568 | } |
1569 | h1 {font-size: 2em;} |
1570 | @@ -675,30 +674,11 @@ |
1571 | |
1572 | /* === Universal page layout === */ |
1573 | |
1574 | -/* All pages begin with a global header or a topline. */ |
1575 | -#globalheader { |
1576 | - clear: both; |
1577 | - position: relative; |
1578 | - width: 100%; |
1579 | - height: 21px; |
1580 | - overflow: hidden; |
1581 | - margin: 0 0 1em 0; |
1582 | - padding: 0; |
1583 | - background-color: #EEE; |
1584 | - background-image: url(globalheader_bg.gif); |
1585 | - background-repeat: repeat-x; |
1586 | - background-position: top center; |
1587 | -} |
1588 | -#globalheader .sitemessage a { |
1589 | - color: #FFF; |
1590 | - text-decoration: underline; |
1591 | -} |
1592 | -/* Site-specific message. */ |
1593 | -#globalheader .sitemessage { |
1594 | - text-align: center; |
1595 | - color: #FFF; |
1596 | - font-size: 13px; |
1597 | - padding-top: 2px; |
1598 | +/* All pages include the sitemessage in the footer if one is defined |
1599 | + in the config. */ |
1600 | +.sitemessage { |
1601 | + font-size: 13px; |
1602 | + text-align: right; |
1603 | } |
1604 | |
1605 | /* === Login control === */ |
1606 | @@ -729,7 +709,8 @@ |
1607 | margin-left: 0; |
1608 | list-style-type: none; |
1609 | clear: both; |
1610 | - margin-bottom: 0.5em; |
1611 | + margin-bottom: 1em; |
1612 | + font-size: 80%; |
1613 | } |
1614 | ol.breadcrumbs li { |
1615 | display:inline; |
1616 | @@ -1492,11 +1473,16 @@ |
1617 | .boardCommentDetails table { |
1618 | margin: -0.5em -12px; |
1619 | } |
1620 | +.boardCommentDetails tr { |
1621 | + width: 100%; |
1622 | +} |
1623 | .boardCommentDetails td { |
1624 | + width:98%; |
1625 | padding: 0.5em 12px; |
1626 | } |
1627 | .boardCommentDetails td.bug-comment-index { |
1628 | - border-right: 1px solid #ddd; |
1629 | + width:2%; |
1630 | + border-left: 1px solid #ddd; |
1631 | } |
1632 | .boardCommentBody {padding: 0.5em 12px 0;} |
1633 | .boardBugActivityBody { |
1634 | @@ -1687,17 +1673,8 @@ |
1635 | border: 1px solid gray; |
1636 | padding: 0.3em; |
1637 | margin: 1em 0 1em 0; |
1638 | - float: left; /* So the border doesn't use 100% of the page */ |
1639 | -} |
1640 | - |
1641 | -div#build-status-summary { |
1642 | - clear:right; |
1643 | - float:right; |
1644 | -} |
1645 | - |
1646 | -div#build-status-summary h2 { |
1647 | - margin-top:0; |
1648 | -} |
1649 | +} |
1650 | + |
1651 | |
1652 | div#build-status-summary th { |
1653 | text-align:left; |
1654 | @@ -1762,6 +1739,30 @@ |
1655 | padding-left: 1em; |
1656 | } |
1657 | |
1658 | +/* PPA installation instructions slider.*/ |
1659 | +#pre-karmic-systems-slide-trigger { |
1660 | + cursor: pointer; |
1661 | + color: #093; /* for the underline on hover */ |
1662 | +} |
1663 | +#pre-karmic-systems-slide-trigger:hover { |
1664 | + text-decoration: underline |
1665 | +} |
1666 | +#ppa-install-slide-trigger { |
1667 | + cursor: pointer; |
1668 | + color: #093; /* for the underline on hover */ |
1669 | +} |
1670 | +#ppa-install-slide-trigger:hover { |
1671 | + text-decoration: underline |
1672 | +} |
1673 | +#ppa-install .widget-body { |
1674 | + margin: 1em; |
1675 | +} |
1676 | +#ppa-install .widget-body.lazr-closed { |
1677 | + height: 0px; |
1678 | + overflow: hidden; |
1679 | + display: block; |
1680 | +} |
1681 | + |
1682 | /* Brand the related PPA versions with the PPA icon (not currently |
1683 | in the sprite image.) */ |
1684 | div#slide-trigger { |
1685 | @@ -1831,6 +1832,13 @@ |
1686 | max-width: 100%; |
1687 | } |
1688 | |
1689 | +/* <pre> for presenting source changelog nicely. */ |
1690 | +pre.changelog { |
1691 | + margin: 0.5em; |
1692 | + padding: 5px; |
1693 | + font: 116% monospace; |
1694 | +} |
1695 | + |
1696 | /* Disabled PPAs when linkified (uploaders) should be gray and when not |
1697 | linkified should be lined-through. */ |
1698 | a.ppa-icon-inactive { |
1699 | @@ -1844,6 +1852,18 @@ |
1700 | padding: 1em 1em 2em 0em; |
1701 | } |
1702 | |
1703 | +/* XXX Michael Nelson 20090828 bug=420376 |
1704 | + * Temporary style for actions on the ppa/+packages page until we move |
1705 | + * the copy/delete packages functionality into the page itself. */ |
1706 | +div.ppa-packaging-tmp-actions { |
1707 | + float:right; |
1708 | +} |
1709 | + |
1710 | +div.ppa-packaging-tmp-actions .portlet { |
1711 | + border-top: 0 none; |
1712 | +} |
1713 | + |
1714 | + |
1715 | /* --- Code --- */ |
1716 | |
1717 | body.tab-branches #applications {background: url(app-code-wm.gif) no-repeat;} |
1718 | @@ -1920,6 +1940,7 @@ |
1719 | #branch-pending-writes { |
1720 | background: #FFF59C; |
1721 | width: 40em; |
1722 | + margin: 1em auto; |
1723 | padding-left: 1em; |
1724 | padding-right: 1em; |
1725 | padding-top: 0.2em; |
1726 | |
1727 | === modified file 'lib/canonical/launchpad/interfaces/_schema_circular_imports.py' |
1728 | --- lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2009-08-24 12:06:17 +0000 |
1729 | +++ lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2009-09-09 11:28:03 +0000 |
1730 | @@ -22,6 +22,8 @@ |
1731 | patch_collection_return_type, patch_plain_parameter_type, |
1732 | patch_choice_parameter_type, patch_reference_property) |
1733 | |
1734 | +from canonical.launchpad.interfaces.structuralsubscription import ( |
1735 | + IStructuralSubscription, IStructuralSubscriptionTarget) |
1736 | from lp.bugs.interfaces.bug import IBug |
1737 | from lp.bugs.interfaces.bugbranch import IBugBranch |
1738 | from lp.bugs.interfaces.bugnomination import IBugNomination |
1739 | @@ -46,6 +48,7 @@ |
1740 | from lp.registry.interfaces.distroseries import IDistroSeries |
1741 | from lp.registry.interfaces.person import IPerson, IPersonPublic |
1742 | from canonical.launchpad.interfaces.hwdb import HWBus, IHWSubmission |
1743 | +from lp.registry.interfaces.pocket import PackagePublishingPocket |
1744 | from lp.registry.interfaces.product import IProduct |
1745 | from lp.registry.interfaces.productseries import IProductSeries |
1746 | from lp.soyuz.interfaces.archive import IArchive |
1747 | @@ -59,7 +62,7 @@ |
1748 | from lp.soyuz.interfaces.publishing import ( |
1749 | IBinaryPackagePublishingHistory, ISecureBinaryPackagePublishingHistory, |
1750 | ISecureSourcePackagePublishingHistory, ISourcePackagePublishingHistory, |
1751 | - PackagePublishingPocket, PackagePublishingStatus) |
1752 | + PackagePublishingStatus) |
1753 | from lp.soyuz.interfaces.packageset import IPackageset |
1754 | from lp.soyuz.interfaces.queue import ( |
1755 | IPackageUpload, PackageUploadCustomFormat, PackageUploadStatus) |
1756 | @@ -277,3 +280,11 @@ |
1757 | IPackageUpload['pocket'].vocabulary = PackagePublishingPocket |
1758 | patch_reference_property(IPackageUpload, 'distroseries', IDistroSeries) |
1759 | patch_reference_property(IPackageUpload, 'archive', IArchive) |
1760 | + |
1761 | +# IStructuralSubscription |
1762 | +patch_reference_property( |
1763 | + IStructuralSubscription, 'target', IStructuralSubscriptionTarget) |
1764 | + |
1765 | +patch_reference_property( |
1766 | + IStructuralSubscriptionTarget, 'parent_subscription_target', |
1767 | + IStructuralSubscriptionTarget) |
1768 | |
1769 | === modified file 'lib/canonical/launchpad/interfaces/ftests/structural-subscription-target.txt' |
1770 | --- lib/canonical/launchpad/interfaces/ftests/structural-subscription-target.txt 2008-02-11 17:44:30 +0000 |
1771 | +++ lib/canonical/launchpad/interfaces/ftests/structural-subscription-target.txt 2009-08-25 11:21:05 +0000 |
1772 | @@ -48,31 +48,100 @@ |
1773 | >>> target.addBugSubscription(no_priv, no_priv) |
1774 | <StructuralSubscription ...> |
1775 | |
1776 | -Let's add an ITeam as one of the subscribers: |
1777 | +People can only be subscribed by themselves, and only the team admins may |
1778 | +subscribe a team. |
1779 | + |
1780 | +no-priv, who has no relationship to ubuntu-team, cannot subscribe it. |
1781 | |
1782 | >>> ubuntu_team = personset.getByName("ubuntu-team") |
1783 | - >>> target.addBugSubscription(ubuntu_team, ubuntu_team) |
1784 | - <StructuralSubscription ...> |
1785 | - |
1786 | - >>> sorted([sub.subscriber.name for sub in target.bug_subscriptions]) |
1787 | - [u'no-priv', u'ubuntu-team'] |
1788 | + >>> target.addBugSubscription(ubuntu_team, no_priv) |
1789 | + Traceback (most recent call last): |
1790 | + ... |
1791 | + UserCannotSubscribePerson: no-priv does not have permission to subscribe ubuntu-team. |
1792 | + |
1793 | +But kamion, an admin of the team, can. |
1794 | + |
1795 | + >>> kamion = personset.getByName("kamion") |
1796 | + >>> target.addBugSubscription(ubuntu_team, kamion) |
1797 | + <StructuralSubscription ...> |
1798 | + |
1799 | + >>> sorted([sub.subscriber.name for sub in target.bug_subscriptions]) |
1800 | + [u'no-priv', u'ubuntu-team'] |
1801 | + |
1802 | +foobar, a Launchpad administrator, can as well. |
1803 | + |
1804 | + >>> foobar = personset.getByName("name16") |
1805 | + >>> target.addBugSubscription(ubuntu_team, foobar) |
1806 | + <StructuralSubscription ...> |
1807 | + |
1808 | +A non-admin cannot subscribe a person other than themselves. |
1809 | + |
1810 | + >>> target.addBugSubscription(kamion, no_priv) |
1811 | + Traceback (most recent call last): |
1812 | + ... |
1813 | + UserCannotSubscribePerson: no-priv does not have permission to subscribe kamion. |
1814 | + >>> sorted([sub.subscriber.name for sub in target.bug_subscriptions]) |
1815 | + [u'no-priv', u'ubuntu-team'] |
1816 | + |
1817 | +But again, an admin can. |
1818 | + |
1819 | + >>> target.addBugSubscription(kamion, foobar) |
1820 | + <StructuralSubscription ...> |
1821 | + >>> sorted([sub.subscriber.name for sub in target.bug_subscriptions]) |
1822 | + [u'kamion', u'no-priv', u'ubuntu-team'] |
1823 | |
1824 | To remove a bug subscription, use |
1825 | IStructuralSubscriptionTarget.removeBugSubscription: |
1826 | |
1827 | - >>> target.removeBugSubscription(no_priv) |
1828 | - >>> sorted([sub.subscriber.name for sub in target.bug_subscriptions]) |
1829 | - [u'ubuntu-team'] |
1830 | + >>> target.removeBugSubscription(no_priv, no_priv) |
1831 | + >>> sorted([sub.subscriber.name for sub in target.bug_subscriptions]) |
1832 | + [u'kamion', u'ubuntu-team'] |
1833 | + |
1834 | +The subscription rules apply to unsubscription as well. |
1835 | + |
1836 | +An unprivileged user cannot unsubscribe a team. |
1837 | + |
1838 | + >>> target.removeBugSubscription(ubuntu_team, no_priv) |
1839 | + Traceback (most recent call last): |
1840 | + ... |
1841 | + UserCannotSubscribePerson: no-priv does not have permission to unsubscribe ubuntu-team. |
1842 | + >>> sorted([sub.subscriber.name for sub in target.bug_subscriptions]) |
1843 | + [u'kamion', u'ubuntu-team'] |
1844 | + |
1845 | +But a team admin can. |
1846 | + |
1847 | + >>> target.removeBugSubscription(ubuntu_team, kamion) |
1848 | + >>> sorted([sub.subscriber.name for sub in target.bug_subscriptions]) |
1849 | + [u'kamion'] |
1850 | + |
1851 | +An unprivileged user also cannot unsubscribe another user. |
1852 | + |
1853 | + >>> target.removeBugSubscription(kamion, no_priv) |
1854 | + Traceback (most recent call last): |
1855 | + ... |
1856 | + UserCannotSubscribePerson: no-priv does not have permission to unsubscribe kamion. |
1857 | + >>> sorted([sub.subscriber.name for sub in target.bug_subscriptions]) |
1858 | + [u'kamion'] |
1859 | + |
1860 | +But the user themselves can. |
1861 | + |
1862 | + >>> target.removeBugSubscription(kamion, kamion) |
1863 | + >>> sorted([sub.subscriber.name for sub in target.bug_subscriptions]) |
1864 | + [] |
1865 | |
1866 | Trying to remove a subscription that doesn't exist on a target raises a |
1867 | DeleteSubscriptionError. |
1868 | |
1869 | - >>> foobar = personset.getByName("name16") |
1870 | - >>> target.removeBugSubscription(foobar) |
1871 | + >>> target.removeBugSubscription(foobar, foobar) |
1872 | Traceback (most recent call last): |
1873 | ... |
1874 | DeleteSubscriptionError: ... |
1875 | |
1876 | +Let's subscribe ubuntu-team again. |
1877 | + |
1878 | + >>> target.addBugSubscription(ubuntu_team, foobar) |
1879 | + <StructuralSubscription ...> |
1880 | + |
1881 | Trying to remove a bug subscription when notification levels for other |
1882 | applications are set, doesn't remove the subscription. Instead the |
1883 | notification level for bugs is set to NOTHING. |
1884 | @@ -93,7 +162,7 @@ |
1885 | >>> print_subscriptions_list(target.getSubscriptions()) |
1886 | name16 COMMENTS LIFECYCLE |
1887 | ubuntu-team COMMENTS NOTHING |
1888 | - >>> target.removeBugSubscription(foobar) |
1889 | + >>> target.removeBugSubscription(foobar, foobar) |
1890 | >>> print_subscriptions_list(target.getSubscriptions()) |
1891 | name16 NOTHING LIFECYCLE |
1892 | ubuntu-team COMMENTS NOTHING |
1893 | |
1894 | === modified file 'lib/canonical/launchpad/interfaces/structuralsubscription.py' |
1895 | --- lib/canonical/launchpad/interfaces/structuralsubscription.py 2009-07-17 00:26:05 +0000 |
1896 | +++ lib/canonical/launchpad/interfaces/structuralsubscription.py 2009-09-09 14:26:18 +0000 |
1897 | @@ -13,7 +13,8 @@ |
1898 | 'DeleteSubscriptionError', |
1899 | 'IStructuralSubscription', |
1900 | 'IStructuralSubscriptionForm', |
1901 | - 'IStructuralSubscriptionTarget' |
1902 | + 'IStructuralSubscriptionTarget', |
1903 | + 'UserCannotSubscribePerson', |
1904 | ] |
1905 | |
1906 | from zope.interface import Attribute, Interface |
1907 | @@ -23,6 +24,14 @@ |
1908 | from canonical.launchpad import _ |
1909 | from canonical.launchpad.fields import ( |
1910 | ParticipatingPersonChoice, PublicPersonChoice) |
1911 | +from lp.registry.interfaces.person import IPerson |
1912 | + |
1913 | +from lazr.restful.declarations import ( |
1914 | + REQUEST_USER, call_with, exported, export_as_webservice_entry, |
1915 | + export_factory_operation, export_read_operation, export_write_operation, |
1916 | + operation_parameters, operation_returns_collection_of, |
1917 | + operation_returns_entry, webservice_error) |
1918 | +from lazr.restful.fields import Reference |
1919 | |
1920 | |
1921 | class BugNotificationLevel(DBEnumeratedType): |
1922 | @@ -83,6 +92,7 @@ |
1923 | |
1924 | class IStructuralSubscription(Interface): |
1925 | """A subscription to a Launchpad structure.""" |
1926 | + export_as_webservice_entry() |
1927 | |
1928 | id = Int(title=_('ID'), readonly=True, required=True) |
1929 | product = Int(title=_('Product'), required=False, readonly=True) |
1930 | @@ -95,13 +105,13 @@ |
1931 | title=_('Distribution series'), required=False, readonly=True) |
1932 | sourcepackagename = Int( |
1933 | title=_('Source package name'), required=False, readonly=True) |
1934 | - subscriber = ParticipatingPersonChoice( |
1935 | + subscriber = exported(ParticipatingPersonChoice( |
1936 | title=_('Subscriber'), required=True, vocabulary='ValidPersonOrTeam', |
1937 | - readonly=True, description=_("The person subscribed.")) |
1938 | - subscribed_by = PublicPersonChoice( |
1939 | + readonly=True, description=_("The person subscribed."))) |
1940 | + subscribed_by = exported(PublicPersonChoice( |
1941 | title=_('Subscribed by'), required=True, |
1942 | vocabulary='ValidPersonOrTeam', readonly=True, |
1943 | - description=_("The person creating the subscription.")) |
1944 | + description=_("The person creating the subscription."))) |
1945 | bug_notification_level = Choice( |
1946 | title=_("Bug notification level"), required=True, |
1947 | vocabulary=BugNotificationLevel, |
1948 | @@ -114,19 +124,30 @@ |
1949 | default=BlueprintNotificationLevel.NOTHING, |
1950 | description=_("The volume and type of blueprint notifications " |
1951 | "this subscription will generate.")) |
1952 | - date_created = Datetime( |
1953 | + date_created = exported(Datetime( |
1954 | title=_("The date on which this subscription was created."), |
1955 | - required=False) |
1956 | - date_last_updated = Datetime( |
1957 | + required=False, readonly=True)) |
1958 | + date_last_updated = exported(Datetime( |
1959 | title=_("The date on which this subscription was last updated."), |
1960 | - required=False) |
1961 | + required=False, readonly=True)) |
1962 | |
1963 | - target = Attribute("The structure to which this subscription belongs.") |
1964 | + target = exported(Reference( |
1965 | + schema=Interface, # IStructuralSubscriptionTarget |
1966 | + required=True, readonly=True, |
1967 | + title=_("The structure to which this subscription belongs."))) |
1968 | |
1969 | |
1970 | class IStructuralSubscriptionTarget(Interface): |
1971 | """A Launchpad Structure allowing users to subscribe to it.""" |
1972 | + export_as_webservice_entry() |
1973 | |
1974 | + # We don't really want to expose the level details yet. Only |
1975 | + # BugNotificationLevel.COMMENTS is used at this time. |
1976 | + @call_with( |
1977 | + min_bug_notification_level=BugNotificationLevel.COMMENTS, |
1978 | + min_blueprint_notification_level=BlueprintNotificationLevel.NOTHING) |
1979 | + @operation_returns_collection_of(IStructuralSubscription) |
1980 | + @export_read_operation() |
1981 | def getSubscriptions(min_bug_notification_level, |
1982 | min_blueprint_notification_level): |
1983 | """Return all the subscriptions with the specified levels. |
1984 | @@ -148,11 +169,21 @@ |
1985 | This method is used to create a new `IStructuralSubscription` |
1986 | for the target, with no levels set. |
1987 | |
1988 | - :subscriber: The IPerson who will be subscribed. |
1989 | + :subscriber: The IPerson who will be subscribed. If omitted, |
1990 | + subscribed_by will be used. |
1991 | :subscribed_by: The IPerson creating the subscription. |
1992 | :return: The new subscription. |
1993 | """ |
1994 | |
1995 | + @operation_parameters( |
1996 | + subscriber=Reference( |
1997 | + schema=IPerson, |
1998 | + title=_( |
1999 | + 'Person to subscribe. If omitted, the requesting user will be' |
2000 | + ' subscribed.'), |
2001 | + required=False)) |
2002 | + @call_with(subscribed_by=REQUEST_USER) |
2003 | + @export_factory_operation(IStructuralSubscription, []) |
2004 | def addBugSubscription(subscriber, subscribed_by): |
2005 | """Add a bug subscription for this structure. |
2006 | |
2007 | @@ -160,21 +191,36 @@ |
2008 | for the target with the bug notification level set to |
2009 | COMMENTS, the only level currently in use. |
2010 | |
2011 | - :subscriber: The IPerson who will be subscribed. |
2012 | + :subscriber: The IPerson who will be subscribed. If omitted, |
2013 | + subscribed_by will be used. |
2014 | :subscribed_by: The IPerson creating the subscription. |
2015 | :return: The new bug subscription. |
2016 | """ |
2017 | |
2018 | - def removeBugSubscription(subscriber): |
2019 | + @operation_parameters( |
2020 | + subscriber=Reference( |
2021 | + schema=IPerson, |
2022 | + title=_( |
2023 | + 'Person to unsubscribe. If omitted, the requesting user will ' |
2024 | + 'be unsubscribed.'), |
2025 | + required=False)) |
2026 | + @call_with(unsubscribed_by=REQUEST_USER) |
2027 | + @export_write_operation() |
2028 | + def removeBugSubscription(subscriber, unsubscribed_by): |
2029 | """Remove a subscription to bugs from this structure. |
2030 | |
2031 | If subscription levels for other applications are set, |
2032 | set the subscription's `bug_notification_level` to |
2033 | `NOTHING`, otherwise, destroy the subscription. |
2034 | |
2035 | - :subscriber: The IPerson who will be subscribed. |
2036 | + :subscriber: The IPerson who will be unsubscribed. If omitted, |
2037 | + unsubscribed_by will be used. |
2038 | + :unsubscribed_by: The IPerson removing the subscription. |
2039 | """ |
2040 | |
2041 | + @operation_parameters(person=Reference(schema=IPerson)) |
2042 | + @operation_returns_entry(IStructuralSubscription) |
2043 | + @export_read_operation() |
2044 | def getSubscription(person): |
2045 | """Return the subscription for `person`, if it exists.""" |
2046 | |
2047 | @@ -207,3 +253,9 @@ |
2048 | |
2049 | Raised when an error occurred trying to delete a |
2050 | structural subscription.""" |
2051 | + webservice_error(400) |
2052 | + |
2053 | + |
2054 | +class UserCannotSubscribePerson(Exception): |
2055 | + """User does not have permission to subscribe the person or team.""" |
2056 | + webservice_error(401) |
2057 | |
2058 | === modified file 'lib/canonical/launchpad/javascript/bugs/bugtask-index.js' |
2059 | --- lib/canonical/launchpad/javascript/bugs/bugtask-index.js 2009-08-26 20:50:26 +0000 |
2060 | +++ lib/canonical/launchpad/javascript/bugs/bugtask-index.js 2009-09-02 22:13:06 +0000 |
2061 | @@ -1211,6 +1211,7 @@ |
2062 | var bugtarget_content = Y.get('#bugtarget-picker-' + conf.row_id); |
2063 | var status_content = tr.query('.status-content'); |
2064 | var importance_content = tr.query('.importance-content'); |
2065 | + var assignee_content = Y.get('#assignee-picker-' + conf.row_id); |
2066 | var milestone_content = tr.query('.milestone-content'); |
2067 | |
2068 | if (Y.Lang.isValue(LP.client.cache.bug) && |
2069 | @@ -1357,6 +1358,20 @@ |
2070 | milestone_choice_edit); |
2071 | milestone_choice_edit.render(); |
2072 | } |
2073 | + if (Y.Lang.isValue(assignee_content)) { |
2074 | + var assignee_picker = Y.lp.picker.addPickerPatcher( |
2075 | + 'ValidAssignee', |
2076 | + conf.bugtask_path, |
2077 | + "assignee_link", |
2078 | + assignee_content.get('id'), |
2079 | + true, |
2080 | + true, |
2081 | + {"step_title": "Search for people or teams", |
2082 | + "header": "Change assignee", |
2083 | + "remove_button_text": "Remove asignee", |
2084 | + "null_display_value": "Unassigned"}); |
2085 | + assignee_picker.render() |
2086 | + } |
2087 | }; |
2088 | |
2089 | /** |
2090 | |
2091 | === modified file 'lib/canonical/launchpad/javascript/registry/tests/milestone_table.html' |
2092 | --- lib/canonical/launchpad/javascript/registry/tests/milestone_table.html 2009-06-19 18:31:25 +0000 |
2093 | +++ lib/canonical/launchpad/javascript/registry/tests/milestone_table.html 2009-09-14 15:03:45 +0000 |
2094 | @@ -22,7 +22,7 @@ |
2095 | <table id="series-trunk" class="listing"> |
2096 | <thead> |
2097 | <tr> |
2098 | - <th>Version "Codename"</th> |
2099 | + <th>Version</th> |
2100 | </tr> |
2101 | </thead> |
2102 | <tbody id="milestone-rows"> |
2103 | |
2104 | === modified file 'lib/canonical/launchpad/javascript/soyuz/update_archive_build_statuses.js' |
2105 | --- lib/canonical/launchpad/javascript/soyuz/update_archive_build_statuses.js 2009-06-30 21:06:27 +0000 |
2106 | +++ lib/canonical/launchpad/javascript/soyuz/update_archive_build_statuses.js 2009-08-28 16:06:56 +0000 |
2107 | @@ -19,9 +19,9 @@ |
2108 | var lp_client = new LP.client.Launchpad(); |
2109 | |
2110 | /** |
2111 | - * Configuration for the dynamic update of the build summary table |
2112 | + * Configuration for the dynamic update of the build summary portlet. |
2113 | */ |
2114 | - var build_summary_table_dynamic_update_config = { |
2115 | + var build_summary_portlet_dynamic_update_config = { |
2116 | uri: null, // Note: we have to defer setting the uri until later as |
2117 | // the LP.client.cache is not initialized until the end |
2118 | // of the page. |
2119 | @@ -30,19 +30,19 @@ |
2120 | |
2121 | /** |
2122 | * This function knows how to update an Archive Build Status summary |
2123 | - * table, when given an object of the form: |
2124 | + * when given an object of the form: |
2125 | * {total: 5, failed: 3} |
2126 | * |
2127 | * @config domUpdateFunction |
2128 | */ |
2129 | - domUpdateFunction: function(table_node, data_object){ |
2130 | - var td_nodelist = table_node.getElementsByTagName('td'); |
2131 | + domUpdateFunction: function(portlet_node, data_object){ |
2132 | + var counter_nodelist = portlet_node.queryAll('.build-count'); |
2133 | |
2134 | - // For each node of the table's td elements |
2135 | - td_nodelist.each(function(node){ |
2136 | + // For each node of the counter node in the portlet: |
2137 | + counter_nodelist.each(function(node){ |
2138 | // Check whether the node has a class matching the data name |
2139 | // of the passed in data, and if so, set the innerHTML to |
2140 | - // thecorresponding value. |
2141 | + // the corresponding value. |
2142 | Y.each(data_object, function(data_value, data_name){ |
2143 | if (node.hasClass(data_name)){ |
2144 | previous_value = node.get("innerHTML"); |
2145 | @@ -67,9 +67,9 @@ |
2146 | * |
2147 | * @config stopUpdatesCheckFunction |
2148 | */ |
2149 | - stopUpdatesCheckFunction: function(table_node){ |
2150 | + stopUpdatesCheckFunction: function(portlet_node){ |
2151 | // Stop updating only when there are zero pending builds: |
2152 | - var pending_elem = table_node.query("td.pending"); |
2153 | + var pending_elem = portlet_node.query(".pending"); |
2154 | if (pending_elem === null){ |
2155 | return true; |
2156 | } |
2157 | @@ -83,13 +83,13 @@ |
2158 | * Initialization of the build count summary dynamic table updates. |
2159 | */ |
2160 | Y.on("domready", function(){ |
2161 | - // Grab the Archive build count table and tell it how to |
2162 | + // Grab the Archive build count portlet and tell it how to |
2163 | // update itself: |
2164 | - var table = Y.get('table#build-count-table'); |
2165 | - build_summary_table_dynamic_update_config.uri = |
2166 | + var portlet = Y.get('div#build-status-summary'); |
2167 | + build_summary_portlet_dynamic_update_config.uri = |
2168 | LP.client.cache.context.self_link; |
2169 | - table.plug(Y.lp.DynamicDomUpdater, |
2170 | - build_summary_table_dynamic_update_config); |
2171 | + portlet.plug(Y.lp.DynamicDomUpdater, |
2172 | + build_summary_portlet_dynamic_update_config); |
2173 | }); |
2174 | |
2175 | /** |
2176 | |
2177 | === modified file 'lib/canonical/launchpad/pagetests/basics/demo-and-lpnet.txt' |
2178 | --- lib/canonical/launchpad/pagetests/basics/demo-and-lpnet.txt 2009-08-30 13:31:02 +0000 |
2179 | +++ lib/canonical/launchpad/pagetests/basics/demo-and-lpnet.txt 2009-09-17 15:24:12 +0000 |
2180 | @@ -53,7 +53,8 @@ |
2181 | >>> print extract_text(find_tag_by_id(browser.contents, 'lp-version')) |
2182 | •...Launchpad...) devmode demo site |
2183 | |
2184 | - >>> print extract_text(find_tag_by_id(browser.contents, 'globalheader')) |
2185 | + >>> print extract_text(find_tags_by_class( |
2186 | + ... browser.contents, 'sitemessage')[0]) |
2187 | This is a demo site mmk. File a bug. |
2188 | >>> print browser.getLink(url="http://example.com").text |
2189 | File a bug |
2190 | @@ -68,7 +69,8 @@ |
2191 | >>> print extract_text(find_tag_by_id(browser.contents, 'lp-version')) |
2192 | |...Launchpad...) devmode demo site |
2193 | |
2194 | - >>> print extract_text(find_tag_by_id(browser.contents, 'globalheader')) |
2195 | + >>> print extract_text(find_tags_by_class( |
2196 | + ... browser.contents, 'sitemessage')[0]) |
2197 | This is a demo site mmk. File a bug. |
2198 | >>> print browser.getLink(url="http://example.com").text |
2199 | File a bug |
2200 | @@ -86,16 +88,91 @@ |
2201 | >>> browser.open('http://launchpad.dev/ubuntu') |
2202 | >>> print extract_text(find_tag_by_id(browser.contents, 'lp-version')) |
2203 | •...Launchpad...) devmode |
2204 | - >>> print find_tag_by_id(browser.contents, 'globalheader') |
2205 | - None |
2206 | + >>> len(find_tags_by_class(browser.contents, 'sitemessage')) |
2207 | + 0 |
2208 | + |
2209 | |
2210 | And for a non-3-0 page: |
2211 | |
2212 | >>> browser.open('http://launchpad.dev/') |
2213 | >>> print extract_text(find_tag_by_id(browser.contents, 'lp-version')) |
2214 | |...Launchpad...) devmode |
2215 | - >>> print find_tag_by_id(browser.contents, 'globalheader') |
2216 | - None |
2217 | + >>> len(find_tags_by_class(browser.contents, 'sitemessage')) |
2218 | + 0 |
2219 | + |
2220 | + |
2221 | +== Launchpad Edge == |
2222 | + |
2223 | +Additionally, when a server is running as an edge server, the site-message |
2224 | +is appended with a link to disable edge redirects. |
2225 | + |
2226 | +In addition to this prominent display on the root page, most pages will |
2227 | +also include the disable-redirect link in the site_message - if the |
2228 | +user is a member of the beta group and has not already disabled |
2229 | +the redirects. |
2230 | + |
2231 | + # Now setup an edge site-message config and re-check. |
2232 | + >>> edge_config_data = """ |
2233 | + ... [launchpad] |
2234 | + ... site_message: This is a beta site. |
2235 | + ... is_edge: True |
2236 | + ... """ |
2237 | + >>> config.push('edge_config_data', edge_config_data) |
2238 | + >>> beta_browser = setupBrowser( |
2239 | + ... auth='Basic beta-admin@launchpad.net:test') |
2240 | + >>> beta_browser.open('http://launchpad.dev/ubuntu') |
2241 | + >>> site_message = find_tags_by_class( |
2242 | + ... beta_browser.contents, 'sitemessage')[0] |
2243 | + >>> print extract_text(site_message) |
2244 | + This is a beta site. Disable edge redirect. |
2245 | + >>> print extract_text(site_message.find( |
2246 | + ... 'a', onclick="setBetaRedirect(false)")) |
2247 | + Disable edge redirect. |
2248 | + |
2249 | +The disable-redirect link will not appear for locationless pages, such |
2250 | +as the login page, as the view does not inherit from LaunchpadView and |
2251 | +so cannot support this functionality. |
2252 | + |
2253 | + >>> beta_browser.open('http://launchpad.dev/+login') |
2254 | + >>> print extract_text(find_tags_by_class( |
2255 | + ... beta_browser.contents, 'sitemessage')[0]) |
2256 | + This is a beta site. |
2257 | + |
2258 | +The disable-redirect link will not appear in the site_message when |
2259 | +browsed by non-beta users. |
2260 | + |
2261 | + >>> browser.open('http://launchpad.dev/ubuntu') |
2262 | + >>> print extract_text(find_tags_by_class( |
2263 | + ... browser.contents, 'sitemessage')[0]) |
2264 | + This is a beta site. |
2265 | + |
2266 | +Similarly, once the redirection has been inhibited, the link changes to |
2267 | +enable redirects.. |
2268 | + |
2269 | + # Workaround bug in mechanize where you cannot use the Cookie |
2270 | + # header with the CookieJar |
2271 | + >>> from mechanize._clientcookie import Cookie |
2272 | + >>> cookiejar = ( |
2273 | + ... beta_browser.mech_browser._ua_handlers['_cookies'].cookiejar) |
2274 | + >>> cookiejar.set_cookie( |
2275 | + ... Cookie( |
2276 | + ... version=0, name='inhibit_beta_redirect', value='1', port=None, |
2277 | + ... port_specified=False, domain='.launchpad.dev', |
2278 | + ... domain_specified=True, domain_initial_dot=True, path='/', |
2279 | + ... path_specified=True, secure=False, expires=None, |
2280 | + ... discard=None, comment=None, comment_url=None, rest={})) |
2281 | + >>> beta_browser.open('http://launchpad.dev/ubuntu') |
2282 | + >>> site_message = find_tags_by_class( |
2283 | + ... beta_browser.contents, 'sitemessage')[0] |
2284 | + >>> print extract_text(site_message) |
2285 | + This is a beta site. Enable edge redirect. |
2286 | + >>> print extract_text(site_message.find( |
2287 | + ... 'a', onclick="setBetaRedirect(true)")) |
2288 | + Enable edge redirect. |
2289 | + |
2290 | + |
2291 | + # Remove the specific site-message config data before continuing. |
2292 | + >>> dummy = config.pop('edge_config_data') |
2293 | |
2294 | |
2295 | == Launchpad.net == |
2296 | |
2297 | === modified file 'lib/canonical/launchpad/pagetests/basics/marketing.txt' |
2298 | --- lib/canonical/launchpad/pagetests/basics/marketing.txt 2009-06-12 16:36:02 +0000 |
2299 | +++ lib/canonical/launchpad/pagetests/basics/marketing.txt 2009-09-07 13:15:05 +0000 |
2300 | @@ -153,9 +153,9 @@ |
2301 | === Bugs === |
2302 | |
2303 | >>> browser.open('http://bugs.launchpad.dev') |
2304 | - >>> tour_link = browser.getLink('Take a tour') |
2305 | + >>> tour_link = browser.getLink('take a tour') |
2306 | >>> print tour_link.url |
2307 | - http://launchpad.dev/+tour/bugs |
2308 | + http://bugs.launchpad.dev/+tour |
2309 | >>> tour_link.click() |
2310 | |
2311 | |
2312 | |
2313 | === modified file 'lib/canonical/launchpad/pagetests/basics/notfound-traversals.txt' |
2314 | --- lib/canonical/launchpad/pagetests/basics/notfound-traversals.txt 2009-08-27 07:05:16 +0000 |
2315 | +++ lib/canonical/launchpad/pagetests/basics/notfound-traversals.txt 2009-09-16 22:57:42 +0000 |
2316 | @@ -67,8 +67,6 @@ |
2317 | >>> check_redirect("/+index", host='feeds.launchpad.dev', status=301) |
2318 | >>> check("/+graphics") |
2319 | |
2320 | ->>> check("/translations/+about") |
2321 | - |
2322 | >>> check("/+imports", host='translations.launchpad.dev') |
2323 | >>> check("/+imports/1/+index", auth=True, host='translations.launchpad.dev') |
2324 | >>> check_not_found("/+imports/foo", host='translations.launchpad.dev') |
2325 | @@ -202,10 +200,6 @@ |
2326 | removing this, you must be completely sure that no supported Ubuntu release |
2327 | is still pointing to this old URL (see bug #138090). |
2328 | |
2329 | ->>> check_redirect("/ubuntu/hoary/+source/evolution/+translate", |
2330 | -... host="launchpad.dev", status=301) |
2331 | ->>> check("/ubuntu/hoary/+source/evolution/+translate", |
2332 | -... host='translations.launchpad.dev' ) |
2333 | >>> check_redirect("/ubuntu/hoary/+source/evolution/+translations", status=301) |
2334 | >>> check("/ubuntu/hoary/+source/evolution/+translations", |
2335 | ... host='translations.launchpad.dev') |
2336 | @@ -408,7 +402,6 @@ |
2337 | >>> check("/~name16/+edithomepage", auth=True) |
2338 | >>> check("/~name16/+review", auth=True) |
2339 | >>> check("/~name16/+portlet-emails") |
2340 | ->>> check("/~name16/+portlet-details") |
2341 | >>> check("/~name16/+portlet-team-assignedbugs") |
2342 | >>> check("/~name16/+specworkload") |
2343 | >>> check("/~name16/+imports", host='translations.launchpad.dev') |
2344 | |
2345 | === modified file 'lib/canonical/launchpad/pagetests/basics/page-request-summaries.txt' |
2346 | --- lib/canonical/launchpad/pagetests/basics/page-request-summaries.txt 2009-08-13 15:12:16 +0000 |
2347 | +++ lib/canonical/launchpad/pagetests/basics/page-request-summaries.txt 2009-09-16 22:57:42 +0000 |
2348 | @@ -14,4 +14,4 @@ |
2349 | >>> browser.open('http://launchpad.dev/~mark/') |
2350 | >>> print browser.contents |
2351 | <!DOCTYPE... |
2352 | - ...<!-- at least ... queries issued in ... seconds -->... |
2353 | + ...<!--... At least ... queries issued in ... seconds ...-->... |
2354 | |
2355 | === modified file 'lib/canonical/launchpad/pagetests/basics/user-requested-oops.txt' |
2356 | --- lib/canonical/launchpad/pagetests/basics/user-requested-oops.txt 2009-07-23 00:09:16 +0000 |
2357 | +++ lib/canonical/launchpad/pagetests/basics/user-requested-oops.txt 2009-09-05 06:40:36 +0000 |
2358 | @@ -12,17 +12,18 @@ |
2359 | |
2360 | The OOPS id is put into the comment at the end of the document. |
2361 | |
2362 | - >>> print browser.contents |
2363 | - <!DOCTYPE ... |
2364 | - ... |
2365 | + >>> (page, summary) = browser.contents.split('</html>') |
2366 | + >>> print summary |
2367 | <!-- at least ... queries issued in ... seconds OOPS-... --> |
2368 | <!-- Launchpad ... --> |
2369 | |
2370 | The ++oops++ can be anywhere in the traversal. |
2371 | |
2372 | >>> browser.open("http://launchpad.dev/gnome-terminal/++oops++/trunk") |
2373 | - >>> print browser.contents |
2374 | - <!DOCTYPE ... |
2375 | - ... |
2376 | - <!-- at least ... queries issued in ... seconds OOPS-... --> |
2377 | - <!-- Launchpad ... --> |
2378 | + >>> (page, summary) = browser.contents.split('</html>') |
2379 | + >>> print summary |
2380 | + <!-- ... |
2381 | + At least ... queries issued in ... seconds OOPS-... |
2382 | + <BLANKLINE> |
2383 | + Launchpad ... |
2384 | + --> |
2385 | |
2386 | === removed directory 'lib/canonical/launchpad/pagetests/distroseries' |
2387 | === modified file 'lib/canonical/launchpad/pagetests/oauth/authorize-token.txt' |
2388 | --- lib/canonical/launchpad/pagetests/oauth/authorize-token.txt 2009-07-06 14:31:45 +0000 |
2389 | +++ lib/canonical/launchpad/pagetests/oauth/authorize-token.txt 2009-09-09 20:08:36 +0000 |
2390 | @@ -47,7 +47,6 @@ |
2391 | |
2392 | >>> main_content = find_tag_by_id(browser.contents, 'maincontent') |
2393 | >>> print extract_text(main_content) |
2394 | - Authorize application to access Launchpad on your behalf |
2395 | The application identified as foobar123451432 wants to access Launchpad on |
2396 | your behalf. What level of access do you want to grant? |
2397 | ... |
2398 | @@ -120,7 +119,6 @@ |
2399 | ... % urlencode(params_with_context)) |
2400 | >>> main_content = find_tag_by_id(browser.contents, 'maincontent') |
2401 | >>> print extract_text(main_content) |
2402 | - Authorize application to access Launchpad on your behalf |
2403 | The application...wants to access things related to Mozilla Firefox... |
2404 | |
2405 | A client other than a web browser may request a JSON representation of |
2406 | |
2407 | === modified file 'lib/canonical/launchpad/pagetests/oauth/managing-tokens.txt' |
2408 | --- lib/canonical/launchpad/pagetests/oauth/managing-tokens.txt 2009-07-01 13:16:44 +0000 |
2409 | +++ lib/canonical/launchpad/pagetests/oauth/managing-tokens.txt 2009-09-15 15:42:39 +0000 |
2410 | @@ -19,7 +19,7 @@ |
2411 | >>> my_browser = setupBrowser(auth='Basic salgado@ubuntu.com:zeca') |
2412 | >>> my_browser.open('http://launchpad.dev/~salgado/+oauth-tokens') |
2413 | >>> print my_browser.title |
2414 | - Applications you authorized to access Launchpad |
2415 | + +oauth-tokens : Guilherme Salgado |
2416 | >>> main_content = find_tag_by_id(my_browser.contents, 'maincontent') |
2417 | >>> print extract_text(main_content) |
2418 | Authorized applications |
2419 | @@ -41,7 +41,7 @@ |
2420 | keys stored in hidden <input>s as well as the button to revoke the |
2421 | authorization. |
2422 | |
2423 | - >>> li = main_content.find('li') |
2424 | + >>> li = find_tag_by_id(main_content, 'tokens').find('li') |
2425 | >>> for input in li.find('form').findAll('input'): |
2426 | ... print input['name'], input['value'] |
2427 | consumer_key foobar123451432 |
2428 | @@ -71,7 +71,7 @@ |
2429 | |
2430 | >>> my_browser.getControl('Revoke Authorization', index=1).click() |
2431 | >>> print my_browser.title |
2432 | - Applications you authorized to access Launchpad |
2433 | + +oauth-tokens : Guilherme Salgado |
2434 | >>> for message in get_feedback_messages(my_browser.contents): |
2435 | ... print message |
2436 | Authorization revoked successfully. |
2437 | |
2438 | === modified file 'lib/canonical/launchpad/pagetests/packaging/xx-ubuntu-pkging.txt' |
2439 | --- lib/canonical/launchpad/pagetests/packaging/xx-ubuntu-pkging.txt 2009-08-11 21:34:18 +0000 |
2440 | +++ lib/canonical/launchpad/pagetests/packaging/xx-ubuntu-pkging.txt 2009-09-09 23:16:08 +0000 |
2441 | @@ -124,8 +124,8 @@ |
2442 | |
2443 | >>> user_browser.open('http://launchpad.dev/bzr/trunk/') |
2444 | >>> user_browser.getLink('Link to Ubuntu package').click() |
2445 | - >>> user_browser.title |
2446 | - 'Ubuntu source packaging' |
2447 | + >>> print user_browser.title |
2448 | + +ubuntupkg : Series trunk : Bazaar |
2449 | |
2450 | >>> user_browser.getControl(name='ubuntupkg').value = ( |
2451 | ... "bzr<script>window.alert('XSS')</script>") |
2452 | |
2453 | === modified file 'lib/canonical/launchpad/pagetests/standalone/xx-launchpad-integration.txt' |
2454 | --- lib/canonical/launchpad/pagetests/standalone/xx-launchpad-integration.txt 2006-11-29 13:24:27 +0000 |
2455 | +++ lib/canonical/launchpad/pagetests/standalone/xx-launchpad-integration.txt 2009-09-15 10:01:13 +0000 |
2456 | @@ -9,9 +9,4 @@ |
2457 | >>> 'Help and support' in anon_browser.contents |
2458 | True |
2459 | |
2460 | - >>> anon_browser.open( |
2461 | - ... 'http://launchpad.dev/distros/ubuntu/hoary/+source/evolution/+translate' |
2462 | - ... ) |
2463 | - >>> 'Help translate' in anon_browser.contents |
2464 | - True |
2465 | - |
2466 | + |
2467 | |
2468 | === modified file 'lib/canonical/launchpad/pagetests/standalone/xx-login-without-preferredemail.txt' |
2469 | --- lib/canonical/launchpad/pagetests/standalone/xx-login-without-preferredemail.txt 2009-05-12 01:39:29 +0000 |
2470 | +++ lib/canonical/launchpad/pagetests/standalone/xx-login-without-preferredemail.txt 2009-09-09 23:16:08 +0000 |
2471 | @@ -55,11 +55,8 @@ |
2472 | |
2473 | >>> path = "%s/+validateemail" % base_path |
2474 | >>> browser.open(path) |
2475 | - >>> print '\n' + browser.contents |
2476 | - <BLANKLINE> |
2477 | - ... |
2478 | - ...Confirm e-mail address...martin.pitt@canonical.com... |
2479 | - ... |
2480 | + >>> print browser.title |
2481 | + Confirm e-mail address |
2482 | |
2483 | >>> browser.getControl('Continue').click() |
2484 | >>> browser.url |
2485 | @@ -77,4 +74,3 @@ |
2486 | >>> e = EmailAddressSet().getByEmail(to_addr) |
2487 | >>> e.status.title |
2488 | 'Preferred Email Address' |
2489 | - |
2490 | |
2491 | === modified file 'lib/canonical/launchpad/pagetests/standalone/xx-opstats.txt' |
2492 | --- lib/canonical/launchpad/pagetests/standalone/xx-opstats.txt 2009-02-05 20:46:59 +0000 |
2493 | +++ lib/canonical/launchpad/pagetests/standalone/xx-opstats.txt 2009-09-15 10:55:13 +0000 |
2494 | @@ -46,6 +46,7 @@ |
2495 | 500s: 0 |
2496 | 503s: 0 |
2497 | 5XXs: 0 |
2498 | + 5XXs_b: 0 |
2499 | 6XXs: 0 |
2500 | http requests: 0 |
2501 | requests: 0 |
2502 | @@ -108,6 +109,26 @@ |
2503 | http requests: 1 |
2504 | requests: 1 |
2505 | |
2506 | +We also have a special metric counting server errors returned to known |
2507 | +web browsers (5XXs_b) - in the production environment we care more |
2508 | +about errors returned to people than robots crawling obscure parts of |
2509 | +the site. |
2510 | + |
2511 | + >>> from textwrap import dedent |
2512 | + >>> output = http(dedent("""\ |
2513 | + ... GET /error-test HTTP/1.1 |
2514 | + ... Host: launchpad.dev |
2515 | + ... User-Agent: Mozilla/42.0 |
2516 | + ... """)) |
2517 | + >>> output.getStatus() |
2518 | + 500 |
2519 | + >>> report() |
2520 | + 500s: 1 |
2521 | + 5XXs: 1 |
2522 | + 5XXs_b: 1 |
2523 | + http requests: 1 |
2524 | + requests: 1 |
2525 | + |
2526 | == Number of XML-RPC Faults == |
2527 | |
2528 | >>> try: |
2529 | @@ -123,7 +144,6 @@ |
2530 | |
2531 | == Number of soft timeouts == |
2532 | |
2533 | - >>> from textwrap import dedent |
2534 | >>> from canonical.config import config |
2535 | >>> test_data = dedent(""" |
2536 | ... [database] |
2537 | |
2538 | === added file 'lib/canonical/launchpad/pagetests/webservice/xx-structuralsubscription.txt' |
2539 | --- lib/canonical/launchpad/pagetests/webservice/xx-structuralsubscription.txt 1970-01-01 00:00:00 +0000 |
2540 | +++ lib/canonical/launchpad/pagetests/webservice/xx-structuralsubscription.txt 2009-08-27 04:16:41 +0000 |
2541 | @@ -0,0 +1,151 @@ |
2542 | += Structural Subscriptions = |
2543 | + |
2544 | +Structural subscriptions can be obtained from any target: a project, |
2545 | +project series, project group, distribution, distribution series or |
2546 | +distribution source package. |
2547 | + |
2548 | + >>> login('admin@canonical.com') |
2549 | + >>> eric_db = factory.makePerson(name='eric') |
2550 | + >>> michael_db = factory.makePerson(name='michael') |
2551 | + >>> pythons_db = factory.makeTeam(name='pythons', owner=michael_db) |
2552 | + >>> pythons_db.addMember(eric_db, michael_db) |
2553 | + |
2554 | + >>> fooix_db = factory.makeProduct(name='fooix', owner=eric_db) |
2555 | + >>> fooix01_db = fooix_db.newSeries(eric_db, '0.1', 'Series 0.1') |
2556 | + >>> logout() |
2557 | + |
2558 | +We can list the structural subscriptions on a target using the |
2559 | +getSubscriptions named operation. There are none just yet. |
2560 | + |
2561 | + >>> from lazr.restful.testing.webservice import ( |
2562 | + ... pprint_collection, pprint_entry) |
2563 | + >>> subscriptions = webservice.named_get( |
2564 | + ... '/fooix', 'getSubscriptions').jsonBody() |
2565 | + >>> pprint_collection(subscriptions) |
2566 | + start: None |
2567 | + total_size: 0 |
2568 | + --- |
2569 | + |
2570 | +Now Eric subscribes to Fooix's bug notifications. |
2571 | + |
2572 | + >>> from canonical.launchpad.testing.pages import webservice_for_person |
2573 | + >>> from canonical.launchpad.webapp.interfaces import OAuthPermission |
2574 | + >>> eric_webservice = webservice_for_person( |
2575 | + ... eric_db, permission=OAuthPermission.WRITE_PRIVATE) |
2576 | + |
2577 | + >>> print eric_webservice.named_post( |
2578 | + ... '/fooix', 'addBugSubscription') |
2579 | + HTTP/1.1 201 Created |
2580 | + ... |
2581 | + Location: http://.../fooix/+subscription/eric |
2582 | + ... |
2583 | + |
2584 | + >>> subscriptions = webservice.named_get( |
2585 | + ... '/fooix', 'getSubscriptions').jsonBody() |
2586 | + >>> pprint_collection(subscriptions) |
2587 | + start: 0 |
2588 | + total_size: 1 |
2589 | + --- |
2590 | + date_created: u'...' |
2591 | + date_last_updated: u'...' |
2592 | + resource_type_link: u'http://.../#structural_subscription' |
2593 | + self_link: u'http://.../fooix/+subscription/eric' |
2594 | + subscribed_by_link: u'http://.../~eric' |
2595 | + subscriber_link: u'http://.../~eric' |
2596 | + target_link: u'http://.../fooix' |
2597 | + --- |
2598 | + |
2599 | +He can examine his subscription directly. |
2600 | + |
2601 | + >>> pprint_entry(eric_webservice.named_get( |
2602 | + ... '/fooix', 'getSubscription', |
2603 | + ... person=webservice.getAbsoluteUrl('/~eric')).jsonBody()) |
2604 | + date_created: u'...' |
2605 | + date_last_updated: u'...' |
2606 | + resource_type_link: u'http://.../#structural_subscription' |
2607 | + self_link: u'http://.../fooix/+subscription/eric' |
2608 | + subscribed_by_link: u'http://.../~eric' |
2609 | + subscriber_link: u'http://.../~eric' |
2610 | + target_link: u'http://.../fooix' |
2611 | + |
2612 | +If the subscription doesn't exist, None will be returned. |
2613 | + |
2614 | + >>> print webservice.named_get( |
2615 | + ... '/fooix', 'getSubscription', |
2616 | + ... person=webservice.getAbsoluteUrl('/~michael')).jsonBody() |
2617 | + None |
2618 | + |
2619 | +Eric can remove his subscription through the webservice. |
2620 | + |
2621 | + >>> print eric_webservice.named_post( |
2622 | + ... '/fooix', 'removeBugSubscription') |
2623 | + HTTP/1.1 200 Ok... |
2624 | + |
2625 | + >>> print webservice.named_get( |
2626 | + ... '/fooix', 'getSubscription', |
2627 | + ... person=webservice.getAbsoluteUrl('/~eric')).jsonBody() |
2628 | + None |
2629 | + |
2630 | +Teams can be subscribed by passing in the team as an argument. Eric |
2631 | +tries this. |
2632 | + |
2633 | + >>> print eric_webservice.named_post( |
2634 | + ... '/fooix', 'addBugSubscription', |
2635 | + ... subscriber=webservice.getAbsoluteUrl('/~pythons')) |
2636 | + HTTP/1.1 401 Unauthorized |
2637 | + ... |
2638 | + UserCannotSubscribePerson: eric does not have permission to subscribe pythons. |
2639 | + <BLANKLINE> |
2640 | + |
2641 | +Oops, Eric isn't a team admin. Eric gets Michael to try, since he is an |
2642 | +admin by virtue of his ownership. |
2643 | + |
2644 | + >>> michael_webservice = webservice_for_person( |
2645 | + ... michael_db, permission=OAuthPermission.WRITE_PRIVATE) |
2646 | + |
2647 | + >>> print michael_webservice.named_post( |
2648 | + ... '/fooix', 'addBugSubscription', |
2649 | + ... subscriber=webservice.getAbsoluteUrl('/~pythons')) |
2650 | + HTTP/1.1 201 Created |
2651 | + ... |
2652 | + Location: http://.../fooix/+subscription/pythons |
2653 | + ... |
2654 | + |
2655 | + >>> subscriptions = webservice.named_get( |
2656 | + ... '/fooix', 'getSubscriptions').jsonBody() |
2657 | + >>> pprint_collection(subscriptions) |
2658 | + start: 0 |
2659 | + total_size: 1 |
2660 | + --- |
2661 | + date_created: u'...' |
2662 | + date_last_updated: u'...' |
2663 | + resource_type_link: u'http://.../#structural_subscription' |
2664 | + self_link: u'http://.../fooix/+subscription/pythons' |
2665 | + subscribed_by_link: u'http://.../~michael' |
2666 | + subscriber_link: u'http://.../~pythons' |
2667 | + target_link: u'http://.../fooix' |
2668 | + --- |
2669 | + |
2670 | +Eric can't unsubscribe the team either. |
2671 | + |
2672 | + >>> print eric_webservice.named_post( |
2673 | + ... '/fooix', 'removeBugSubscription', |
2674 | + ... subscriber=webservice.getAbsoluteUrl('/~pythons')) |
2675 | + HTTP/1.1 401 Unauthorized |
2676 | + ... |
2677 | + UserCannotSubscribePerson: eric does not have permission to unsubscribe pythons. |
2678 | + <BLANKLINE> |
2679 | + |
2680 | +Michael can, though. |
2681 | + |
2682 | + >>> print michael_webservice.named_post( |
2683 | + ... '/fooix', 'removeBugSubscription', |
2684 | + ... subscriber=webservice.getAbsoluteUrl('/~pythons')) |
2685 | + HTTP/1.1 200 Ok... |
2686 | + |
2687 | + >>> subscriptions = webservice.named_get( |
2688 | + ... '/fooix', 'getSubscriptions').jsonBody() |
2689 | + >>> pprint_collection(subscriptions) |
2690 | + start: None |
2691 | + total_size: 0 |
2692 | + --- |
2693 | |
2694 | === modified file 'lib/canonical/launchpad/pagetitles.py' |
2695 | --- lib/canonical/launchpad/pagetitles.py 2009-09-01 15:42:27 +0000 |
2696 | +++ lib/canonical/launchpad/pagetitles.py 2009-09-18 09:21:50 +0000 |
2697 | @@ -76,17 +76,6 @@ |
2698 | return self.text % context.displayname |
2699 | |
2700 | |
2701 | -class FilteredTranslationsTitle(SubstitutionHelper): |
2702 | - """Return the formatted string with context's title and view's person.""" |
2703 | - def __call__(self, context, view): |
2704 | - if view.person is not None: |
2705 | - person = view.person.displayname |
2706 | - else: |
2707 | - person = 'unknown' |
2708 | - return self.text % {'title' : context.title, |
2709 | - 'person' : person } |
2710 | - |
2711 | - |
2712 | class ContextId(SubstitutionHelper): |
2713 | """Return the formatted string with context's id.""" |
2714 | def __call__(self, context, view): |
2715 | @@ -139,8 +128,6 @@ |
2716 | |
2717 | archive_edit_dependencies = ContextDisplayName('Edit dependencies for %s') |
2718 | |
2719 | -archive_index = ContextDisplayName('%s') |
2720 | - |
2721 | archive_subscriber_edit = ContextDisplayName('Edit %s') |
2722 | |
2723 | archive_subscribers = ContextDisplayName('Manage access to %s') |
2724 | @@ -178,8 +165,6 @@ |
2725 | |
2726 | bug_branch_add = LaunchbagBugID('Bug #%d - Add branch') |
2727 | |
2728 | -bug_cve = LaunchbagBugID("Bug #%d - Add CVE reference") |
2729 | - |
2730 | bug_edit = ContextBugId('Bug #%d - Edit') |
2731 | |
2732 | bug_edit_confirm = ContextBugId('Bug #%d - Edit confirmation') |
2733 | @@ -198,23 +183,14 @@ |
2734 | |
2735 | bug_nominate_for_series = ViewLabel() |
2736 | |
2737 | -bug_removecve = LaunchbagBugID("Bug #%d - Remove CVE reference") |
2738 | - |
2739 | bug_secrecy = ContextBugId('Bug #%d - Set visibility') |
2740 | |
2741 | bug_subscription = LaunchbagBugID('Bug #%d - Subscription options') |
2742 | |
2743 | -bug_remove_question = LaunchbagBugID( |
2744 | - 'Bug #%d - Convert this question back to a bug') |
2745 | - |
2746 | bugbranch_delete = 'Delete bug branch link' |
2747 | |
2748 | bugbranch_edit = "Edit branch fix status" |
2749 | |
2750 | -def bugcomment_index(context, view): |
2751 | - """Return the page title for a bug comment.""" |
2752 | - return "Bug #%d - Comment #%d" % (context.bug.id, view.comment.index) |
2753 | - |
2754 | buglinktarget_linkbug = 'Link to bug report' |
2755 | |
2756 | buglinktarget_unlinkbugs = 'Remove links to bug reports' |
2757 | @@ -227,23 +203,11 @@ |
2758 | """Return the view's page heading.""" |
2759 | return view.getSearchPageHeading() |
2760 | |
2761 | -bug_listing_expirable = ContextTitle("Bugs that can expire in %s") |
2762 | - |
2763 | def bugnomination_edit(context, view): |
2764 | """Return the title for the page to manage bug nominations.""" |
2765 | return 'Manage nomination for bug #%d in %s' % ( |
2766 | context.bug.id, context.target.bugtargetdisplayname) |
2767 | |
2768 | -def bugwatch_editform(context, view): |
2769 | - """Return the title for the page to edit an external bug watch.""" |
2770 | - return 'Bug #%d - Edit external bug watch (%s in %s)' % ( |
2771 | - context.bug.id, context.remotebug, context.bugtracker.title) |
2772 | - |
2773 | -def bugwatch_comments(context, view): |
2774 | - """Return the title for a page of imported comments for a bug watch.""" |
2775 | - return "Bug #%d - Comments imported from bug watch %s on %s" % ( |
2776 | - context.bug.id, context.remotebug, context.bugtracker.title) |
2777 | - |
2778 | def bugs_assigned(context, view): |
2779 | """Return the page title for the bugs assigned to the logged-in user.""" |
2780 | if view.user: |
2781 | @@ -293,27 +257,6 @@ |
2782 | # bugtask_macros_buglisting contains only macros |
2783 | # bugtasks_index is a redirect |
2784 | |
2785 | -bugtracker_edit = ContextTitle( |
2786 | - smartquote('Change details for "%s" bug tracker')) |
2787 | - |
2788 | -bugtracker_index = ContextTitle(smartquote('Bug tracker "%s"')) |
2789 | - |
2790 | -bugtrackers_add = 'Register an external bug tracker' |
2791 | - |
2792 | -bugtrackers_index = 'Bug trackers registered in Launchpad' |
2793 | - |
2794 | -build_buildlog = ContextTitle('Build log for %s') |
2795 | - |
2796 | -build_changes = ContextTitle('Changes in %s') |
2797 | - |
2798 | -build_index = ContextTitle('%s') |
2799 | - |
2800 | -build_retry = ContextTitle('Retry %s') |
2801 | - |
2802 | -build_rescore = ContextTitle('Rescore %s') |
2803 | - |
2804 | -builders_index = 'Launchpad build farm' |
2805 | - |
2806 | calendar_index = ContextTitle('%s') |
2807 | |
2808 | calendar_event_addform = ContextTitle('Add event to %s') |
2809 | @@ -357,8 +300,6 @@ |
2810 | |
2811 | codeofconduct_admin = 'Administer Codes of Conduct' |
2812 | |
2813 | -codeofconduct_index = ContextTitle('%s') |
2814 | - |
2815 | codeofconduct_list = 'Ubuntu Codes of Conduct' |
2816 | |
2817 | def contact_user(context, view): |
2818 | @@ -380,8 +321,6 @@ |
2819 | |
2820 | distributionmirror_index = ContextTitle('Mirror %s') |
2821 | |
2822 | -distribution_allpackages = ContextTitle('All packages in %s') |
2823 | - |
2824 | distribution_archive_list = ContextTitle('%s Copy Archives') |
2825 | |
2826 | distribution_upstream_bug_report = ContextTitle('Upstream Bug Report for %s') |
2827 | @@ -392,8 +331,6 @@ |
2828 | |
2829 | distribution_mirrors = ContextTitle("Mirrors of %s") |
2830 | |
2831 | -distribution_series = ContextTitle("%s version history") |
2832 | - |
2833 | distribution_translations = ContextDisplayName('Translating %s') |
2834 | |
2835 | distribution_translation_settings = ContextTitle( |
2836 | @@ -405,8 +342,6 @@ |
2837 | |
2838 | distribution_builds = ContextTitle('%s builds') |
2839 | |
2840 | -distribution_ppa_list = ContextTitle('%s Personal Package Archives') |
2841 | - |
2842 | distributionsourcepackage_bugs = ContextTitle('Bugs in %s') |
2843 | |
2844 | distributionsourcepackage_index = ContextTitle('%s') |
2845 | @@ -414,11 +349,6 @@ |
2846 | distributionsourcepackage_publishinghistory = ContextTitle( |
2847 | 'Publishing history of %s') |
2848 | |
2849 | -structural_subscriptions_manage = ContextTitle( |
2850 | - 'Bug subscriptions for %s') |
2851 | - |
2852 | -distributionsourcepackagerelease_index = ContextTitle('%s') |
2853 | - |
2854 | distroarchseries_index = ContextTitle('%s in Launchpad') |
2855 | |
2856 | distroarchseries_builds = ContextTitle('%s builds') |
2857 | @@ -434,33 +364,15 @@ |
2858 | |
2859 | distroseries_cvereport = ContextDisplayName('CVE report for %s') |
2860 | |
2861 | -def distroseries_index(context, view): |
2862 | - """Return the distribution and version page title.""" |
2863 | - return '%s %s in Launchpad' % ( |
2864 | - context.distribution.title, context.version) |
2865 | - |
2866 | def distroseries_language_packs(context, view): |
2867 | return view.page_title |
2868 | |
2869 | -distroseries_packaging = ContextDisplayName('Mapping packages to upstream ' |
2870 | - 'for %s') |
2871 | - |
2872 | -distroseries_search = ContextDisplayName('Search packages in %s') |
2873 | - |
2874 | distroseries_translations = ContextTitle('Translations of %s in Launchpad') |
2875 | |
2876 | -distroseries_builds = ContextTitle('%s builds') |
2877 | - |
2878 | distroseries_queue = ContextTitle('Queue for %s') |
2879 | |
2880 | -distroseriesbinarypackage_index = ContextTitle('%s') |
2881 | - |
2882 | -distroserieslanguage_index = ContextTitle('%s') |
2883 | - |
2884 | distroseriessourcepackagerelease_index = ContextTitle('%s') |
2885 | |
2886 | -edit_bug_supervisor = ContextTitle('Edit bug supervisor for %s') |
2887 | - |
2888 | errorservice_config = 'Configure error log' |
2889 | |
2890 | errorservice_entry = 'Error log entry' |
2891 | @@ -503,18 +415,12 @@ |
2892 | |
2893 | hassprints_sprints = ContextTitle("Events related to %s") |
2894 | |
2895 | -hastranslationimports_index = 'Translation import queue' |
2896 | - |
2897 | hwdb_fingerprint_submissions = ( |
2898 | "Hardware Database submissions for a fingerprint") |
2899 | |
2900 | hwdb_submit_hardware_data = ( |
2901 | 'Submit New Data to the Launchpad Hardware Database') |
2902 | |
2903 | -language_index = ContextDisplayName("%s in Launchpad") |
2904 | - |
2905 | -languageset_index = 'Languages in Launchpad' |
2906 | - |
2907 | # launchpad_debug doesn't need a title. |
2908 | |
2909 | def launchpad_addform(context, view): |
2910 | @@ -538,30 +444,20 @@ |
2911 | |
2912 | # launchpad_js is standard javascript |
2913 | |
2914 | -launchpad_invalidbatchsize = "Invalid Batch Size" |
2915 | - |
2916 | launchpad_legal = 'Launchpad legalese' |
2917 | |
2918 | launchpad_login = 'Log in or register with Launchpad' |
2919 | |
2920 | -launchpad_notfound = 'Error: Page not found' |
2921 | - |
2922 | launchpad_onezerostatus = 'One-Zero Page Template Status' |
2923 | |
2924 | -launchpad_requestexpired = 'Error: Timeout' |
2925 | - |
2926 | def launchpad_search(context, view): |
2927 | """Return the page title corresponding to the user's search.""" |
2928 | return view.page_title |
2929 | |
2930 | -launchpad_translationunavailable = 'Translation page is not available' |
2931 | - |
2932 | launchpad_unexpectedformdata = 'Error: Unexpected form data' |
2933 | |
2934 | launchpad_librarianfailure = "Sorry, you can't do this right now" |
2935 | |
2936 | -launchpad_readonlyfailure = "Sorry, you can't do this right now" |
2937 | - |
2938 | # launchpad_widget_macros doesn't need a title. |
2939 | |
2940 | launchpadstatisticset_index = 'Launchpad statistics' |
2941 | @@ -579,31 +475,8 @@ |
2942 | |
2943 | loginservice_login = 'Launchpad Login Service' |
2944 | |
2945 | -logintoken_claimprofile = 'Claim Launchpad profile' |
2946 | - |
2947 | -logintoken_claimteam = 'Claim Launchpad team' |
2948 | - |
2949 | -# This page will always redirect the user to another page specific to the |
2950 | -# login token in question, except when the token has been consumed already, in |
2951 | -# which case the user will see the title. |
2952 | -logintoken_index = 'You have already done this' |
2953 | - |
2954 | -logintoken_mergepeople = 'Merge Launchpad accounts' |
2955 | - |
2956 | -logintoken_newaccount = 'Create a new Launchpad account' |
2957 | - |
2958 | -logintoken_resetpassword = 'Forgotten your password?' |
2959 | - |
2960 | loginservice_standalone_login = loginservice_login |
2961 | |
2962 | -logintoken_validateemail = 'Confirm e-mail address' |
2963 | - |
2964 | -logintoken_validategpg = 'Confirm OpenPGP key' |
2965 | - |
2966 | -logintoken_validatesignonlygpg = 'Confirm sign-only OpenPGP key' |
2967 | - |
2968 | -logintoken_validateteamemail = 'Confirm e-mail address' |
2969 | - |
2970 | # main_template has the code to insert one of these titles. |
2971 | |
2972 | malone_about = 'About Launchpad Bugs' |
2973 | @@ -644,8 +517,6 @@ |
2974 | """Return the view's pagetitle.""" |
2975 | return view.pagetitle |
2976 | |
2977 | -marketing_translations_about = "About Translations" |
2978 | - |
2979 | marketing_translations_faq = "FAQs about Translations" |
2980 | |
2981 | mentoringofferset_success = "Successful mentorships over the past year." |
2982 | @@ -658,8 +529,6 @@ |
2983 | |
2984 | milestone_add = ContextTitle('Add new milestone for %s') |
2985 | |
2986 | -milestone_index = ContextTitle('%s') |
2987 | - |
2988 | milestone_edit = ContextTitle('Edit %s') |
2989 | |
2990 | milestone_delete = ContextTitle('Delete %s') |
2991 | @@ -689,8 +558,6 @@ |
2992 | """Return the page title to change the driver.""" |
2993 | return view.page_title |
2994 | |
2995 | -object_milestones = ContextTitle(smartquote("%s's milestones")) |
2996 | - |
2997 | # object_pots is a fragment. |
2998 | |
2999 | object_translations = ContextDisplayName('Translation templates for %s') |
3000 | @@ -723,8 +590,6 @@ |
3001 | |
3002 | openidrpconfigset_index = 'OpenID Relying Party Configurations' |
3003 | |
3004 | -official_bug_target_manage_tags = 'Manage Official Bug Tags' |
3005 | - |
3006 | def package_bugs(context, view): |
3007 | """Return the page title bug in a package.""" |
3008 | return 'Bugs in %s' % context.name |
3009 | @@ -741,8 +606,6 @@ |
3010 | |
3011 | people_requestmerge_multiple = 'Merge Launchpad accounts' |
3012 | |
3013 | -active_reviews = ContextDisplayName('Pending proposals for %s') |
3014 | - |
3015 | person_archive_subscription = ContextDisplayName('%s') |
3016 | |
3017 | person_archive_subscriptions = 'Private PPA access' |
3018 | @@ -750,33 +613,6 @@ |
3019 | person_answer_contact_for = ContextDisplayName( |
3020 | 'Projects for which %s is an answer contact') |
3021 | |
3022 | -person_changepassword = 'Change your password' |
3023 | - |
3024 | -person_claim = 'Claim account' |
3025 | - |
3026 | -person_claim_team = 'Claim team' |
3027 | - |
3028 | -person_deactivate_account = 'Deactivate your Launchpad account' |
3029 | - |
3030 | -person_codesofconduct = ContextDisplayName( |
3031 | - smartquote("%s's code of conduct signatures")) |
3032 | - |
3033 | -person_edit = ContextDisplayName(smartquote("%s's details")) |
3034 | - |
3035 | -person_editemails = ContextDisplayName(smartquote("%s's e-mail addresses")) |
3036 | - |
3037 | -person_editlocation = ContextDisplayName(smartquote("%s's usual location")) |
3038 | - |
3039 | -person_editpgpkeys = ContextDisplayName(smartquote("%s's OpenPGP keys")) |
3040 | - |
3041 | -person_editircnicknames = ContextDisplayName(smartquote("%s's IRC nicknames")) |
3042 | - |
3043 | -person_editjabberids = ContextDisplayName(smartquote("%s's Jabber IDs")) |
3044 | - |
3045 | -person_editsshkeys = ContextDisplayName(smartquote("%s's SSH keys")) |
3046 | - |
3047 | -person_editwikinames = ContextDisplayName(smartquote("%s's wiki names")) |
3048 | - |
3049 | # person_foaf is an rdf file |
3050 | |
3051 | person_hwdb_submissions = ContextDisplayName( |
3052 | @@ -784,69 +620,27 @@ |
3053 | |
3054 | person_images = ContextDisplayName(smartquote("%s's hackergotchi and emblem")) |
3055 | |
3056 | -def person_index(context, view): |
3057 | - """Return the page title to the person index page.""" |
3058 | - if context.is_valid_person_or_team: |
3059 | - return '%s in Launchpad' % context.displayname |
3060 | - else: |
3061 | - return "%s does not use Launchpad" % context.displayname |
3062 | - |
3063 | person_karma = ContextDisplayName(smartquote("%s's karma in Launchpad")) |
3064 | |
3065 | -person_maintained_packages = ContextDisplayName('Software maintained by %s') |
3066 | - |
3067 | person_mentoringoffers = ContextTitle('Mentoring offered by %s') |
3068 | |
3069 | def person_mergeproposals(context, view): |
3070 | """Return the view's heading.""" |
3071 | return view.heading |
3072 | |
3073 | -person_oauth_tokens = "Applications you authorized to access Launchpad" |
3074 | - |
3075 | person_packagebugs = ContextDisplayName("%s's package bug reports") |
3076 | |
3077 | person_packagebugs_overview = person_packagebugs |
3078 | |
3079 | person_packagebugs_search = person_packagebugs |
3080 | |
3081 | -person_participation = ContextTitle("Team participation by %s") |
3082 | - |
3083 | -person_ppa_packages = ContextDisplayName('PPA packages related to %s') |
3084 | - |
3085 | -person_related_projects = ContextDisplayName('Projects related to %s') |
3086 | - |
3087 | -person_related_software = ContextDisplayName('Software related to %s') |
3088 | - |
3089 | -person_review = ContextDisplayName("Review %s") |
3090 | - |
3091 | person_specfeedback = ContextDisplayName('Feature feedback requests for %s') |
3092 | |
3093 | person_specworkload = ContextDisplayName('Blueprint workload for %s') |
3094 | |
3095 | -person_translations = ContextDisplayName('Translations related to %s') |
3096 | - |
3097 | -person_translations_relicensing = "Translations licensing" |
3098 | - |
3099 | person_translations_to_review = ContextDisplayName( |
3100 | 'Translations for review by %s') |
3101 | |
3102 | -person_teamhierarchy = ContextDisplayName('Team hierarchy for %s') |
3103 | - |
3104 | -person_uploaded_packages = ContextDisplayName('Software uploaded by %s') |
3105 | - |
3106 | -person_vouchers = ContextDisplayName( |
3107 | - 'Commercial subscription vouchers for %s') |
3108 | - |
3109 | -pofile_filter = FilteredTranslationsTitle( |
3110 | - smartquote('Translations by %(person)s in "%(title)s"')) |
3111 | - |
3112 | -pofile_index = ContextTitle(smartquote('Translation overview for "%s"')) |
3113 | - |
3114 | -def pofile_translate(context, view): |
3115 | - """Return the page to translate a template into a language.""" |
3116 | - return 'Translating %s into %s' % ( |
3117 | - context.potemplate.displayname, context.language.englishname) |
3118 | - |
3119 | # portlet_* are portlets |
3120 | |
3121 | poll_edit = ContextTitle(smartquote('Edit poll "%s"')) |
3122 | @@ -869,17 +663,12 @@ |
3123 | |
3124 | poll_vote_simple = ContextTitle(smartquote('Vote in poll "%s"')) |
3125 | |
3126 | -potemplate_index = ContextTitle(smartquote('Translation status for "%s"')) |
3127 | - |
3128 | product_admin = ContextTitle('Administer %s in Launchpad') |
3129 | |
3130 | product_bugs = ContextDisplayName('Bugs in %s') |
3131 | |
3132 | product_code_index = ContextDisplayName("Bazaar branches of %s") |
3133 | |
3134 | -product_distros = ContextDisplayName( |
3135 | - '%s packages: Comparison of distributions') |
3136 | - |
3137 | product_cvereport = ContextTitle('CVE reports for %s') |
3138 | |
3139 | product_edit = 'Change project details' |
3140 | @@ -893,57 +682,25 @@ |
3141 | """Return the view's heading.""" |
3142 | return view.heading |
3143 | |
3144 | -def product_new(context, view): |
3145 | - """Return the view's heading.""" |
3146 | - return view.heading |
3147 | - |
3148 | product_new_guided = 'Before you register your project...' |
3149 | |
3150 | -product_packages = ContextDisplayName('%s packages in Launchpad') |
3151 | - |
3152 | product_purchase_subscription = ContextDisplayName( |
3153 | 'Purchase Subscription for %s') |
3154 | |
3155 | product_review_license = ContextTitle('Review %s') |
3156 | |
3157 | -product_series = ContextDisplayName('%s timeline') |
3158 | - |
3159 | product_timeline = ContextTitle('Timeline Diagram for %s') |
3160 | |
3161 | product_translations = ContextTitle('Translations of %s in Launchpad') |
3162 | |
3163 | -productrelease_add = ContextDisplayName('Publish the release of %s') |
3164 | - |
3165 | -productrelease_add_from_series = productrelease_add |
3166 | - |
3167 | -productrelease_delete = ContextTitle('Delete %s in Launchpad') |
3168 | - |
3169 | -productrelease_file_add = ContextDisplayName('Add a file to %s') |
3170 | - |
3171 | productrelease_admin = ContextTitle('Administer %s in Launchpad') |
3172 | |
3173 | -productrelease_edit = ContextDisplayName('Edit details of %s in Launchpad') |
3174 | - |
3175 | productrelease_index = ContextDisplayName('%s in Launchpad') |
3176 | |
3177 | -products_review_licenses = 'Review projects' |
3178 | - |
3179 | -productserieslanguage_index = ContextTitle('%s') |
3180 | - |
3181 | -productseries_index = ContextTitle('%s') |
3182 | - |
3183 | -productseries_packaging = ContextDisplayName( |
3184 | - 'Packaging of %s in distributions') |
3185 | - |
3186 | productseries_translations = ContextTitle('Translations overview for %s') |
3187 | |
3188 | productseries_translations_settings = 'Settings for translations' |
3189 | |
3190 | -productseries_translations_bzr_import = ( |
3191 | - 'Request translations import from Bazaar branch') |
3192 | - |
3193 | -project_add = 'Register a project group with Launchpad' |
3194 | - |
3195 | project_index = ContextTitle('%s in Launchpad') |
3196 | |
3197 | project_bugs = ContextTitle('Bugs in %s') |
3198 | @@ -1049,22 +806,10 @@ |
3199 | |
3200 | sourcepackage_builds = ContextTitle('Builds for %s') |
3201 | |
3202 | -sourcepackage_translate = ContextTitle('Help translate %s') |
3203 | - |
3204 | sourcepackage_changelog = 'Source package changelog' |
3205 | |
3206 | sourcepackage_filebug = ContextTitle("Report a bug about %s") |
3207 | |
3208 | -sourcepackage_gethelp = ContextTitle('Help and support options for %s') |
3209 | - |
3210 | -sourcepackage_packaging = ContextTitle('%s upstream links') |
3211 | - |
3212 | -def sourcepackage_index(context, view): |
3213 | - """Return the page title for a source package in a distroseries.""" |
3214 | - return '%s source packages' % context.distroseries.title |
3215 | - |
3216 | -sourcepackage_translate = ContextTitle('Help translate %s') |
3217 | - |
3218 | sourcepackagenames_index = 'Source package name set' |
3219 | |
3220 | sourcepackagerelease_index = ContextTitle('Source package %s') |
3221 | @@ -1135,8 +880,6 @@ |
3222 | """Return the page title for subscribing to a specification.""" |
3223 | return "Subscription of %s" % context.person.displayname |
3224 | |
3225 | -specificationtarget_documentation = ContextTitle('Documentation for %s') |
3226 | - |
3227 | specificationtarget_index = ContextTitle('Blueprint listing for %s') |
3228 | |
3229 | def specificationtarget_specs(context, view): |
3230 | @@ -1173,48 +916,8 @@ |
3231 | |
3232 | standardshipitrequest_edit = 'Edit standard option' |
3233 | |
3234 | -team_addmember = ContextBrowsername('Add members to %s') |
3235 | - |
3236 | -team_add_my_teams = 'Propose/add one of your teams to another one' |
3237 | - |
3238 | -team_editproposed = ContextBrowsername('Proposed members of %s') |
3239 | - |
3240 | team_index = ContextBrowsername('%s in Launchpad') |
3241 | |
3242 | -team_invitations = ContextBrowsername("Invitations sent to %s") |
3243 | - |
3244 | -team_join = ContextBrowsername('Join %s') |
3245 | - |
3246 | -team_leave = ContextBrowsername('Leave %s') |
3247 | - |
3248 | -team_mailinglist = 'Configure mailing list' |
3249 | - |
3250 | -team_mailinglist_moderate = 'Moderate mailing list' |
3251 | - |
3252 | -team_mailinglist_subscribers = ContextBrowsername( |
3253 | - 'Mailing list subscribers for the %s team') |
3254 | - |
3255 | -team_map = ContextBrowsername('Map of %s participants') |
3256 | - |
3257 | -team_members = ContextBrowsername(smartquote('"%s" members')) |
3258 | - |
3259 | -team_mugshots = ContextBrowsername(smartquote('Mugshots in the "%s" team')) |
3260 | - |
3261 | -def teammembership_index(context, view): |
3262 | - """Return the page title to the persons status in a team.""" |
3263 | - return smartquote("%s's membership status in %s") % ( |
3264 | - context.person.displayname, context.team.displayname) |
3265 | - |
3266 | -def teammembership_invitation(context, view): |
3267 | - """Return the page title to invite a person to become a team member.""" |
3268 | - return "Make %s a member of %s" % ( |
3269 | - context.person.displayname, context.team.displayname) |
3270 | - |
3271 | -def teammembership_self_renewal(context, view): |
3272 | - """Return the page title renew membership in a team.""" |
3273 | - return "Renew membership of %s in %s" % ( |
3274 | - context.person.displayname, context.team.displayname) |
3275 | - |
3276 | team_mentoringoffers = ContextTitle('Mentoring available for newcomers to %s') |
3277 | |
3278 | team_newpoll = ContextTitle('New poll for team %s') |
3279 | @@ -1235,30 +938,6 @@ |
3280 | |
3281 | token_authorized = 'Almost finished ...' |
3282 | |
3283 | -translationgroup_index = ContextTitle( |
3284 | - smartquote('"%s" Launchpad translation group')) |
3285 | - |
3286 | -translationgroup_appoint = ContextTitle( |
3287 | - smartquote('Appoint a new translator to "%s"')) |
3288 | - |
3289 | -translationgroup_edit = ContextTitle(smartquote( |
3290 | - 'Edit "%s" translation group details')) |
3291 | - |
3292 | -translationgroup_reassignment = ContextTitle(smartquote( |
3293 | - 'Change the owner of "%s" translation group')) |
3294 | - |
3295 | -translationgroups_index = 'Launchpad translation groups' |
3296 | - |
3297 | translationimportqueueentry_index = 'Translation import queue entry' |
3298 | |
3299 | -translationimportqueue_index = 'Translation import queue' |
3300 | - |
3301 | -translationimportqueue_blocked = 'Translation import queue - Blocked' |
3302 | - |
3303 | -def translationmessage_translate(context, view): |
3304 | - """Return the page to translate a template into a language per message.""" |
3305 | - return 'Translating %s into %s' % ( |
3306 | - context.pofile.potemplate.displayname, |
3307 | - context.pofile.language.englishname) |
3308 | - |
3309 | unauthorized = 'Error: Not authorized' |
3310 | |
3311 | === modified file 'lib/canonical/launchpad/scripts/hardware-1_0.rng' |
3312 | --- lib/canonical/launchpad/scripts/hardware-1_0.rng 2009-02-24 17:43:37 +0000 |
3313 | +++ lib/canonical/launchpad/scripts/hardware-1_0.rng 2009-09-14 09:16:45 +0000 |
3314 | @@ -98,40 +98,64 @@ |
3315 | </element> |
3316 | </zeroOrMore> |
3317 | </element> |
3318 | + <optional> |
3319 | + <element name="kernel-release"> |
3320 | + <attribute name="value"> |
3321 | + <text/> |
3322 | + </attribute> |
3323 | + </element> |
3324 | + </optional> |
3325 | </interleave> |
3326 | </define> |
3327 | |
3328 | <define name="hardwareSection"> |
3329 | <interleave> |
3330 | - <element name="hal"> |
3331 | - <attribute name="version"> |
3332 | - <text/> |
3333 | - </attribute> |
3334 | - <oneOrMore> |
3335 | - <element name="device"> |
3336 | - <attribute name="id"> |
3337 | - <data type="integer"> |
3338 | - <except> |
3339 | - <value/> |
3340 | - </except> |
3341 | - </data> |
3342 | - </attribute> |
3343 | - <attribute name="udi"> |
3344 | - <text/> |
3345 | - </attribute> |
3346 | - <optional> |
3347 | - <attribute name="parent"> |
3348 | - <data type="integer"/> |
3349 | - </attribute> |
3350 | - </optional> |
3351 | - <!-- XXX: Abel Deuring 2007-12-07: |
3352 | - specify a set of required properties? --> |
3353 | - <oneOrMore> |
3354 | - <ref name="property"/> |
3355 | - </oneOrMore> |
3356 | - </element> |
3357 | - </oneOrMore> |
3358 | - </element> |
3359 | + <choice> |
3360 | + <element name="hal"> |
3361 | + <attribute name="version"> |
3362 | + <text/> |
3363 | + </attribute> |
3364 | + <oneOrMore> |
3365 | + <element name="device"> |
3366 | + <attribute name="id"> |
3367 | + <data type="integer"> |
3368 | + <except> |
3369 | + <value/> |
3370 | + </except> |
3371 | + </data> |
3372 | + </attribute> |
3373 | + <attribute name="udi"> |
3374 | + <text/> |
3375 | + </attribute> |
3376 | + <optional> |
3377 | + <attribute name="parent"> |
3378 | + <data type="integer"/> |
3379 | + </attribute> |
3380 | + </optional> |
3381 | + <!-- XXX: Abel Deuring 2007-12-07: |
3382 | + specify a set of required properties? --> |
3383 | + <oneOrMore> |
3384 | + <ref name="property"/> |
3385 | + </oneOrMore> |
3386 | + </element> |
3387 | + </oneOrMore> |
3388 | + </element> |
3389 | + <group> |
3390 | + <interleave> |
3391 | + <element name="udev"> |
3392 | + <text/> |
3393 | + </element> |
3394 | + <element name="dmi"> |
3395 | + <text/> |
3396 | + </element> |
3397 | + <element name="sysfs-attributes"> |
3398 | + <zeroOrMore> |
3399 | + <text/> |
3400 | + </zeroOrMore> |
3401 | + </element> |
3402 | + </interleave> |
3403 | + </group> |
3404 | + </choice> |
3405 | <element name="processors"> |
3406 | <oneOrMore> |
3407 | <element name="processor"> |
3408 | @@ -156,11 +180,7 @@ |
3409 | <zeroOrMore> |
3410 | <element name="alias"> |
3411 | <attribute name="target"> |
3412 | - <data type="integer"> |
3413 | - <except> |
3414 | - <value/> |
3415 | - </except> |
3416 | - </data> |
3417 | + <text/> |
3418 | </attribute> |
3419 | <interleave> |
3420 | <element name="vendor"> |
3421 | @@ -292,11 +312,7 @@ |
3422 | <zeroOrMore> |
3423 | <element name="target"> |
3424 | <attribute name="id"> |
3425 | - <data type="integer"> |
3426 | - <except> |
3427 | - <value/> |
3428 | - </except> |
3429 | - </data> |
3430 | + <text/> |
3431 | </attribute> |
3432 | <interleave> |
3433 | <zeroOrMore> |
3434 | |
3435 | === modified file 'lib/canonical/launchpad/scripts/hwdbsubmissions.py' |
3436 | --- lib/canonical/launchpad/scripts/hwdbsubmissions.py 2009-06-25 05:30:52 +0000 |
3437 | +++ lib/canonical/launchpad/scripts/hwdbsubmissions.py 2009-09-15 17:05:32 +0000 |
3438 | @@ -16,7 +16,10 @@ |
3439 | |
3440 | import bz2 |
3441 | from cStringIO import StringIO |
3442 | -import cElementTree as etree |
3443 | +try: |
3444 | + import xml.etree.cElementTree as etree |
3445 | +except ImportError: |
3446 | + import cElementTree as etree |
3447 | from datetime import datetime, timedelta |
3448 | from logging import getLogger |
3449 | import os |
3450 | @@ -437,16 +440,111 @@ |
3451 | aliases.append(alias) |
3452 | return aliases |
3453 | |
3454 | - _parse_hardware_section = { |
3455 | - 'hal': _parseHAL, |
3456 | - 'processors': _parseProcessors, |
3457 | - 'aliases': _parseAliases} |
3458 | + def _parseUdev(self, udev_node): |
3459 | + """Parse the <udev> node. |
3460 | + |
3461 | + :return: A list of dictionaries, where each dictionary |
3462 | + describes a udev device. |
3463 | + |
3464 | + The <udev> node contains the output produced by |
3465 | + "udevadm info --export-db". Each entry of the dictionaries |
3466 | + represents the data of the key:value pairs as they appear |
3467 | + in this data. The value of d['S'] is a list of strings, |
3468 | + the value s['E'] is a dictionary containing the key=value |
3469 | + pairs of the "E:" lines. |
3470 | + """ |
3471 | + # We get the plain text as produced by "udevadm info --export-db" |
3472 | + # This data looks like: |
3473 | + # |
3474 | + # P: /devices/LNXSYSTM:00 |
3475 | + # E: UDEV_LOG=3 |
3476 | + # E: DEVPATH=/devices/LNXSYSTM:00 |
3477 | + # E: MODALIAS=acpi:LNXSYSTM: |
3478 | + # |
3479 | + # P: /devices/LNXSYSTM:00/ACPI_CPU:00 |
3480 | + # E: UDEV_LOG=3 |
3481 | + # E: DEVPATH=/devices/LNXSYSTM:00/ACPI_CPU:00 |
3482 | + # E: DRIVER=processor |
3483 | + # E: MODALIAS=acpi:ACPI_CPU: |
3484 | + # |
3485 | + # Data for different devices is separated by empty lines. |
3486 | + # Each line for a device consists of key:value pairs. |
3487 | + # The following keys are defined: |
3488 | + # |
3489 | + # A: udev_device_get_num_fake_partitions() |
3490 | + # E: udev_device_get_properties_list_entry() |
3491 | + # L: the device link priority (udev_device_get_devlink_priority()) |
3492 | + # N: the device node file name (udev_device_get_devnode()) |
3493 | + # P: the device path (udev_device_get_devpath()) |
3494 | + # R: udev_device_get_ignore_remove() |
3495 | + # S: udev_get_dev_path() |
3496 | + # W: udev_device_get_watch_handle() |
3497 | + # |
3498 | + # The key P is always present; the keys A, L, N, R, W appear at |
3499 | + # most once per device; the keys E and S may appear more than |
3500 | + # once. |
3501 | + # The values of the E records have the format "key=value" |
3502 | + # |
3503 | + # See also the libudev reference manual: |
3504 | + # http://www.kernel.org/pub/linux/utils/kernel/hotplug/libudev/ |
3505 | + # and the udev file udevadm-info.c, function print_record() |
3506 | + |
3507 | + udev_data = udev_node.text.split('\n') |
3508 | + devices = [] |
3509 | + device = None |
3510 | + line_number = 0 |
3511 | + |
3512 | + for line_number, line in enumerate(udev_data): |
3513 | + if len(line) == 0: |
3514 | + device = None |
3515 | + continue |
3516 | + record = line.split(':', 1) |
3517 | + if len(record) != 2: |
3518 | + self._logError( |
3519 | + 'Line %i in <udev>: No valid key:value data: %r' |
3520 | + % (line_number, line), |
3521 | + self.submission_key) |
3522 | + return None |
3523 | + |
3524 | + key, value = record |
3525 | + if device is None: |
3526 | + device = { |
3527 | + 'E': {}, |
3528 | + 'S': [], |
3529 | + } |
3530 | + devices.append(device) |
3531 | + # Some attribute lines have a space character after the |
3532 | + # ':', others don't have it (see udevadm-info.c). |
3533 | + value = value.lstrip() |
3534 | + |
3535 | + if key == 'E': |
3536 | + property_data = value.split('=', 1) |
3537 | + if len(property_data) != 2: |
3538 | + self._logError( |
3539 | + 'Line %i in <udev>: Property without valid key=value ' |
3540 | + 'data: %r' % (line_number, line), |
3541 | + self.submission_key) |
3542 | + return None |
3543 | + property_key, property_value = property_data |
3544 | + device['E'][property_key] = property_value |
3545 | + elif key == 'S': |
3546 | + device['S'].append(value) |
3547 | + else: |
3548 | + if key in device: |
3549 | + self._logWarning( |
3550 | + 'Line %i in <udev>: Duplicate attribute key: %r' |
3551 | + % (line_number, line), |
3552 | + self.submission_key) |
3553 | + device[key] = value |
3554 | + return devices |
3555 | |
3556 | def _setHardwareSectionParsers(self): |
3557 | self._parse_hardware_section = { |
3558 | 'hal': self._parseHAL, |
3559 | 'processors': self._parseProcessors, |
3560 | - 'aliases': self._parseAliases} |
3561 | + 'aliases': self._parseAliases, |
3562 | + 'udev': self._parseUdev, |
3563 | + } |
3564 | |
3565 | def _parseHardware(self, hardware_node): |
3566 | """Parse the <hardware> part of a submission. |
3567 | |
3568 | === modified file 'lib/canonical/launchpad/scripts/sftracker.py' |
3569 | --- lib/canonical/launchpad/scripts/sftracker.py 2009-06-25 05:30:52 +0000 |
3570 | +++ lib/canonical/launchpad/scripts/sftracker.py 2009-09-04 10:43:39 +0000 |
3571 | @@ -34,7 +34,7 @@ |
3572 | |
3573 | # use cElementTree if it is available ... |
3574 | try: |
3575 | - import xml.elementtree.cElementTree as ET |
3576 | + import xml.etree.cElementTree as ET |
3577 | except ImportError: |
3578 | try: |
3579 | import cElementTree as ET |
3580 | |
3581 | === added file 'lib/canonical/launchpad/scripts/tests/hardwaretest-udev.xml' |
3582 | --- lib/canonical/launchpad/scripts/tests/hardwaretest-udev.xml 1970-01-01 00:00:00 +0000 |
3583 | +++ lib/canonical/launchpad/scripts/tests/hardwaretest-udev.xml 2009-09-14 09:16:45 +0000 |
3584 | @@ -0,0 +1,453 @@ |
3585 | +<?xml version="1.0" ?> |
3586 | +<system version="1.0"> |
3587 | + |
3588 | + <!-- summary: generic information about the submission --> |
3589 | + <summary> |
3590 | + |
3591 | + <!-- live_cd: Was this submission made on a system running an Ubuntu Live |
3592 | + CD or on a regular Ubuntu/Linux installation? |
3593 | + --> |
3594 | + <live_cd value="False"/> |
3595 | + |
3596 | + <!-- system_id: A hash of the "system identifier". This value is intended |
3597 | + to identify the tested computer model; the value should |
3598 | + be derived from the properties |
3599 | + system.product, system.vendor of the HAL UDI |
3600 | + /org/freedesktop/Hal/devices/computer. |
3601 | + --> |
3602 | + <system_id value="f982bb1ab536469cebfd6eaadcea0ffc"/> |
3603 | + |
3604 | + <!-- distribution, distroseries: These values are retrieved from |
3605 | + /etc/lsb-release, parameters DISTRIB_ID and DISTRIB_RELEASE. |
3606 | + --> |
3607 | + <distribution value="Ubuntu"/> |
3608 | + <distroseries value="7.04"/> |
3609 | + |
3610 | + <!-- architecture: The processor architecture of the operating system. |
3611 | + --> |
3612 | + <architecture value="amd64"/> |
3613 | + |
3614 | + <!-- private: If False, this submission is publicly accessible from |
3615 | + Launchpad, else it is only accesible by the submitter, by |
3616 | + Launchpad administrators and by scripts running with |
3617 | + administrator rights. Submissions marked "private" should |
3618 | + only be used to gather statistical data. |
3619 | + --> |
3620 | + <private value="False"/> |
3621 | + |
3622 | + <!-- contactable: If True, the owner agrees to be contacted by other |
3623 | + persons about devices which appear in his submission. |
3624 | + Example of a use case: Developers can ask device owners |
3625 | + to perform tests. |
3626 | + --> |
3627 | + <contactable value="False"/> |
3628 | + |
3629 | + <!-- date_created: Date and time (UTC) of the submission. |
3630 | + --> |
3631 | + <date_created value="2007-09-28T16:09:20.126842"/> |
3632 | + |
3633 | + <!-- client: The name and version of the program that created the |
3634 | + submission data. |
3635 | + --> |
3636 | + <client name="hwtest" version="0.9"> |
3637 | + |
3638 | + <!-- plugin: name and version of a plugin used by the client. |
3639 | + This tag may appear more than once. |
3640 | + --> |
3641 | + <plugin name="architecture_info" version="1.1"/> |
3642 | + <plugin name="find_network_controllers" version="2.34"/> |
3643 | + <plugin name="internet_ping" version="1.1"/> |
3644 | + <plugin name="harddisk_speed" version="0.7"/> |
3645 | + </client> |
3646 | + |
3647 | + <!-- The kernel name and version, as shown by "uname -r" |
3648 | + --> |
3649 | + <kernel-release value="2.6.28-14-generic"/> |
3650 | + </summary> |
3651 | + |
3652 | + <!-- hardware: data about the hardware the submission was made on. |
3653 | + --> |
3654 | + <hardware> |
3655 | + |
3656 | + <!-- udev: The output of running "udevadm info - -export-db" --> |
3657 | + |
3658 | + <udev>P: /devices/LNXSYSTM:00 |
3659 | +E: UDEV_LOG=3 |
3660 | +E: DEVPATH=/devices/LNXSYSTM:00 |
3661 | +E: MODALIAS=acpi:LNXSYSTM: |
3662 | + |
3663 | +P: /devices/pci0000:00/0000:00:1a.0 |
3664 | +E: UDEV_LOG=3 |
3665 | +E: DEVPATH=/devices/pci0000:00/0000:00:1a.0 |
3666 | +E: DRIVER=uhci_hcd |
3667 | +E: PCI_CLASS=C0300 |
3668 | +E: PCI_ID=8086:2834 |
3669 | +E: PCI_SUBSYS_ID=17AA:20AA |
3670 | +E: PCI_SLOT_NAME=0000:00:1a.0 |
3671 | +E: MODALIAS=pci:v00008086d00002834sv000017AAsd000020AAbc0Csc03i00 |
3672 | + |
3673 | +P: /devices/pci0000:00/0000:00:1a.0/usb3 |
3674 | +N: bus/usb/003/001 |
3675 | +S: char/189:256 |
3676 | +E: UDEV_LOG=3 |
3677 | +E: DEVPATH=/devices/pci0000:00/0000:00:1a.0/usb3 |
3678 | +E: MAJOR=189 |
3679 | +E: MINOR=256 |
3680 | +E: DEVTYPE=usb_device |
3681 | +E: DRIVER=usb |
3682 | +E: DEVICE=/proc/bus/usb/003/001 |
3683 | +E: PRODUCT=1d6b/1/206 |
3684 | +E: TYPE=9/0/0 |
3685 | +E: BUSNUM=003 |
3686 | +E: DEVNUM=001 |
3687 | +E: DEVNAME=/dev/bus/usb/003/001 |
3688 | +E: DEVLINKS=/dev/char/189:256 |
3689 | + |
3690 | +P: /devices/pci0000:00/0000:00:1a.0/usb3/3-0:1.0 |
3691 | +E: UDEV_LOG=3 |
3692 | +E: DEVPATH=/devices/pci0000:00/0000:00:1a.0/usb3/3-0:1.0 |
3693 | +E: DEVTYPE=usb_interface |
3694 | +E: DRIVER=hub |
3695 | +E: DEVICE=/proc/bus/usb/003/001 |
3696 | +E: PRODUCT=1d6b/1/206 |
3697 | +E: TYPE=9/0/0 |
3698 | +E: INTERFACE=9/0/0 |
3699 | +E: MODALIAS=usb:v1D6Bp0001d0206dc09dsc00dp00ic09isc00ip00 |
3700 | + |
3701 | +P: /devices/pci0000:00/0000:00:1a.0/usb3/3-0:1.0/usb_endpoint/usbdev3.1_ep81 |
3702 | +N: usbdev3.1_ep81 |
3703 | +S: char/252:4 |
3704 | +E: UDEV_LOG=3 |
3705 | +E: DEVPATH=/devices/pci0000:00/0000:00:1a.0/usb3/3-0:1.0/usb_endpoint/usbdev3.1_ep81 |
3706 | +E: MAJOR=252 |
3707 | +E: MINOR=4 |
3708 | +E: DEVNAME=/dev/usbdev3.1_ep81 |
3709 | +E: DEVLINKS=/dev/char/252:4 |
3710 | + |
3711 | +P: /devices/pci0000:00/0000:00:1f.1 |
3712 | +E: UDEV_LOG=3 |
3713 | +E: DEVPATH=/devices/pci0000:00/0000:00:1f.1 |
3714 | +E: DRIVER=ata_piix |
3715 | +E: PCI_CLASS=1018A |
3716 | +E: PCI_ID=8086:2850 |
3717 | +E: PCI_SUBSYS_ID=17AA:20A6 |
3718 | +E: PCI_SLOT_NAME=0000:00:1f.1 |
3719 | +E: MODALIAS=pci:v00008086d00002850sv000017AAsd000020A6bc01sc01i8a |
3720 | + |
3721 | +P: /devices/pci0000:00/0000:00:1f.1/host3 |
3722 | +E: UDEV_LOG=3 |
3723 | +E: DEVPATH=/devices/pci0000:00/0000:00:1f.1/host3 |
3724 | +E: DEVTYPE=scsi_host |
3725 | + |
3726 | +P: /devices/pci0000:00/0000:00:1f.1/host3/scsi_host/host3 |
3727 | +E: UDEV_LOG=3 |
3728 | +E: DEVPATH=/devices/pci0000:00/0000:00:1f.1/host3/scsi_host/host3 |
3729 | + |
3730 | +P: /devices/pci0000:00/0000:00:1f.1/host3/target3:0:0 |
3731 | +E: UDEV_LOG=3 |
3732 | +E: DEVPATH=/devices/pci0000:00/0000:00:1f.1/host3/target3:0:0 |
3733 | +E: DEVTYPE=scsi_target |
3734 | + |
3735 | +P: /devices/pci0000:00/0000:00:1f.1/host3/target3:0:0/3:0:0:0 |
3736 | +E: UDEV_LOG=3 |
3737 | +E: DEVPATH=/devices/pci0000:00/0000:00:1f.1/host3/target3:0:0/3:0:0:0 |
3738 | +E: DEVTYPE=scsi_device |
3739 | +E: DRIVER=sr |
3740 | +E: MODALIAS=scsi:t-0x05 |
3741 | +</udev> |
3742 | + |
3743 | + <!-- The content of publicly accessible files in /sys/class/dmi/id/ |
3744 | + format: filename:content |
3745 | + as for example generated by "grep -r . /sys/class/dmi/id/" |
3746 | + --> |
3747 | + |
3748 | + <dmi>/sys/class/dmi/id/bios_vendor:LENOVO |
3749 | +/sys/class/dmi/id/bios_version:7LETB9WW (2.19 ) |
3750 | +/sys/class/dmi/id/bios_date:06/06/2008 |
3751 | +/sys/class/dmi/id/sys_vendor:LENOVO |
3752 | +/sys/class/dmi/id/product_name:6457BAG |
3753 | +/sys/class/dmi/id/product_version:ThinkPad T61 |
3754 | +/sys/class/dmi/id/board_vendor:LENOVO |
3755 | +/sys/class/dmi/id/board_name:6457BAG |
3756 | +/sys/class/dmi/id/board_version:Not Available |
3757 | +/sys/class/dmi/id/chassis_vendor:LENOVO |
3758 | +/sys/class/dmi/id/chassis_type:10 |
3759 | +/sys/class/dmi/id/chassis_version:Not Available |
3760 | +/sys/class/dmi/id/chassis_asset_tag:No Asset Information |
3761 | +/sys/class/dmi/id/modalias:dmi:bvnLENOVO:bvr7LETB9WW(2.19) |
3762 | +</dmi> |
3763 | + |
3764 | + <!-- Additional data for SCSI devices: vendor, model, type |
3765 | + |
3766 | + For each udev node which has DEVTYPE=scsi_device, we need |
3767 | + the content of the sysfs files vendor, model, type. The data |
3768 | + is stored in the same format as the DMI data: |
3769 | + /path/to/file:filecontent |
3770 | + --> |
3771 | + <sysfs-attributes>/sys/devices/pci0000:00/0000:00:1f.1/host3/target3:0:0/3:0:0:0/vendor:HL-DT-ST |
3772 | +/sys/devices/pci0000:00/0000:00:1f.1/host3/target3:0:0/3:0:0:0/model:DVDRAM GSA-4083N |
3773 | +/sys/devices/pci0000:00/0000:00:1f.1/host3/target3:0:0/3:0:0:0/type:5 |
3774 | + </sysfs-attributes> |
3775 | + |
3776 | + <!-- processors: Data about processors installed in a system. |
3777 | + The data is retrieved from /proc/cpuinfo. |
3778 | + --> |
3779 | + <processors> |
3780 | + |
3781 | + <!-- processor: Data from /proc/cpuinfo about a single processor. |
3782 | + --> |
3783 | + <processor id="123" name="0"> |
3784 | + |
3785 | + <!-- property: The data of one line of /proc/cpuinfo. |
3786 | + attribute name: The name of the property |
3787 | + (the text left of the ':' in a line of /proc/cpuinfo) |
3788 | + attribute type: A Python type appropriate for the value. |
3789 | + --> |
3790 | + <property name="wp" type="bool"> |
3791 | + True |
3792 | + </property> |
3793 | + <property name="flags" type="list"> |
3794 | + <value type="str"> |
3795 | + fpu |
3796 | + </value> |
3797 | + <value type="str"> |
3798 | + vme |
3799 | + </value> |
3800 | + <value type="str"> |
3801 | + de |
3802 | + </value> |
3803 | + </property> |
3804 | + <property name="cpu_mhz" type="float"> |
3805 | + 1000.0 |
3806 | + </property> |
3807 | + </processor> |
3808 | + </processors> |
3809 | + |
3810 | + <!-- aliases: optional data provided by the user: |
3811 | + The name of a peripheral, PCI card etc as shown by a label on |
3812 | + the device. OEM devices are often sold under different names |
3813 | + by different vendors; having a set of alias names for a device |
3814 | + allows users of the HWDB to search for information by these |
3815 | + "marketing names". |
3816 | + --> |
3817 | + <aliases> |
3818 | + <!-- alias: The "label name" of a device or system. |
3819 | + attribute target: The sysfs path of a device as given |
3820 | + in <udev>. |
3821 | + --> |
3822 | + <alias target="/devices/pci0000:00/0000:00:1f.1/host3/target3:0:0/3:0:0:0"> |
3823 | + |
3824 | + <!-- vendor: The vendor name shown on the device label. |
3825 | + --> |
3826 | + <vendor>Medion</vendor> |
3827 | + |
3828 | + <!-- model: The model name of shown on the label. |
3829 | + --> |
3830 | + <model>QuickPrint 9876</model> |
3831 | + </alias> |
3832 | + </aliases> |
3833 | + </hardware> |
3834 | + |
3835 | + <!-- software: Data about the software installed on the system. |
3836 | + --> |
3837 | + <software> |
3838 | + |
3839 | + <!-- lsbrelease: The data from /etc/lsb-release. |
3840 | + --> |
3841 | + <lsbrelease> |
3842 | + |
3843 | + <!-- property: the data from one line of /etc/lsb-release. |
3844 | + attribute type: A Python type appropriate for this |
3845 | + property (str). |
3846 | + --> |
3847 | + <property name="release" type="str"> |
3848 | + 7.04 |
3849 | + </property> |
3850 | + <property name="codename" type="str"> |
3851 | + feisty |
3852 | + </property> |
3853 | + <property name="distributor-id" type="str"> |
3854 | + Ubuntu |
3855 | + </property> |
3856 | + <property name="description" type="str"> |
3857 | + Ubuntu 7.04 |
3858 | + </property> |
3859 | + <property name="dict_example" type="dict"> |
3860 | + <value name="a" type="str">value for key a</value> |
3861 | + <value name="b" type="int">1234</value> |
3862 | + </property> |
3863 | + </lsbrelease> |
3864 | + |
3865 | + <!-- packages: Data about the installed software packages. |
3866 | + --> |
3867 | + <packages> |
3868 | + |
3869 | + <!-- package: Data about a single package. |
3870 | + The <property> sub-tags contain the DEB properties |
3871 | + "name", "priority", "section", "source", "version", |
3872 | + "installed_size", "size", "summary". |
3873 | + |
3874 | + XXX Abel Deuring 2007-12-12: What about submissions |
3875 | + from RPM-based Linux versions? (And "exotic" variants |
3876 | + like Gentoo?) |
3877 | + --> |
3878 | + <package name="metacity" id="200"> |
3879 | + <property name="installed_size" type="int"> |
3880 | + 868352 |
3881 | + </property> |
3882 | + <property name="section" type="str"> |
3883 | + x11 |
3884 | + </property> |
3885 | + <property name="summary" type="str"> |
3886 | + A lightweight GTK2 based Window Manager |
3887 | + </property> |
3888 | + <property name="priority" type="str"> |
3889 | + optional |
3890 | + </property> |
3891 | + <property name="source" type="str"> |
3892 | + metacity |
3893 | + </property> |
3894 | + <property name="version" type="str"> |
3895 | + 1:2.18.2-0ubuntu1.1 |
3896 | + </property> |
3897 | + <property name="size" type="int"> |
3898 | + 429128 |
3899 | + </property> |
3900 | + </package> |
3901 | + </packages> |
3902 | + <!-- Information extracted from Xorg.0.log. |
3903 | + HAL does not provide any information about Xorg drivers, so |
3904 | + we retrieve that from the xserver's log file. |
3905 | + --> |
3906 | + <xorg version="1.3.0"> |
3907 | + <!-- driver: Data about a driver. |
3908 | + (optional) |
3909 | + attribute name: The name of the driver. |
3910 | + attribute version: The version of the driver. |
3911 | + attribute class: The module class of the driver |
3912 | + attribute device: The ID of a device driven by this driver. |
3913 | + --> |
3914 | + <driver name="fglrx" version="1.23" class="X.Org Video Driver" |
3915 | + device="12"/> |
3916 | + </xorg> |
3917 | + </software> |
3918 | + |
3919 | + <!-- questions: User's answers to questions asked by the client. |
3920 | + --> |
3921 | + <questions> |
3922 | + |
3923 | + <!-- question: Data of a question. |
3924 | + attribute name: The unique name of the question. |
3925 | + attribute plugin: The name of the plugin which asked |
3926 | + the question. |
3927 | + attribute version: The version of the question. |
3928 | + attribute type: Allowed values are "manual" and "automatic". |
3929 | + A "manual" question requires user input for the answer; |
3930 | + an "automatic" question gets the answer automatically. |
3931 | + --> |
3932 | + <question name="detected_network_controllers" |
3933 | + plugin="find_network_controllers"> |
3934 | + |
3935 | + <!-- target: Information about a device or software package the |
3936 | + question is about. The attribute "id" is the sysfs path of |
3937 | + a device as given in <udev>, or the ID of a software package |
3938 | + node. |
3939 | + This node may appear multiple times. |
3940 | + --> |
3941 | + <target id="/devices/pci0000:00/0000:00:1a.0/usb3/3-0:1.0/usb_endpoint/usbdev3.1_ep81"> |
3942 | + <!-- driver: The driver which controls the target device. This tag |
3943 | + may appear more than once. |
3944 | + |
3945 | + While we are working on a project called "Hardware Database", |
3946 | + we are not that much interested in the question, if a device |
3947 | + works "as such", but if their Linux driver(s) work. |
3948 | + |
3949 | + It is not in every case possible to identify the used driver |
3950 | + from HAL data, so we need another way to add this information. |
3951 | + (example: HAL does not know, which driver is used for the |
3952 | + graphics card.) |
3953 | + |
3954 | + Example for multiple drivers: Some scanners have a SCSI _and_ |
3955 | + a USB interface; if such a scanner is tested, we not only want |
3956 | + to know, which Sane backend is used, but also, which interface |
3957 | + is used. |
3958 | + |
3959 | + Also, it might be interesting to know for many USB 2.0 devices, |
3960 | + if a USB 1 (uhci_hcd or ohci_hcd driver) or the USB 2 driver |
3961 | + (ehci_hcd) was used. A USB 1 driver might for example explain |
3962 | + latency problems. |
3963 | + --> |
3964 | + <driver>ipw3945</driver> |
3965 | + </target> |
3966 | + |
3967 | + <!-- ID of the 88E8055 PCI-E Gigabit Ethernet Controller --> |
3968 | + <target id="/devices/pci0000:00/0000:00:1f.1"/> |
3969 | + |
3970 | + <!-- command: The command line of an external command required to |
3971 | + ask this question. |
3972 | + --> |
3973 | + <command/> |
3974 | + |
3975 | + <!-- answer: The answer to the question. Two types of answers are |
3976 | + defined, "multiple_choice" and "measurement". (See below |
3977 | + for an example of the latter.) |
3978 | + attribute type: Must be "multiple_choice" or "measurement". |
3979 | + --> |
3980 | + <answer type="multiple_choice">pass</answer> |
3981 | + |
3982 | + <!-- answer_choices: The list of possible choices. |
3983 | + The data should only be used for consistency |
3984 | + checks and to detect variants of the question. |
3985 | + --> |
3986 | + <answer_choices> |
3987 | + <value type="str">fail</value> |
3988 | + <value type="str">pass</value> |
3989 | + <value type="str">skip</value> |
3990 | + </answer_choices> |
3991 | + |
3992 | + <!-- A user comment about the device or about the test. |
3993 | + --> |
3994 | + <comment> |
3995 | + The WLAN adapter drops the connection very frequently. |
3996 | + </comment> |
3997 | + </question> |
3998 | + |
3999 | + <question name="internet_ping" |
4000 | + plugin="internet_ping"> |
4001 | + <target id="/devices/pci0000:00/0000:00:1f.1"/> |
4002 | + <command/> |
4003 | + <answer type="multiple_choice">pass</answer> |
4004 | + <answer_choices> |
4005 | + <value type="str">fail</value> |
4006 | + <value type="str">pass</value> |
4007 | + <value type="str">skip</value> |
4008 | + </answer_choices> |
4009 | + </question> |
4010 | + |
4011 | + <!-- example for a "measurement question" |
4012 | + --> |
4013 | + <question name="harddisk_speed" |
4014 | + plugin="harddisk_speed"> |
4015 | + <target id="/devices/pci0000:00/0000:00:1f.1/host3/target3:0:0/3:0:0:0"/> |
4016 | + <command>hdparm -t /dev/sda</command> |
4017 | + <!-- answer: The answer to a "measurement question". |
4018 | + attribute type: See above. |
4019 | + attribute unit: The unit of the result of the measurement. |
4020 | + XXX Abel Deuring 2007-12-12 bug=175978 We should |
4021 | + enumerate a list of allowed units, in order to avoid |
4022 | + multiple units for the same dimension. e.g., B/sec, |
4023 | + MB/sec or inch, cm, foot. |
4024 | + |
4025 | + For dimensionless values, the attribute unit is omitted. |
4026 | + |
4027 | + "Percentage" and similar "convenience pseudo-units" like |
4028 | + ppm are _not_ allowed; instead a dimensionless |
4029 | + value must be used, where 0 is equivalent 0% and 1.0 is |
4030 | + equivalent to 100%. |
4031 | + --> |
4032 | + <answer type="measurement" unit="MB/sec">38.4</answer> |
4033 | + </question> |
4034 | + </questions> |
4035 | + <!-- miscellaneous additional text data. |
4036 | + --> |
4037 | +</system> |
4038 | |
4039 | === modified file 'lib/canonical/launchpad/scripts/tests/test_hwdb_submission_parser.py' |
4040 | --- lib/canonical/launchpad/scripts/tests/test_hwdb_submission_parser.py 2009-06-25 05:30:52 +0000 |
4041 | +++ lib/canonical/launchpad/scripts/tests/test_hwdb_submission_parser.py 2009-09-15 17:05:32 +0000 |
4042 | @@ -4,7 +4,10 @@ |
4043 | """Tests of the HWDB submissions parser.""" |
4044 | |
4045 | from cStringIO import StringIO |
4046 | -import cElementTree as etree |
4047 | +try: |
4048 | + import xml.etree.cElementTree as etree |
4049 | +except ImportError: |
4050 | + import cElementTree as etree |
4051 | from datetime import datetime |
4052 | import logging |
4053 | import os |
4054 | @@ -507,6 +510,111 @@ |
4055 | 'model': 'MD 4394'}], |
4056 | 'Invalid parsing result for <aliases>') |
4057 | |
4058 | + def testUdev(self): |
4059 | + """The content of the <udev> node is converted into a list of dicts. |
4060 | + """ |
4061 | + parser = SubmissionParser(self.log) |
4062 | + node = etree.fromstring(""" |
4063 | +<udev>P: /devices/LNXSYSTM:00 |
4064 | +E: UDEV_LOG=3 |
4065 | +E: DEVPATH=/devices/LNXSYSTM:00 |
4066 | +E: MODALIAS=acpi:LNXSYSTM: |
4067 | + |
4068 | +P: /devices/pci0000:00/0000:00:1a.0 |
4069 | +E: UDEV_LOG=3 |
4070 | +E: DEVPATH=/devices/pci0000:00/0000:00:1a.0 |
4071 | +S: char/189:256 |
4072 | +</udev> |
4073 | +""") |
4074 | + result = parser._parseUdev(node) |
4075 | + self.assertEqual( |
4076 | + [ |
4077 | + { |
4078 | + 'P': '/devices/LNXSYSTM:00', |
4079 | + 'E': { |
4080 | + 'UDEV_LOG': '3', |
4081 | + 'DEVPATH': '/devices/LNXSYSTM:00', |
4082 | + 'MODALIAS': 'acpi:LNXSYSTM:', |
4083 | + }, |
4084 | + 'S': [], |
4085 | + }, |
4086 | + { |
4087 | + 'P': '/devices/pci0000:00/0000:00:1a.0', |
4088 | + 'E': { |
4089 | + 'UDEV_LOG': '3', |
4090 | + 'DEVPATH': '/devices/pci0000:00/0000:00:1a.0', |
4091 | + }, |
4092 | + 'S': ['char/189:256'], |
4093 | + }, |
4094 | + ], |
4095 | + result, |
4096 | + 'Invalid parsing result for <udev>') |
4097 | + |
4098 | + def testUdevLineWithoutColon(self): |
4099 | + """<udev> nodes with lines not in key: value format are rejected.""" |
4100 | + parser = SubmissionParser(self.log) |
4101 | + parser.submission_key = 'Detect udev lines not in key:value format' |
4102 | + node = etree.fromstring(""" |
4103 | +<udev>P: /devices/LNXSYSTM:00 |
4104 | +bad line |
4105 | +</udev> |
4106 | +""") |
4107 | + result = parser._parseUdev(node) |
4108 | + self.assertEqual( |
4109 | + None, result, |
4110 | + 'Invalid parsing result for a <udev> node with a line not having ' |
4111 | + 'the key: value format.') |
4112 | + self.assertErrorMessage( |
4113 | + parser.submission_key, |
4114 | + "Line 1 in <udev>: No valid key:value data: 'bad line'") |
4115 | + |
4116 | + def testUdevPropertyLineWithoutEqualSign(self): |
4117 | + """<udev> nodes with lines not in key: value format are rejected.""" |
4118 | + parser = SubmissionParser(self.log) |
4119 | + parser.submission_key = ( |
4120 | + 'Detect udev property lines not in key=value format') |
4121 | + node = etree.fromstring(""" |
4122 | +<udev>P: /devices/LNXSYSTM:00 |
4123 | +E: bad property |
4124 | +</udev> |
4125 | +""") |
4126 | + result = parser._parseUdev(node) |
4127 | + self.assertEqual( |
4128 | + None, result, |
4129 | + 'Invalid parsing result for a <udev> node with a property line ' |
4130 | + 'not having the key=value format.') |
4131 | + self.assertErrorMessage( |
4132 | + parser.submission_key, |
4133 | + "Line 1 in <udev>: Property without valid key=value data: " |
4134 | + "'E: bad property'") |
4135 | + |
4136 | + def testUdevDataWithDuplicateKey(self): |
4137 | + """<udev> nodes with lines not in key: value format are rejected.""" |
4138 | + parser = SubmissionParser(self.log) |
4139 | + parser.submission_key = 'Detect duplactae attributes in udev data' |
4140 | + node = etree.fromstring(""" |
4141 | +<udev>P: /devices/LNXSYSTM:00 |
4142 | +W:1 |
4143 | +W:2 |
4144 | +</udev> |
4145 | +""") |
4146 | + result = parser._parseUdev(node) |
4147 | + self.assertEqual( |
4148 | + [ |
4149 | + { |
4150 | + 'P': '/devices/LNXSYSTM:00', |
4151 | + 'E': {}, |
4152 | + 'S': [], |
4153 | + 'W': '2', |
4154 | + }, |
4155 | + ], |
4156 | + result, |
4157 | + 'Invalid parsing result for a <udev> node with a duplicate ' |
4158 | + 'attribute.') |
4159 | + self.assertWarningMessage( |
4160 | + parser.submission_key, |
4161 | + "Line 2 in <udev>: Duplicate attribute key: 'W:2'") |
4162 | + |
4163 | def testHardware(self): |
4164 | """The <hardware> tag is converted into a dictionary.""" |
4165 | test = self |
4166 | |
4167 | === modified file 'lib/canonical/launchpad/scripts/tests/test_hwdb_submission_validation.py' |
4168 | --- lib/canonical/launchpad/scripts/tests/test_hwdb_submission_validation.py 2009-06-25 05:30:52 +0000 |
4169 | +++ lib/canonical/launchpad/scripts/tests/test_hwdb_submission_validation.py 2009-09-14 17:38:57 +0000 |
4170 | @@ -375,8 +375,20 @@ |
4171 | |
4172 | The only allowed tags are specified by the Relax NG schema: |
4173 | live_cd, system_id, distribution, distroseries, architecture, |
4174 | - private, contactable, date_created. |
4175 | + private, contactable, date_created (tested in |
4176 | + testSummaryRequiredTags()), and the optional tag <kernel-release>. |
4177 | """ |
4178 | + # we can add the tag <kernel-release> |
4179 | + sample_data = self.insertSampledata( |
4180 | + data=self.sample_data, |
4181 | + insert_text='<kernel-release value="2.6.28-15-generic"/>', |
4182 | + where='</summary>') |
4183 | + result, submission_id = self.runValidator(sample_data) |
4184 | + self.assertNotEqual( |
4185 | + result, None, |
4186 | + 'Valid submission containing a <kernel-release> tag rejected.') |
4187 | + |
4188 | + # Adding any other tag is not possible. |
4189 | sample_data = self.insertSampledata( |
4190 | data=self.sample_data, |
4191 | insert_text='<nonsense/>', |
4192 | @@ -609,24 +621,99 @@ |
4193 | 'Invalid attribute foo for element plugin', |
4194 | 'invalid attribute in client plugin') |
4195 | |
4196 | - def testHardwareSubTags(self): |
4197 | + def testHardwareSubTagHalOrUdev(self): |
4198 | + """The <hardware> tag requires data about hardware devices. |
4199 | + |
4200 | + This data is stored either in the sub-tag <hal> or in the |
4201 | + three tags <udev>, <dmi>, <sysfs-attributes>. |
4202 | + """ |
4203 | + # Omitting <hal> leads to an error. |
4204 | + sample_data = self.replaceSampledata( |
4205 | + data=self.sample_data, |
4206 | + replace_text='', |
4207 | + from_text='<hal', |
4208 | + to_text='</hal>') |
4209 | + result, submission_id = self.runValidator(sample_data) |
4210 | + self.assertErrorMessage( |
4211 | + submission_id, result, |
4212 | + 'Expecting an element hal, got nothing', |
4213 | + 'missing tag <hal> in <hardware>') |
4214 | + |
4215 | + # But we may replace <hal> by the three tags <udev>, <dmi>, |
4216 | + #<sysfs-attributes>. |
4217 | + sample_data = self.replaceSampledata( |
4218 | + data=self.sample_data, |
4219 | + replace_text=""" |
4220 | + <udev>some text</udev> |
4221 | + <dmi>some text</dmi> |
4222 | + <sysfs-attributes>some text</sysfs-attributes> |
4223 | + """, |
4224 | + from_text='<hal', |
4225 | + to_text='</hal>') |
4226 | + result, submission_id = self.runValidator(sample_data) |
4227 | + self.assertNotEqual( |
4228 | + result, None, |
4229 | + 'submission with valid <udev>, <dmi>, <sysfs-attributes> tags ' |
4230 | + 'rejected') |
4231 | + |
4232 | + def testHardwareSubTagUdevIncomplete(self): |
4233 | """The <hardware> tag has a fixed set of allowed sub-tags. |
4234 | |
4235 | - Valid sub-tags are <hal>, <processors>, <aliases>. |
4236 | - <aliases> is optional; <hal> and <processors> are required. |
4237 | + Valid sub-tags are <hal>, <udev>, <dmi>, <sysfs-attributes>, |
4238 | + <processors>, <aliases>. <aliases> is optional, <processors> |
4239 | + is required, and either <hal> or all three tags <udev>, <dmi>, |
4240 | + <sysfs-attributes> must be present. |
4241 | """ |
4242 | - # Omitting either of the required tags leads on an error. |
4243 | - for tag in ('hal', 'processors'): |
4244 | + # Omitting any of the three tags <udev>, <dmi>, <sysfs-attributes> |
4245 | + # makes the data invalid. |
4246 | + all_tags = ['udev', 'dmi', 'sysfs-attributes'] |
4247 | + for index, missing_tag in enumerate(all_tags): |
4248 | + test_tags = all_tags[:] |
4249 | + del test_tags[index] |
4250 | + replace_text = [ |
4251 | + '<%s>text</%s>' % (tag, tag) for tag in test_tags] |
4252 | + replace_text = ''.join(replace_text) |
4253 | sample_data = self.replaceSampledata( |
4254 | data=self.sample_data, |
4255 | - replace_text='', |
4256 | - from_text='<%s' % tag, |
4257 | - to_text='</%s>' % tag) |
4258 | - result, submission_id = self.runValidator(sample_data) |
4259 | - self.assertErrorMessage( |
4260 | - submission_id, result, |
4261 | - 'Expecting an element %s, got nothing' % tag, |
4262 | - 'missing tag <%s> in <hardware>' % tag) |
4263 | + replace_text=replace_text, |
4264 | + from_text='<hal', |
4265 | + to_text='</hal>') |
4266 | + result, submission_id = self.runValidator(sample_data) |
4267 | + self.assertErrorMessage( |
4268 | + submission_id, result, |
4269 | + 'Expecting an element %s, got nothing' % missing_tag, |
4270 | + 'missing tag <%s> in <hardware>' % missing_tag) |
4271 | + |
4272 | + def testHardwareSubTagHalMixedWithUdev(self): |
4273 | + """Mixing <hal> with <udev>, <dmi>, <sysfs-attributes> is impossible. |
4274 | + """ |
4275 | + # A submission containing the tag <hal> as well as one of <udev>, |
4276 | + # <dmi>, <sysfs-attributes> is invalid. |
4277 | + for tag in ['udev', 'dmi', 'sysfs-attributes']: |
4278 | + sample_data = self.insertSampledata( |
4279 | + data=self.sample_data, |
4280 | + insert_text='<%s>some text</%s>' % (tag, tag), |
4281 | + where='<hal') |
4282 | + result, submission_id = self.runValidator(sample_data) |
4283 | + self.assertErrorMessage( |
4284 | + submission_id, result, |
4285 | + 'Invalid sequence in interleave', |
4286 | + '<hal> mixed with <%s> in <hardware>' % tag) |
4287 | + |
4288 | + def testHardwareOtherSubTags(self): |
4289 | + """The <hardware> tag has a fixed set of allowed sub-tags. |
4290 | + """ |
4291 | + # The <processors> tag must not be omitted. |
4292 | + sample_data = self.replaceSampledata( |
4293 | + data=self.sample_data, |
4294 | + replace_text='', |
4295 | + from_text='<processors', |
4296 | + to_text='</processors>') |
4297 | + result, submission_id = self.runValidator(sample_data) |
4298 | + self.assertErrorMessage( |
4299 | + submission_id, result, |
4300 | + 'Expecting an element processors, got nothing', |
4301 | + '<processor> tag omitted') |
4302 | |
4303 | # The <aliases> tag may be omitted. |
4304 | sample_data = self.replaceSampledata( |
4305 | @@ -1603,24 +1690,6 @@ |
4306 | 'Extra element aliases in interleave', |
4307 | 'missing attribute of <alias>') |
4308 | |
4309 | - # target must be an integer. |
4310 | - sample_data = self.sample_data.replace( |
4311 | - '<alias target="65">', '<alias target="noInteger">') |
4312 | - result, submission_id = self.runValidator(sample_data) |
4313 | - self.assertErrorMessage( |
4314 | - submission_id, result, |
4315 | - 'Extra element aliases in interleave', |
4316 | - 'missing attribute of <alias>') |
4317 | - |
4318 | - # target must not be empty. |
4319 | - sample_data = self.sample_data.replace( |
4320 | - '<alias target="65">', '<alias target="">') |
4321 | - result, submission_id = self.runValidator(sample_data) |
4322 | - self.assertErrorMessage( |
4323 | - submission_id, result, |
4324 | - 'Element hardware failed to validate content', |
4325 | - 'missing attribute of <alias>') |
4326 | - |
4327 | # Other attributes are not allowed. We get again the same |
4328 | # quite unspecific error message as above. |
4329 | sample_data = self.sample_data.replace( |
4330 | |
4331 | === modified file 'lib/canonical/launchpad/security.py' |
4332 | --- lib/canonical/launchpad/security.py 2009-08-20 12:36:07 +0000 |
4333 | +++ lib/canonical/launchpad/security.py 2009-08-31 03:03:00 +0000 |
4334 | @@ -63,7 +63,7 @@ |
4335 | IMilestone, IProjectMilestone) |
4336 | from canonical.launchpad.interfaces.oauth import ( |
4337 | IOAuthAccessToken, IOAuthRequestToken) |
4338 | -from lp.soyuz.interfaces.packageset import IPackagesetSet |
4339 | +from lp.soyuz.interfaces.packageset import IPackageset, IPackagesetSet |
4340 | from lp.translations.interfaces.pofile import IPOFile |
4341 | from lp.translations.interfaces.potemplate import ( |
4342 | IPOTemplate, IPOTemplateSubset) |
4343 | @@ -195,6 +195,7 @@ |
4344 | return (user.inTeam(celebrities.registry_experts) |
4345 | or user.inTeam(celebrities.admin)) |
4346 | |
4347 | + |
4348 | class ReviewProduct(ReviewByRegistryExpertsOrAdmins): |
4349 | usedfor = IProduct |
4350 | |
4351 | @@ -211,8 +212,6 @@ |
4352 | usedfor = IProjectSet |
4353 | |
4354 | |
4355 | - |
4356 | - |
4357 | class ViewPillar(AuthorizationBase): |
4358 | usedfor = IPillar |
4359 | permission = 'launchpad.View' |
4360 | @@ -385,8 +384,7 @@ |
4361 | if user.inTeam(driver): |
4362 | return True |
4363 | admins = getUtility(ILaunchpadCelebrities).admin |
4364 | - return (user.inTeam(self.obj.target.owner) or |
4365 | - user.inTeam(admins)) |
4366 | + return (user.inTeam(targetowner) or user.inTeam(admins)) |
4367 | |
4368 | |
4369 | class DriverSpecification(AuthorizationBase): |
4370 | @@ -514,6 +512,7 @@ |
4371 | """IProjectMilestone is a fake content object.""" |
4372 | return False |
4373 | |
4374 | + |
4375 | class EditMilestoneByTargetOwnerOrAdmins(AuthorizationBase): |
4376 | permission = 'launchpad.Edit' |
4377 | usedfor = IMilestone |
4378 | @@ -2268,6 +2267,18 @@ |
4379 | or user.inTeam(celebrities.admin)) |
4380 | |
4381 | |
4382 | +class EditPackageset(AuthorizationBase): |
4383 | + permission = 'launchpad.Edit' |
4384 | + usedfor = IPackageset |
4385 | + |
4386 | + def checkAuthenticated(self, user): |
4387 | + """The owner of a package set can edit the object.""" |
4388 | + celebrities = getUtility(ILaunchpadCelebrities) |
4389 | + return ( |
4390 | + user.inTeam(self.obj.owner) |
4391 | + or user.inTeam(celebrities.admin)) |
4392 | + |
4393 | + |
4394 | class EditPackagesetSet(AuthorizationBase): |
4395 | permission = 'launchpad.Edit' |
4396 | usedfor = IPackagesetSet |
4397 | |
4398 | === removed file 'lib/canonical/launchpad/templates/bugbranch-delete.pt' |
4399 | --- lib/canonical/launchpad/templates/bugbranch-delete.pt 2009-07-17 17:59:07 +0000 |
4400 | +++ lib/canonical/launchpad/templates/bugbranch-delete.pt 1970-01-01 00:00:00 +0000 |
4401 | @@ -1,28 +0,0 @@ |
4402 | -<html |
4403 | - xmlns="http://www.w3.org/1999/xhtml" |
4404 | - xml:lang="en" |
4405 | - lang="en" |
4406 | - metal:use-macro="context/@@main_template/master" |
4407 | - i18n:domain="launchpad"> |
4408 | - |
4409 | -<body> |
4410 | - |
4411 | -<metal:leftportlets fill-slot="portlets"> |
4412 | - <div tal:replace="structure context/branch/@@+portlet-details" /> |
4413 | -</metal:leftportlets> |
4414 | - |
4415 | -<div metal:fill-slot="main"> |
4416 | - |
4417 | - <h1>Delete bug branch link</h1> |
4418 | - |
4419 | - <div class="documentDescription"> |
4420 | - Are you sure you want to remove the link between... |
4421 | - </div> |
4422 | - |
4423 | - <div metal:use-macro="context/@@launchpad_form/form"> |
4424 | - </div> |
4425 | - |
4426 | -</div> |
4427 | - |
4428 | -</body> |
4429 | -</html> |
4430 | |
4431 | === modified file 'lib/canonical/launchpad/templates/launchpad-form.pt' |
4432 | --- lib/canonical/launchpad/templates/launchpad-form.pt 2009-08-01 02:04:55 +0000 |
4433 | +++ lib/canonical/launchpad/templates/launchpad-form.pt 2009-09-03 15:39:22 +0000 |
4434 | @@ -14,11 +14,6 @@ |
4435 | enctype="multipart/form-data" |
4436 | accept-charset="UTF-8"> |
4437 | |
4438 | - <h1 tal:condition="view/label" |
4439 | - tal:content="view/label" |
4440 | - metal:define-slot="heading" |
4441 | - >Add Something</h1> |
4442 | - |
4443 | <div metal:define-macro="formbody"> |
4444 | |
4445 | <p metal:define-slot="extra_info" tal:replace="nothing"> |
4446 | @@ -38,8 +33,8 @@ |
4447 | Schema validation errors. |
4448 | </p> |
4449 | |
4450 | - <div class="row" |
4451 | - metal:define-slot="extra_top" |
4452 | + <div class="row" |
4453 | + metal:define-slot="extra_top" |
4454 | tal:replace="nothing"> |
4455 | <div>Extra top</div> |
4456 | <div><input type="text"/></div> |
4457 | @@ -55,7 +50,7 @@ |
4458 | tal:content="structure script" /> |
4459 | |
4460 | <div class="row" |
4461 | - metal:define-slot="extra_bottom" |
4462 | + metal:define-slot="extra_bottom" |
4463 | tal:replace="nothing"> |
4464 | <div>Extra bottom</div> |
4465 | <div class="field"><input type="text" /></div> |
4466 | @@ -73,8 +68,8 @@ |
4467 | </tal:has-cancel-link> |
4468 | </div> |
4469 | |
4470 | - <div class="row" |
4471 | - metal:define-slot="extra_buttons" |
4472 | + <div class="row" |
4473 | + metal:define-slot="extra_buttons" |
4474 | tal:replace="nothing"> |
4475 | </div> |
4476 | |
4477 | |
4478 | === modified file 'lib/canonical/launchpad/templates/logintoken-claimprofile.pt' |
4479 | --- lib/canonical/launchpad/templates/logintoken-claimprofile.pt 2009-07-17 17:59:07 +0000 |
4480 | +++ lib/canonical/launchpad/templates/logintoken-claimprofile.pt 2009-09-16 20:51:57 +0000 |
4481 | @@ -3,10 +3,7 @@ |
4482 | xmlns:tal="http://xml.zope.org/namespaces/tal" |
4483 | xmlns:metal="http://xml.zope.org/namespaces/metal" |
4484 | xmlns:i18n="http://xml.zope.org/namespaces/i18n" |
4485 | - xml:lang="en" |
4486 | - lang="en" |
4487 | - dir="ltr" |
4488 | - metal:use-macro="context/@@main_template/master" |
4489 | + metal:use-macro="view/macro:page/locationless" |
4490 | i18n:domain="launchpad" |
4491 | > |
4492 | <body> |
4493 | |
4494 | === modified file 'lib/canonical/launchpad/templates/logintoken-claimteam.pt' |
4495 | --- lib/canonical/launchpad/templates/logintoken-claimteam.pt 2009-07-17 17:59:07 +0000 |
4496 | +++ lib/canonical/launchpad/templates/logintoken-claimteam.pt 2009-09-16 20:51:57 +0000 |
4497 | @@ -3,10 +3,7 @@ |
4498 | xmlns:tal="http://xml.zope.org/namespaces/tal" |
4499 | xmlns:metal="http://xml.zope.org/namespaces/metal" |
4500 | xmlns:i18n="http://xml.zope.org/namespaces/i18n" |
4501 | - xml:lang="en" |
4502 | - lang="en" |
4503 | - dir="ltr" |
4504 | - metal:use-macro="context/@@main_template/master" |
4505 | + metal:use-macro="view/macro:page/locationless" |
4506 | i18n:domain="launchpad" |
4507 | > |
4508 | <body> |
4509 | |
4510 | === modified file 'lib/canonical/launchpad/templates/logintoken-index.pt' |
4511 | --- lib/canonical/launchpad/templates/logintoken-index.pt 2009-07-17 17:59:07 +0000 |
4512 | +++ lib/canonical/launchpad/templates/logintoken-index.pt 2009-09-17 18:46:13 +0000 |
4513 | @@ -3,10 +3,7 @@ |
4514 | xmlns:tal="http://xml.zope.org/namespaces/tal" |
4515 | xmlns:metal="http://xml.zope.org/namespaces/metal" |
4516 | xmlns:i18n="http://xml.zope.org/namespaces/i18n" |
4517 | - xml:lang="en" |
4518 | - lang="en" |
4519 | - dir="ltr" |
4520 | - metal:use-macro="context/@@main_template/master" |
4521 | + metal:use-macro="view/macro:page/locationless" |
4522 | i18n:domain="launchpad" |
4523 | > |
4524 | |
4525 | @@ -14,11 +11,9 @@ |
4526 | |
4527 | <div metal:fill-slot="main"> |
4528 | |
4529 | - <h1>Confirmation already concluded</h1> |
4530 | - |
4531 | <p> |
4532 | You reached this page probably because you followed a link received by |
4533 | - email. That link was sent to confirm you have access to the email |
4534 | + email. That link was sent to confirm you have access to the email |
4535 | address it was sent to, but this confirmation was already concluded, so |
4536 | you don't need to do anything else. |
4537 | </p> |
4538 | |
4539 | === modified file 'lib/canonical/launchpad/templates/logintoken-newaccount.pt' |
4540 | --- lib/canonical/launchpad/templates/logintoken-newaccount.pt 2009-07-17 17:59:07 +0000 |
4541 | +++ lib/canonical/launchpad/templates/logintoken-newaccount.pt 2009-09-16 20:51:57 +0000 |
4542 | @@ -3,10 +3,7 @@ |
4543 | xmlns:tal="http://xml.zope.org/namespaces/tal" |
4544 | xmlns:metal="http://xml.zope.org/namespaces/metal" |
4545 | xmlns:i18n="http://xml.zope.org/namespaces/i18n" |
4546 | - xml:lang="en" |
4547 | - lang="en" |
4548 | - dir="ltr" |
4549 | - metal:use-macro="context/@@main_template/master" |
4550 | + metal:use-macro="view/macro:page/locationless" |
4551 | i18n:domain="launchpad" |
4552 | > |
4553 | <body> |
4554 | |
4555 | === modified file 'lib/canonical/launchpad/templates/logintoken-resetpassword.pt' |
4556 | --- lib/canonical/launchpad/templates/logintoken-resetpassword.pt 2009-07-17 17:59:07 +0000 |
4557 | +++ lib/canonical/launchpad/templates/logintoken-resetpassword.pt 2009-09-16 20:51:57 +0000 |
4558 | @@ -3,10 +3,7 @@ |
4559 | xmlns:tal="http://xml.zope.org/namespaces/tal" |
4560 | xmlns:metal="http://xml.zope.org/namespaces/metal" |
4561 | xmlns:i18n="http://xml.zope.org/namespaces/i18n" |
4562 | - xml:lang="en" |
4563 | - lang="en" |
4564 | - dir="ltr" |
4565 | - metal:use-macro="context/@@main_template/master" |
4566 | + metal:use-macro="view/macro:page/locationless" |
4567 | i18n:domain="launchpad" |
4568 | > |
4569 | <body> |
4570 | |
4571 | === modified file 'lib/canonical/launchpad/templates/logintoken-validateemail.pt' |
4572 | --- lib/canonical/launchpad/templates/logintoken-validateemail.pt 2009-07-17 17:59:07 +0000 |
4573 | +++ lib/canonical/launchpad/templates/logintoken-validateemail.pt 2009-09-16 20:51:57 +0000 |
4574 | @@ -3,10 +3,7 @@ |
4575 | xmlns:tal="http://xml.zope.org/namespaces/tal" |
4576 | xmlns:metal="http://xml.zope.org/namespaces/metal" |
4577 | xmlns:i18n="http://xml.zope.org/namespaces/i18n" |
4578 | - xml:lang="en" |
4579 | - lang="en" |
4580 | - dir="ltr" |
4581 | - metal:use-macro="context/@@main_template/master" |
4582 | + metal:use-macro="view/macro:page/locationless" |
4583 | i18n:domain="launchpad" |
4584 | > |
4585 | <body> |
4586 | |
4587 | === modified file 'lib/canonical/launchpad/templates/logintoken-validategpg.pt' |
4588 | --- lib/canonical/launchpad/templates/logintoken-validategpg.pt 2009-07-17 17:59:07 +0000 |
4589 | +++ lib/canonical/launchpad/templates/logintoken-validategpg.pt 2009-09-16 20:51:57 +0000 |
4590 | @@ -3,10 +3,7 @@ |
4591 | xmlns:tal="http://xml.zope.org/namespaces/tal" |
4592 | xmlns:metal="http://xml.zope.org/namespaces/metal" |
4593 | xmlns:i18n="http://xml.zope.org/namespaces/i18n" |
4594 | - xml:lang="en" |
4595 | - lang="en" |
4596 | - dir="ltr" |
4597 | - metal:use-macro="context/@@main_template/master" |
4598 | + metal:use-macro="view/macro:page/locationless" |
4599 | i18n:domain="launchpad" |
4600 | > |
4601 | <body> |
4602 | |
4603 | === modified file 'lib/canonical/launchpad/templates/logintoken-validatesignonlygpg.pt' |
4604 | --- lib/canonical/launchpad/templates/logintoken-validatesignonlygpg.pt 2009-07-17 17:59:07 +0000 |
4605 | +++ lib/canonical/launchpad/templates/logintoken-validatesignonlygpg.pt 2009-09-17 18:46:13 +0000 |
4606 | @@ -3,10 +3,7 @@ |
4607 | xmlns:tal="http://xml.zope.org/namespaces/tal" |
4608 | xmlns:metal="http://xml.zope.org/namespaces/metal" |
4609 | xmlns:i18n="http://xml.zope.org/namespaces/i18n" |
4610 | - xml:lang="en" |
4611 | - lang="en" |
4612 | - dir="ltr" |
4613 | - metal:use-macro="context/@@main_template/master" |
4614 | + metal:use-macro="view/macro:page/locationless" |
4615 | i18n:domain="launchpad" |
4616 | > |
4617 | <body> |
4618 | @@ -15,10 +12,6 @@ |
4619 | |
4620 | <div metal:use-macro="context/@@launchpad_form/form"> |
4621 | |
4622 | - <metal:heading fill-slot="heading"> |
4623 | - <h1>Confirm your OpenPGP key</h1> |
4624 | - </metal:heading> |
4625 | - |
4626 | <p metal:fill-slot="extra_info"> |
4627 | Thanks for adding your OpenPGP key to Launchpad. So we can confirm that the key is yours, we need you to use the key to sign some text. |
4628 | </p> |
4629 | |
4630 | === modified file 'lib/canonical/launchpad/templates/logintoken-validateteamemail.pt' |
4631 | --- lib/canonical/launchpad/templates/logintoken-validateteamemail.pt 2009-07-17 17:59:07 +0000 |
4632 | +++ lib/canonical/launchpad/templates/logintoken-validateteamemail.pt 2009-09-16 20:51:57 +0000 |
4633 | @@ -3,10 +3,7 @@ |
4634 | xmlns:tal="http://xml.zope.org/namespaces/tal" |
4635 | xmlns:metal="http://xml.zope.org/namespaces/metal" |
4636 | xmlns:i18n="http://xml.zope.org/namespaces/i18n" |
4637 | - xml:lang="en" |
4638 | - lang="en" |
4639 | - dir="ltr" |
4640 | - metal:use-macro="context/@@main_template/master" |
4641 | + metal:use-macro="view/macro:page/locationless" |
4642 | i18n:domain="launchpad" |
4643 | > |
4644 | <body> |
4645 | |
4646 | === modified file 'lib/canonical/launchpad/templates/main-template.pt' |
4647 | --- lib/canonical/launchpad/templates/main-template.pt 2009-08-30 13:31:02 +0000 |
4648 | +++ lib/canonical/launchpad/templates/main-template.pt 2009-09-15 17:30:59 +0000 |
4649 | @@ -98,12 +98,6 @@ |
4650 | <input type="search" id="search-text" name="field.text" /> |
4651 | </form> |
4652 | <tal:hierarchy replace="structure context/@@+hierarchy" /> |
4653 | - <div id="globalheader" xml:lang="en" lang="en" dir="ltr" |
4654 | - tal:condition="site_message"> |
4655 | - <div class="sitemessage" tal:content="structure site_message"> |
4656 | - This site is running pre-release code. |
4657 | - </div> |
4658 | - </div> |
4659 | <div |
4660 | tal:condition="view/macro:pagehas/applicationtabs" |
4661 | tal:define="facetmenu view/menu:facet" |
4662 | @@ -239,6 +233,10 @@ |
4663 | <a tal:condition="request/lp:person" href="/feedback" |
4664 | >Contact us</a> | <a href="https://help.launchpad.net/">Get help with Launchpad</a> |
4665 | </div> |
4666 | + |
4667 | + <metal:site-message |
4668 | + use-macro="context/@@+base-layout-macros/site-message"/> |
4669 | + |
4670 | <div id="lp-arcana"> |
4671 | © 2004-2009 <a |
4672 | href="http://canonical.com/">Canonical Ltd.</a> |
4673 | |
4674 | === modified file 'lib/canonical/launchpad/templates/structural-subscriptions-manage.pt' |
4675 | --- lib/canonical/launchpad/templates/structural-subscriptions-manage.pt 2009-07-17 17:59:07 +0000 |
4676 | +++ lib/canonical/launchpad/templates/structural-subscriptions-manage.pt 2009-09-17 17:12:58 +0000 |
4677 | @@ -3,41 +3,28 @@ |
4678 | xmlns:tal="http://xml.zope.org/namespaces/tal" |
4679 | xmlns:metal="http://xml.zope.org/namespaces/metal" |
4680 | xmlns:i18n="http://xml.zope.org/namespaces/i18n" |
4681 | - xml:lang="en" |
4682 | - lang="en" |
4683 | - dir="ltr" |
4684 | - metal:use-macro="context/@@main_template/master" |
4685 | + metal:use-macro="view/macro:page/main_side" |
4686 | i18n:domain="launchpad" |
4687 | > |
4688 | <body> |
4689 | - |
4690 | -<metal:leftportlets fill-slot="portlets_one"> |
4691 | - <div tal:replace="structure context/@@+portlet-malone-bugmail-filtering-faq"/> |
4692 | -</metal:leftportlets> |
4693 | - |
4694 | -<metal:rightportlets fill-slot="portlets_two"> |
4695 | - <div tal:condition="view/show_details_portlet" |
4696 | - tal:replace="structure context/@@+portlet-details" /> |
4697 | - <div tal:replace="structure context/@@+portlet-structural-subscribers" /> |
4698 | -</metal:rightportlets> |
4699 | - |
4700 | -<metal:heading fill-slot="pageheading"> |
4701 | - <h1>Change bug subscriptions</h1> |
4702 | -</metal:heading> |
4703 | - |
4704 | -<div metal:fill-slot="main"> |
4705 | - |
4706 | - <p> |
4707 | - You can choose to receive an e-mail every time someone reports or |
4708 | - changes a public bug associated with |
4709 | - <span tal:replace="context/title">this item</span>. |
4710 | - </p> |
4711 | - <p> |
4712 | - <strong>Important:</strong> subscribing here may mean you receive a |
4713 | - great deal of e-mail. You can return here to unsubscribe at any |
4714 | - time. |
4715 | - </p> |
4716 | - <div metal:use-macro="context/@@launchpad_form/form" /> |
4717 | -</div> |
4718 | + <div metal:fill-slot="main"> |
4719 | + <p> |
4720 | + You can choose to receive an e-mail every time someone reports or |
4721 | + changes a public bug associated with |
4722 | + <span tal:replace="context/title">this item</span>. |
4723 | + </p> |
4724 | + <p> |
4725 | + <strong>Important:</strong> subscribing here may mean you receive a |
4726 | + great deal of e-mail. You can return here to unsubscribe at any |
4727 | + time. |
4728 | + </p> |
4729 | + <div metal:use-macro="context/@@launchpad_form/form" /> |
4730 | + </div> |
4731 | + <div metal:fill-slot="side"> |
4732 | + <div tal:replace="structure context/@@+portlet-malone-bugmail-filtering-faq"/> |
4733 | + <div tal:condition="view/show_details_portlet" |
4734 | + tal:replace="structure context/@@+portlet-details" /> |
4735 | + <div tal:replace="structure context/@@+portlet-structural-subscribers" /> |
4736 | + </div> |
4737 | </body> |
4738 | </html> |
4739 | |
4740 | === modified file 'lib/canonical/launchpad/testing/fakepackager.py' |
4741 | --- lib/canonical/launchpad/testing/fakepackager.py 2009-06-25 05:30:52 +0000 |
4742 | +++ lib/canonical/launchpad/testing/fakepackager.py 2009-09-14 04:07:18 +0000 |
4743 | @@ -354,7 +354,7 @@ |
4744 | 'Selected upstream directory does not exist: %s' % ( |
4745 | os.path.basename(self.upstream_directory))) |
4746 | |
4747 | - debuild_options = ['-S'] |
4748 | + debuild_options = ['--no-conf', '-S'] |
4749 | |
4750 | if not signed: |
4751 | debuild_options.extend(['-uc', '-us']) |
4752 | |
4753 | === modified file 'lib/canonical/launchpad/testing/pages.py' |
4754 | --- lib/canonical/launchpad/testing/pages.py 2009-08-27 19:55:58 +0000 |
4755 | +++ lib/canonical/launchpad/testing/pages.py 2009-09-01 09:54:54 +0000 |
4756 | @@ -258,7 +258,8 @@ |
4757 | for col_num, item in enumerate(row.findAll('td')): |
4758 | if columns is None or col_num in columns: |
4759 | row_content.append(extract_text(item)) |
4760 | - print sep.join(row_content) |
4761 | + if len(row_content) > 0: |
4762 | + print sep.join(row_content) |
4763 | |
4764 | def print_radio_button_field(content, name): |
4765 | """Find the input called field.name, and print a friendly representation. |
4766 | |
4767 | === modified file 'lib/canonical/launchpad/utilities/searchservice.py' |
4768 | --- lib/canonical/launchpad/utilities/searchservice.py 2009-06-25 05:30:52 +0000 |
4769 | +++ lib/canonical/launchpad/utilities/searchservice.py 2009-09-04 10:43:39 +0000 |
4770 | @@ -13,7 +13,10 @@ |
4771 | 'PageMatches', |
4772 | ] |
4773 | |
4774 | -import cElementTree as ET |
4775 | +try: |
4776 | + import xml.etree.cElementTree as ET |
4777 | +except ImportError: |
4778 | + import cElementTree as ET |
4779 | import urllib |
4780 | from urlparse import urlunparse |
4781 | |
4782 | |
4783 | === modified file 'lib/canonical/launchpad/utilities/unicode_csv.py' |
4784 | --- lib/canonical/launchpad/utilities/unicode_csv.py 2009-06-25 05:30:52 +0000 |
4785 | +++ lib/canonical/launchpad/utilities/unicode_csv.py 2009-09-04 11:42:13 +0000 |
4786 | @@ -41,6 +41,17 @@ |
4787 | class UnicodeCSVReader: |
4788 | """A CSV reader that reads encoded files and yields unicode.""" |
4789 | |
4790 | + class DelegateLineNumAccessDescriptor: |
4791 | + """The Python 2.5 DictReader expects its reader to support access to a |
4792 | + line_num attribute, therefore to keep UnicodeCSVReader capable of being |
4793 | + used within a DictReader we provide a line_num attribute which |
4794 | + delegates to the real reader.""" |
4795 | + |
4796 | + def __get__(self, obj, type): |
4797 | + return obj.reader.line_num |
4798 | + |
4799 | + line_num = DelegateLineNumAccessDescriptor() |
4800 | + |
4801 | def __init__(self, file_, dialect=csv.excel, encoding="utf-8", **kwds): |
4802 | file_ = UTF8Recoder(file_, encoding) |
4803 | self.reader = csv.reader(file_, dialect=dialect, **kwds) |
4804 | |
4805 | === modified file 'lib/canonical/launchpad/versioninfo.py' |
4806 | --- lib/canonical/launchpad/versioninfo.py 2009-06-25 05:30:52 +0000 |
4807 | +++ lib/canonical/launchpad/versioninfo.py 2009-09-02 19:11:01 +0000 |
4808 | @@ -1,7 +1,7 @@ |
4809 | # Copyright 2009 Canonical Ltd. This software is licensed under the |
4810 | # GNU Affero General Public License version 3 (see the file LICENSE). |
4811 | |
4812 | -"""Give access to bzr version info, if available. |
4813 | +"""Give access to bzr and other version info, if available. |
4814 | |
4815 | The bzr version info file is expected to be in the Launchpad root in the |
4816 | file bzr-version-info.py. |
4817 | @@ -16,19 +16,28 @@ |
4818 | If the bzr-version-info.py file does not exist, then revno, date and |
4819 | branch_nick will all be None. |
4820 | |
4821 | -If that file exists, and contains valid python, revno, date and branch_nick |
4822 | +If that file exists, and contains valid Python, revno, date and branch_nick |
4823 | will have appropriate values from version_info. |
4824 | |
4825 | -If that file exists, and contains invalid python, there will be an error when |
4826 | +If that file exists, and contains invalid Python, there will be an error when |
4827 | this module is loaded. This module is imported into |
4828 | canonical/launchpad/__init__.py so that such errors are caught at start-up. |
4829 | |
4830 | +This module also reads version.txt at the top of the tree (i.e. a sibling of |
4831 | +bzr-version-info.py), which contains the Launchpad release number. If that |
4832 | +file does not exist, we make something up. |
4833 | """ |
4834 | |
4835 | +__all__ = [ |
4836 | + 'branch_nick', |
4837 | + 'date', |
4838 | + 'revno', |
4839 | + 'versioninfo', |
4840 | + ] |
4841 | + |
4842 | + |
4843 | import imp |
4844 | |
4845 | -__all__ = ['versioninfo', 'revno', 'date', 'branch_nick'] |
4846 | - |
4847 | |
4848 | def read_version_info(): |
4849 | try: |
4850 | @@ -52,3 +61,14 @@ |
4851 | date = versioninfo.get('date') |
4852 | branch_nick = versioninfo.get('branch_nick') |
4853 | |
4854 | + |
4855 | +try: |
4856 | + version_file = open('version.txt') |
4857 | +except IOError: |
4858 | + release = 'x.y.z' |
4859 | +else: |
4860 | + try: |
4861 | + version_data = version_file.read() |
4862 | + release = version_data.strip() |
4863 | + finally: |
4864 | + version_file.close() |
4865 | |
4866 | === modified file 'lib/canonical/launchpad/webapp/breadcrumb.py' |
4867 | --- lib/canonical/launchpad/webapp/breadcrumb.py 2009-08-25 12:35:23 +0000 |
4868 | +++ lib/canonical/launchpad/webapp/breadcrumb.py 2009-09-18 04:06:03 +0000 |
4869 | @@ -7,6 +7,9 @@ |
4870 | |
4871 | __all__ = [ |
4872 | 'Breadcrumb', |
4873 | + 'DisplaynameBreadcrumb', |
4874 | + 'NameBreadcrumb', |
4875 | + 'TitleBreadcrumb', |
4876 | ] |
4877 | |
4878 | |
4879 | @@ -27,6 +30,7 @@ |
4880 | implements(IBreadcrumb) |
4881 | |
4882 | text = None |
4883 | + _url = None |
4884 | |
4885 | def __init__(self, context): |
4886 | self.context = context |
4887 | @@ -46,20 +50,35 @@ |
4888 | |
4889 | @property |
4890 | def url(self): |
4891 | - return canonical_url(self.context, rootsite=self.rootsite) |
4892 | - |
4893 | - @property |
4894 | - def icon(self): |
4895 | - """See `IBreadcrumb`.""" |
4896 | - # Get the <img> tag from the path adapter. |
4897 | - return queryAdapter( |
4898 | - self.context, IPathAdapter, name='image').icon() |
4899 | + if self._url is None: |
4900 | + return canonical_url(self.context, rootsite=self.rootsite) |
4901 | + else: |
4902 | + return self._url |
4903 | |
4904 | def __repr__(self): |
4905 | - if self.icon is not None: |
4906 | - icon_repr = " icon='%s'" % self.icon |
4907 | - else: |
4908 | - icon_repr = "" |
4909 | - |
4910 | - return "<%s url='%s' text='%s'%s>" % ( |
4911 | - self.__class__.__name__, self.url, self.text, icon_repr) |
4912 | + return "<%s url='%s' text='%s'>" % ( |
4913 | + self.__class__.__name__, self.url, self.text) |
4914 | + |
4915 | + |
4916 | +class NameBreadcrumb(Breadcrumb): |
4917 | + """An `IBreadcrumb` that uses the context's name as its text.""" |
4918 | + |
4919 | + @property |
4920 | + def text(self): |
4921 | + return self.context.name |
4922 | + |
4923 | + |
4924 | +class DisplaynameBreadcrumb(Breadcrumb): |
4925 | + """An `IBreadcrumb` that uses the context's displayname as its text.""" |
4926 | + |
4927 | + @property |
4928 | + def text(self): |
4929 | + return self.context.displayname |
4930 | + |
4931 | + |
4932 | +class TitleBreadcrumb(Breadcrumb): |
4933 | + """An `IBreadcrumb` that uses the context's title as its text.""" |
4934 | + |
4935 | + @property |
4936 | + def text(self): |
4937 | + return self.context.title |
4938 | |
4939 | === modified file 'lib/canonical/launchpad/webapp/configure.zcml' |
4940 | --- lib/canonical/launchpad/webapp/configure.zcml 2009-08-27 09:00:17 +0000 |
4941 | +++ lib/canonical/launchpad/webapp/configure.zcml 2009-09-11 15:26:11 +0000 |
4942 | @@ -365,6 +365,13 @@ |
4943 | /> |
4944 | |
4945 | <adapter |
4946 | + for="lp.registry.interfaces.distroseries.IDistroSeries" |
4947 | + provides="zope.traversing.interfaces.IPathAdapter" |
4948 | + factory="canonical.launchpad.webapp.tales.DistroSeriesFormatterAPI" |
4949 | + name="fmt" |
4950 | + /> |
4951 | + |
4952 | + <adapter |
4953 | for="canonical.launchpad.interfaces.IBug" |
4954 | provides="zope.traversing.interfaces.IPathAdapter" |
4955 | factory="canonical.launchpad.webapp.tales.BugFormatterAPI" |
4956 | @@ -507,6 +514,24 @@ |
4957 | name="fmt" |
4958 | /> |
4959 | <adapter |
4960 | + for="lp.translations.interfaces.translationgroup.ITranslationGroup" |
4961 | + provides="zope.traversing.interfaces.IPathAdapter" |
4962 | + factory="canonical.launchpad.webapp.tales.TranslationGroupFormatterAPI" |
4963 | + name="fmt" |
4964 | + /> |
4965 | + <adapter |
4966 | + for="lp.services.worlddata.interfaces.language.ILanguage" |
4967 | + provides="zope.traversing.interfaces.IPathAdapter" |
4968 | + factory="canonical.launchpad.webapp.tales.LanguageFormatterAPI" |
4969 | + name="fmt" |
4970 | + /> |
4971 | + <adapter |
4972 | + for="lp.translations.interfaces.pofile.IPOFile" |
4973 | + provides="zope.traversing.interfaces.IPathAdapter" |
4974 | + factory="canonical.launchpad.webapp.tales.POFileFormatterAPI" |
4975 | + name="fmt" |
4976 | + /> |
4977 | + <adapter |
4978 | for="*" |
4979 | provides="zope.traversing.interfaces.IPathAdapter" |
4980 | factory="canonical.launchpad.webapp.tales.PermissionRequiredQuery" |
4981 | |
4982 | === modified file 'lib/canonical/launchpad/webapp/error.py' |
4983 | --- lib/canonical/launchpad/webapp/error.py 2009-07-17 18:46:25 +0000 |
4984 | +++ lib/canonical/launchpad/webapp/error.py 2009-09-08 22:42:42 +0000 |
4985 | @@ -2,6 +2,16 @@ |
4986 | # GNU Affero General Public License version 3 (see the file LICENSE). |
4987 | |
4988 | __metaclass__ = type |
4989 | +__all__ = [ |
4990 | + 'InvalidBatchSizeView', |
4991 | + 'NotFoundView', |
4992 | + 'ProtocolErrorView', |
4993 | + 'ReadOnlyErrorView', |
4994 | + 'RequestExpiredView', |
4995 | + 'SystemErrorView', |
4996 | + 'TranslationUnavailableView', |
4997 | + ] |
4998 | + |
4999 | |
5000 | import sys |
Hi Aaron-
First of all, I apologize for the size of this branch. I couldn't see a sane
way of splitting it up.
This branch is a long time coming. It's the re-design of the branch index
page. I'm 99% sure I got all the failing tests and fixed them (it's gone
through ec2 at least 5 times). While working on that though, I found that our
tests are far too dependent on the html layout of pages, instead of the actual
content. I plan to fix this soon by moving a lot of what we're testing out
into unittests (and kill the page tests).
There are some flakes errors being raised about not being able to import lazr
packages, but I think flakes is just stupid.
reviewer abentley
Cheers,
Paul