Merge lp:~al-maisan/launchpad/clog-oops-452070 into lp:launchpad
- clog-oops-452070
- Merge into devel
Proposed by
Muharem Hrnjadovic
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Eleanor Berger | ||||
Approved revision: | no longer in the source branch. | ||||
Merged at revision: | not available | ||||
Proposed branch: | lp:~al-maisan/launchpad/clog-oops-452070 | ||||
Merge into: | lp:launchpad | ||||
Diff against target: |
659 lines 13 files modified
lib/canonical/launchpad/webapp/tales.py (+9/-2) lib/lp/bugs/doc/bug.txt (+28/-0) lib/lp/bugs/interfaces/bug.py (+7/-0) lib/lp/bugs/model/bug.py (+10/-1) lib/lp/registry/browser/distributionsourcepackage.py (+46/-4) lib/lp/registry/configure.zcml (+1/-0) lib/lp/registry/doc/sourcepackage.txt (+10/-8) lib/lp/registry/model/distributionsourcepackage.py (+22/-1) lib/lp/soyuz/browser/configure.zcml (+0/-3) lib/lp/soyuz/browser/sourcepackagerelease.py (+117/-96) lib/lp/soyuz/stories/soyuz/xx-distributionsourcepackagerelease-pages.txt (+34/-0) lib/lp/soyuz/templates/distributionsourcepackagerelease-changes.pt (+5/-4) lib/lp/soyuz/templates/sourcepackagerelease-change-summary.pt (+0/-10) |
||||
To merge this branch: | bzr merge lp:~al-maisan/launchpad/clog-oops-452070 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Eleanor Berger (community) | Approve | ||
Review via email: mp+13789@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
Muharem Hrnjadovic (al-maisan) wrote : | # |
Revision history for this message
Eleanor Berger (intellectronica) : | # |
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'lib/canonical/launchpad/webapp/tales.py' |
2 | --- lib/canonical/launchpad/webapp/tales.py 2009-09-18 21:54:45 +0000 |
3 | +++ lib/canonical/launchpad/webapp/tales.py 2009-10-22 21:21:16 +0000 |
4 | @@ -2876,7 +2876,7 @@ |
5 | "<<email address hidden>>", "<email address hidden>") |
6 | return text |
7 | |
8 | - def linkify_email(self): |
9 | + def linkify_email(self, preloaded_person_data=None): |
10 | """Linkify any email address recognised in Launchpad. |
11 | |
12 | If an email address is recognised as one registered in Launchpad, |
13 | @@ -2891,7 +2891,14 @@ |
14 | matches = re.finditer(self._re_email, text) |
15 | for match in matches: |
16 | address = match.group() |
17 | - person = getUtility(IPersonSet).getByEmail(address) |
18 | + person = None |
19 | + # First try to find the person required in the preloaded person |
20 | + # data dictionary. |
21 | + if preloaded_person_data is not None: |
22 | + person = preloaded_person_data.get(address, None) |
23 | + else: |
24 | + # No pre-loaded data -> we need to perform a database lookup. |
25 | + person = getUtility(IPersonSet).getByEmail(address) |
26 | # Only linkify if person exists and does not want to hide |
27 | # their email addresses. |
28 | if person is not None and not person.hide_email_addresses: |
29 | |
30 | === modified file 'lib/lp/bugs/doc/bug.txt' |
31 | --- lib/lp/bugs/doc/bug.txt 2009-07-08 03:14:59 +0000 |
32 | +++ lib/lp/bugs/doc/bug.txt 2009-10-22 21:21:16 +0000 |
33 | @@ -37,6 +37,34 @@ |
34 | ... |
35 | NotFoundError: 'Unable to locate bug with nickname +bugs.' |
36 | |
37 | +It is also possible to retrieve a number of bugs by specifying the bug numbers |
38 | +of interest. |
39 | + |
40 | + >>> result_set = bugset.getByNumbers([6, 1234]) |
41 | + >>> print result_set.count() |
42 | + 1 |
43 | + |
44 | + >>> [the_bug_found] = result_set |
45 | + >>> print the_bug_found.title |
46 | + Firefox crashes when Save As dialog for a nonexistent window is closed |
47 | + |
48 | + >>> result_set = bugset.getByNumbers([6, 4321, 1]) |
49 | + >>> print result_set.count() |
50 | + 2 |
51 | + |
52 | + >>> second_bug_found = result_set[1] |
53 | + >>> print second_bug_found.title |
54 | + Firefox does not support SVG |
55 | + |
56 | +If no bug numbers are specified an empty result set is returned. |
57 | + |
58 | + >>> result_set = bugset.getByNumbers(None) |
59 | + >>> print result_set.count() |
60 | + 0 |
61 | + >>> result_set = bugset.getByNumbers([]) |
62 | + >>> print result_set.count() |
63 | + 0 |
64 | + |
65 | == Bug creation events == |
66 | |
67 | IObjectCreatedEvent events are fired off when a bug is created. First |
68 | |
69 | === modified file 'lib/lp/bugs/interfaces/bug.py' |
70 | --- lib/lp/bugs/interfaces/bug.py 2009-08-26 11:06:05 +0000 |
71 | +++ lib/lp/bugs/interfaces/bug.py 2009-10-22 21:21:16 +0000 |
72 | @@ -937,6 +937,13 @@ |
73 | :param limit: The number of distinct Bugs to return. |
74 | """ |
75 | |
76 | + def getByNumbers(bug_numbers): |
77 | + """Get `IBug` instances identified by the `bug_numbers` iterable. |
78 | + |
79 | + :param bug_numbers: An iterable of bug numbers for which we should |
80 | + return Bugs. |
81 | + """ |
82 | + |
83 | |
84 | class InvalidBugTargetType(Exception): |
85 | """Bug target's type is not valid.""" |
86 | |
87 | === modified file 'lib/lp/bugs/model/bug.py' |
88 | --- lib/lp/bugs/model/bug.py 2009-08-26 01:54:39 +0000 |
89 | +++ lib/lp/bugs/model/bug.py 2009-10-22 21:21:16 +0000 |
90 | @@ -33,7 +33,7 @@ |
91 | from sqlobject import SQLMultipleJoin, SQLRelatedJoin |
92 | from sqlobject import SQLObjectNotFound |
93 | from storm.expr import And, Count, In, LeftJoin, Select, SQLRaw, Func |
94 | -from storm.store import Store |
95 | +from storm.store import EmptyResultSet, Store |
96 | |
97 | from lazr.lifecycle.event import ( |
98 | ObjectCreatedEvent, ObjectDeletedEvent, ObjectModifiedEvent) |
99 | @@ -49,6 +49,7 @@ |
100 | from canonical.launchpad.interfaces.hwdb import IHWSubmissionBugSet |
101 | from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities |
102 | from canonical.launchpad.interfaces.librarian import ILibraryFileAliasSet |
103 | +from canonical.launchpad.interfaces.lpstorm import IStore |
104 | from canonical.launchpad.interfaces.message import ( |
105 | IMessage, IndexedMessage) |
106 | from canonical.launchpad.interfaces.structuralsubscription import ( |
107 | @@ -1627,6 +1628,14 @@ |
108 | |
109 | return bugs |
110 | |
111 | + def getByNumbers(self, bug_numbers): |
112 | + """see `IBugSet`.""" |
113 | + if bug_numbers is None or len(bug_numbers) < 1: |
114 | + return EmptyResultSet() |
115 | + store = IStore(Bug) |
116 | + result_set = store.find(Bug, In(Bug.id, bug_numbers)) |
117 | + return result_set |
118 | + |
119 | |
120 | class BugAffectsPerson(SQLBase): |
121 | """A bug is marked as affecting a user.""" |
122 | |
123 | === modified file 'lib/lp/registry/browser/distributionsourcepackage.py' |
124 | --- lib/lp/registry/browser/distributionsourcepackage.py 2009-10-20 17:57:51 +0000 |
125 | +++ lib/lp/registry/browser/distributionsourcepackage.py 2009-10-22 21:21:16 +0000 |
126 | @@ -27,6 +27,7 @@ |
127 | |
128 | from canonical.cachedproperty import cachedproperty |
129 | from canonical.launchpad import _ |
130 | +from canonical.launchpad.interfaces import IBugSet |
131 | from lp.answers.interfaces.questionenums import QuestionStatus |
132 | from lp.soyuz.interfaces.archive import IArchiveSet |
133 | from lp.soyuz.interfaces.distributionsourcepackagerelease import ( |
134 | @@ -49,6 +50,8 @@ |
135 | from canonical.launchpad.webapp.breadcrumb import Breadcrumb |
136 | |
137 | from lazr.delegates import delegates |
138 | +from lp.soyuz.browser.sourcepackagerelease import ( |
139 | + extract_bug_numbers, extract_email_addresses, linkify_changelog) |
140 | from canonical.lazr.utils import smartquote |
141 | |
142 | |
143 | @@ -134,11 +137,14 @@ |
144 | """ |
145 | delegates(IDistributionSourcePackageRelease, 'context') |
146 | |
147 | - def __init__(self, distributionsourcepackagerelease, |
148 | - publishing_history, package_diffs): |
149 | + def __init__( |
150 | + self, distributionsourcepackagerelease, publishing_history, |
151 | + package_diffs, person_data, user): |
152 | self.context = distributionsourcepackagerelease |
153 | self._publishing_history = publishing_history |
154 | self._package_diffs = package_diffs |
155 | + self._person_data = person_data |
156 | + self._user = user |
157 | |
158 | @property |
159 | def publishing_history(self): |
160 | @@ -150,6 +156,12 @@ |
161 | """ See `ISourcePackageRelease`.""" |
162 | return self._package_diffs |
163 | |
164 | + @property |
165 | + def change_summary(self): |
166 | + """ See `ISourcePackageRelease`.""" |
167 | + return linkify_changelog( |
168 | + self._user, self.context.change_summary, self._person_data) |
169 | + |
170 | |
171 | class IDistributionSourcePackageActionMenu(Interface): |
172 | """Marker interface for the action menu.""" |
173 | @@ -172,14 +184,43 @@ |
174 | """Common features to all `DistributionSourcePackage` views.""" |
175 | |
176 | def releases(self): |
177 | + def not_empty(text): |
178 | + return ( |
179 | + text is not None and isinstance(text, basestring) |
180 | + and len(text.strip()) > 0) |
181 | + |
182 | dspr_pubs = self.context.getReleasesAndPublishingHistory() |
183 | |
184 | # Return as early as possible to avoid unnecessary processing. |
185 | if len(dspr_pubs) == 0: |
186 | return [] |
187 | |
188 | + sprs = [dspr.sourcepackagerelease for (dspr, spphs) in dspr_pubs] |
189 | + # Pre-load the bugs and persons referenced by the +changelog page from |
190 | + # the database. |
191 | + # This will improve the performance of the ensuing changelog |
192 | + # linkification. |
193 | + the_changelog = '\n'.join( |
194 | + [spr.changelog_entry for spr in sprs |
195 | + if not_empty(spr.changelog_entry)]) |
196 | + unique_bugs = extract_bug_numbers(the_changelog) |
197 | + self._bug_data = list( |
198 | + getUtility(IBugSet).getByNumbers(unique_bugs.keys())) |
199 | + # Preload email/person data only if user is logged on. In the opposite |
200 | + # case the emails in the changelog will be obfuscated anyway and thus |
201 | + # cause no database lookups. |
202 | + if self.user: |
203 | + unique_emails = extract_email_addresses(the_changelog) |
204 | + # The method below returns a [(EmailAddress,Person]] result set. |
205 | + result_set = self.context.getPersonsByEmail(unique_emails) |
206 | + # Ignore the persons who want their email addresses hidden. |
207 | + self._person_data = dict( |
208 | + [(email.email,person) for (email,person) in result_set |
209 | + if not person.hide_email_addresses]) |
210 | + else: |
211 | + self._person_data = None |
212 | + |
213 | # Collate diffs for relevant SourcePackageReleases |
214 | - sprs = [dspr.sourcepackagerelease for (dspr, spphs) in dspr_pubs] |
215 | pkg_diffs = getUtility(IPackageDiffSet).getDiffsToReleases(sprs) |
216 | spr_diffs = {} |
217 | for spr, diffs in itertools.groupby(pkg_diffs, |
218 | @@ -188,7 +229,8 @@ |
219 | |
220 | return [ |
221 | DecoratedDistributionSourcePackageRelease( |
222 | - dspr, spphs, spr_diffs.get(dspr.sourcepackagerelease, [])) |
223 | + dspr, spphs, spr_diffs.get(dspr.sourcepackagerelease, []), |
224 | + self._person_data, self.user) |
225 | for (dspr, spphs) in dspr_pubs] |
226 | |
227 | |
228 | |
229 | === modified file 'lib/lp/registry/configure.zcml' |
230 | --- lib/lp/registry/configure.zcml 2009-10-22 09:24:53 +0000 |
231 | +++ lib/lp/registry/configure.zcml 2009-10-22 21:21:16 +0000 |
232 | @@ -392,6 +392,7 @@ |
233 | getSubscription |
234 | parent_subscription_target |
235 | getBugNotificationsRecipients |
236 | + getPersonsByEmail |
237 | getReleasesAndPublishingHistory |
238 | upstream_product |
239 | target_type_display |
240 | |
241 | === modified file 'lib/lp/registry/doc/sourcepackage.txt' |
242 | --- lib/lp/registry/doc/sourcepackage.txt 2009-10-19 21:20:31 +0000 |
243 | +++ lib/lp/registry/doc/sourcepackage.txt 2009-10-22 21:21:16 +0000 |
244 | @@ -370,32 +370,34 @@ |
245 | |
246 | Zero or more spaces may appear between the ":" and the "#". |
247 | |
248 | - >>> spr_view._linkify_bug_numbers("LP: #10") |
249 | + >>> from lp.soyuz.browser.sourcepackagerelease import ( |
250 | + ... linkify_bug_numbers) |
251 | + >>> linkify_bug_numbers("LP: #10") |
252 | u'LP: <a href="/bugs/10" title="another test bug">#10</a>' |
253 | |
254 | - >>> spr_view._linkify_bug_numbers("LP: #10") |
255 | + >>> linkify_bug_numbers("LP: #10") |
256 | u'LP: <a href="/bugs/10" title="another test bug">#10</a>' |
257 | |
258 | - >>> spr_view._linkify_bug_numbers("LP:#10") |
259 | + >>> linkify_bug_numbers("LP:#10") |
260 | u'LP:<a href="/bugs/10" title="another test bug">#10</a>' |
261 | |
262 | - >>> spr_view._linkify_bug_numbers("LP: #999") |
263 | + >>> linkify_bug_numbers("LP: #999") |
264 | 'LP: <a href="/bugs/999" title="No such bug">#999</a>' |
265 | |
266 | - >>> spr_view._linkify_bug_numbers("garbage") |
267 | + >>> linkify_bug_numbers("garbage") |
268 | 'garbage' |
269 | |
270 | - >>> spr_view._linkify_bug_numbers("LP: #10, #7") |
271 | + >>> linkify_bug_numbers("LP: #10, #7") |
272 | u'LP: <a href="/bugs/10" title="another test bug">#10</a>, |
273 | <a href="/bugs/7" title="A test bug">#7</a>' |
274 | |
275 | - >>> spr_view._linkify_bug_numbers("LP: #10 LP: #10") |
276 | + >>> linkify_bug_numbers("LP: #10 LP: #10") |
277 | u'LP: <a href="/bugs/10" title="another test bug">#10</a> |
278 | LP: <a href="/bugs/10" title="another test bug">#10</a>' |
279 | |
280 | The regex is case-insensitive, so "lp: #nnn" also works. |
281 | |
282 | - >>> spr_view._linkify_bug_numbers("lp: #10") |
283 | + >>> linkify_bug_numbers("lp: #10") |
284 | u'lp: <a href="/bugs/10" title="another test bug">#10</a>' |
285 | |
286 | |
287 | |
288 | === modified file 'lib/lp/registry/model/distributionsourcepackage.py' |
289 | --- lib/lp/registry/model/distributionsourcepackage.py 2009-09-16 04:31:39 +0000 |
290 | +++ lib/lp/registry/model/distributionsourcepackage.py 2009-10-22 21:21:16 +0000 |
291 | @@ -15,7 +15,8 @@ |
292 | import operator |
293 | |
294 | from sqlobject.sqlbuilder import SQLConstant |
295 | -from storm.expr import And, Desc, In |
296 | +from storm.expr import And, Desc, In, Join, Lower |
297 | +from storm.store import EmptyResultSet |
298 | from storm.locals import Int, Reference, Store, Storm, Unicode |
299 | from zope.interface import implements |
300 | |
301 | @@ -36,10 +37,13 @@ |
302 | from lp.soyuz.model.sourcepackagerelease import ( |
303 | SourcePackageRelease) |
304 | from lp.registry.model.karma import KarmaTotalCache |
305 | +from lp.registry.model.person import Person |
306 | from lp.registry.model.sourcepackage import ( |
307 | SourcePackage, SourcePackageQuestionTargetMixin) |
308 | +from canonical.launchpad.database.emailaddress import EmailAddress |
309 | from canonical.launchpad.database.structuralsubscription import ( |
310 | StructuralSubscriptionTargetMixin) |
311 | +from canonical.launchpad.interfaces.lpstorm import IStore |
312 | |
313 | from canonical.lazr.utils import smartquote |
314 | |
315 | @@ -408,6 +412,23 @@ |
316 | 'BugTask.distribution = %s AND BugTask.sourcepackagename = %s' % |
317 | sqlvalues(self.distribution, self.sourcepackagename)) |
318 | |
319 | + @staticmethod |
320 | + def getPersonsByEmail(email_addresses): |
321 | + """[(EmailAddress,Person), ..] iterable for given email addresses.""" |
322 | + if email_addresses is None or len(email_addresses) < 1: |
323 | + return EmptyResultSet() |
324 | + # Perform basic sanitization of email addresses. |
325 | + email_addresses = [ |
326 | + address.lower().strip() for address in email_addresses] |
327 | + store = IStore(Person) |
328 | + origin = [ |
329 | + Person, Join(EmailAddress, EmailAddress.personID == Person.id)] |
330 | + # Get all persons whose email addresses are in the list. |
331 | + result_set = store.using(*origin).find( |
332 | + (EmailAddress, Person), |
333 | + In(Lower(EmailAddress.email), email_addresses)) |
334 | + return result_set |
335 | + |
336 | |
337 | class DistributionSourcePackageInDatabase(Storm): |
338 | """Temporary class to allow access to the database.""" |
339 | |
340 | === modified file 'lib/lp/soyuz/browser/configure.zcml' |
341 | --- lib/lp/soyuz/browser/configure.zcml 2009-09-29 07:21:40 +0000 |
342 | +++ lib/lp/soyuz/browser/configure.zcml 2009-10-22 21:21:16 +0000 |
343 | @@ -112,9 +112,6 @@ |
344 | name="+files" |
345 | facet="overview" |
346 | template="../templates/sourcepackagerelease-files.pt"/> |
347 | - <browser:page |
348 | - name="+change_summary" |
349 | - template="../templates/sourcepackagerelease-change-summary.pt"/> |
350 | </browser:pages> |
351 | <facet |
352 | facet="overview"> |
353 | |
354 | === modified file 'lib/lp/soyuz/browser/sourcepackagerelease.py' |
355 | --- lib/lp/soyuz/browser/sourcepackagerelease.py 2009-09-09 11:52:55 +0000 |
356 | +++ lib/lp/soyuz/browser/sourcepackagerelease.py 2009-10-22 21:21:16 +0000 |
357 | @@ -16,108 +16,129 @@ |
358 | from canonical.launchpad.webapp.tales import FormattersAPI |
359 | |
360 | |
361 | +def extract_bug_numbers(text): |
362 | + '''Unique bug numbers matching the "LP: #n(, #n)*" pattern in the text.''' |
363 | + # FormattersAPI._linkify_substitution requires a match object |
364 | + # that has named groups "bug" and "bugnum". The matching text for |
365 | + # the "bug" group is used as the link text and "bugnum" forms part |
366 | + # of the URL for the link to the bug. Example: |
367 | + # >>> bm.groupdict( ) |
368 | + # {'bugnum': '400686', 'bug': '#400686'} |
369 | + |
370 | + # We need to match bug numbers of the form: |
371 | + # LP: #1, #2, #3 |
372 | + # #4, #5 |
373 | + # over multiple lines. |
374 | + # |
375 | + # Writing a single catch-all regex for this has proved rather hard |
376 | + # so I am taking the strategy of matching LP:(group) first, and |
377 | + # feeding the result into another regex to pull out the bug and |
378 | + # bugnum groups. |
379 | + unique_bug_matches = dict() |
380 | + |
381 | + line_matches = re.finditer( |
382 | + 'LP:\s*(?P<buglist>(.+?[^,]))($|\n)', text, |
383 | + re.DOTALL | re.IGNORECASE) |
384 | + |
385 | + for line_match in line_matches: |
386 | + bug_matches = re.finditer( |
387 | + '\s*((?P<bug>#(?P<bugnum>\d+)),?\s*)', |
388 | + line_match.group('buglist')) |
389 | + |
390 | + for bug_match in bug_matches: |
391 | + bugnum = bug_match.group('bugnum') |
392 | + if bugnum in unique_bug_matches: |
393 | + # We got this bug already, ignore it. |
394 | + continue |
395 | + unique_bug_matches[bugnum] = bug_match |
396 | + |
397 | + return unique_bug_matches |
398 | + |
399 | + |
400 | +def linkify_bug_numbers(text): |
401 | + """Linkify to a bug if LP: #number appears in the (changelog) text.""" |
402 | + unique_bug_matches = extract_bug_numbers(text) |
403 | + for bug_match in unique_bug_matches.values(): |
404 | + replace_text = bug_match.group('bug') |
405 | + if replace_text is not None: |
406 | + # XXX julian 2008-01-10 |
407 | + # Note that re.sub would be far more efficient to use |
408 | + # instead of string.replace() but this requires a regex |
409 | + # that matches everything in one go. We're also at danger |
410 | + # of replacing the wrong thing if string.replace() finds |
411 | + # other matching substrings. So for example in the |
412 | + # string: |
413 | + # "LP: #9, #999" |
414 | + # replacing #9 with some HTML would also interfere with |
415 | + # #999. The liklihood of this happening is very, very |
416 | + # small, however. |
417 | + text = text.replace( |
418 | + replace_text, |
419 | + FormattersAPI._linkify_substitution(bug_match)) |
420 | + return text |
421 | + |
422 | + |
423 | +def extract_email_addresses(text): |
424 | + '''Unique email adresses in the text.''' |
425 | + matches = re.finditer(FormattersAPI._re_email, text) |
426 | + return list(set([match.group() for match in matches])) |
427 | + |
428 | + |
429 | +def obfuscate_email(user, text): |
430 | + """Obfuscate email addresses if the user is not logged in.""" |
431 | + if not text: |
432 | + # If there is nothing to obfuscate, the FormattersAPI |
433 | + # will blow up, so just return. |
434 | + return text |
435 | + formatter = FormattersAPI(text) |
436 | + if user: |
437 | + return text |
438 | + else: |
439 | + return formatter.obfuscate_email() |
440 | + |
441 | + |
442 | +def linkify_email(text, preloaded_person_data): |
443 | + """Email addresses are linkified to point to the person's profile.""" |
444 | + formatter = FormattersAPI(text) |
445 | + return formatter.linkify_email(preloaded_person_data) |
446 | + |
447 | + |
448 | +def linkify_changelog(user, changelog, preloaded_person_data=None): |
449 | + """Linkify the changelog. |
450 | + |
451 | + This obfuscates email addresses to anonymous users, linkifies |
452 | + them for non-anonymous and links to the bug page for any bug |
453 | + numbers mentioned. |
454 | + """ |
455 | + if changelog is None: |
456 | + return '' |
457 | + |
458 | + # Remove any email addresses if the user is not logged in. |
459 | + changelog = obfuscate_email(user, changelog) |
460 | + |
461 | + # CGI Escape the changelog here before further replacements |
462 | + # insert HTML. Email obfuscation does not insert HTML but can insert |
463 | + # characters that must be escaped. |
464 | + changelog = cgi.escape(changelog) |
465 | + |
466 | + # Any email addresses remaining in the changelog were not obfuscated, |
467 | + # so we linkify them here. |
468 | + changelog = linkify_email(changelog, preloaded_person_data) |
469 | + |
470 | + # Ensure any bug numbers are linkified to the bug page. |
471 | + changelog = linkify_bug_numbers(changelog) |
472 | + |
473 | + return changelog |
474 | + |
475 | + |
476 | class SourcePackageReleaseView(LaunchpadView): |
477 | |
478 | @property |
479 | def changelog_entry(self): |
480 | """Return a linkified changelog entry.""" |
481 | - return self._linkify_changelog(self.context.changelog_entry) |
482 | + return linkify_changelog(self.user, self.context.changelog_entry) |
483 | |
484 | @property |
485 | def change_summary(self): |
486 | """Return a linkified change summary.""" |
487 | - return self._linkify_changelog(self.context.change_summary) |
488 | - |
489 | - def _obfuscate_email(self, text): |
490 | - """Obfuscate email addresses if the user is not logged in.""" |
491 | - if not text: |
492 | - # If there is nothing to obfuscate, the FormattersAPI |
493 | - # will blow up, so just return. |
494 | - return text |
495 | - formatter = FormattersAPI(text) |
496 | - if self.user: |
497 | - return text |
498 | - else: |
499 | - return formatter.obfuscate_email() |
500 | - |
501 | - def _linkify_email(self, text): |
502 | - """Email addresses are linkified to point to the person's profile.""" |
503 | - formatter = FormattersAPI(text) |
504 | - return formatter.linkify_email() |
505 | - |
506 | - def _linkify_bug_numbers(self, changelog): |
507 | - """Linkify to a bug if LP: #number appears in the changelog text.""" |
508 | - # FormattersAPI._linkify_substitution requires a match object |
509 | - # that has named groups "bug" and "bugnum". The matching text for |
510 | - # the "bug" group is used as the link text and "bugnum" forms part |
511 | - # of the URL for the link to the bug. |
512 | - |
513 | - # We need to match bug numbers of the form: |
514 | - # LP: #1, #2, #3 |
515 | - # #4, #5 |
516 | - # over multiple lines. |
517 | - # |
518 | - # Writing a single catch-all regex for this has proved rather hard |
519 | - # so I am taking the strategy of matching LP:(group) first, and |
520 | - # feeding the result into another regex to pull out the bug and |
521 | - # bugnum groups. |
522 | - line_matches = re.finditer( |
523 | - 'LP:\s*(?P<buglist>(.+?[^,]))($|\n)', changelog, |
524 | - re.DOTALL | re.IGNORECASE) |
525 | - seen_bugnums = set() |
526 | - for line_match in line_matches: |
527 | - bug_matches = re.finditer( |
528 | - '\s*((?P<bug>#(?P<bugnum>\d+)),?\s*)', |
529 | - line_match.group('buglist')) |
530 | - for bug_match in bug_matches: |
531 | - bugnum = bug_match.group('bug') |
532 | - if bugnum in seen_bugnums: |
533 | - # This bug was already replaced across the entire text of |
534 | - # the changelog. |
535 | - continue |
536 | - seen_bugnums.add(bugnum) |
537 | - replace_text = bug_match.group('bug') |
538 | - if replace_text is not None: |
539 | - # XXX julian 2008-01-10 |
540 | - # Note that re.sub would be far more efficient to use |
541 | - # instead of string.replace() but this requires a regex |
542 | - # that matches everything in one go. We're also at danger |
543 | - # of replacing the wrong thing if string.replace() finds |
544 | - # other matching substrings. So for example in the |
545 | - # string: |
546 | - # "LP: #9, #999" |
547 | - # replacing #9 with some HTML would also interfere with |
548 | - # #999. The liklihood of this happening is very, very |
549 | - # small, however. |
550 | - changelog = changelog.replace( |
551 | - replace_text, |
552 | - FormattersAPI._linkify_substitution(bug_match)) |
553 | - return changelog |
554 | - |
555 | - def _linkify_changelog(self, changelog): |
556 | - """Linkify the changelog. |
557 | - |
558 | - This obfuscates email addresses to anonymous users, linkifies |
559 | - them for non-anonymous and links to the bug page for any bug |
560 | - numbers mentioned. |
561 | - """ |
562 | - if changelog is None: |
563 | - return '' |
564 | - |
565 | - # Remove any email addresses if the user is not logged in. |
566 | - changelog = self._obfuscate_email(changelog) |
567 | - |
568 | - # CGI Escape the changelog here before further replacements |
569 | - # insert HTML. Email obfuscation does not insert HTML but can insert |
570 | - # characters that must be escaped. |
571 | - changelog = cgi.escape(changelog) |
572 | - |
573 | - # Any email addresses remaining in the changelog were not obfuscated, |
574 | - # so we linkify them here. |
575 | - changelog = self._linkify_email(changelog) |
576 | - |
577 | - # Ensure any bug numbers are linkified to the bug page. |
578 | - changelog = self._linkify_bug_numbers(changelog) |
579 | - |
580 | - return changelog |
581 | - |
582 | + return linkify_changelog(self.user, self.context.change_summary) |
583 | |
584 | === modified file 'lib/lp/soyuz/stories/soyuz/xx-distributionsourcepackagerelease-pages.txt' |
585 | --- lib/lp/soyuz/stories/soyuz/xx-distributionsourcepackagerelease-pages.txt 2009-09-23 14:58:12 +0000 |
586 | +++ lib/lp/soyuz/stories/soyuz/xx-distributionsourcepackagerelease-pages.txt 2009-10-22 21:21:16 +0000 |
587 | @@ -270,3 +270,37 @@ |
588 | Binary packages built by this source |
589 | foo-bin: Foo app is great |
590 | Well ... it does nothing, though |
591 | + |
592 | +The full change log can be viewed as follows: |
593 | + |
594 | + >>> user_browser.open( |
595 | + ... 'http://launchpad.dev/ubuntutest/+source/testing-dspr') |
596 | + >>> user_browser.getLink('View full change log').click() |
597 | + >>> print user_browser.url |
598 | + http://launchpad.dev/ubuntutest/+source/testing-dspr/+changelog |
599 | + |
600 | + >>> print_tag_with_id(user_browser.contents, 'body_testing-dspr_1.0') |
601 | + Testing!!! |
602 | + biscuit@canonical.com |
603 | + LP: #1234 |
604 | + celso.providelo@canonical.com |
605 | + |
606 | + >>> print extract_text(find_main_content(user_browser.contents)) |
607 | + Change log for “testing-dspr” package in ubuntutest |
608 | + ubuntutest |
609 | + “testing-dspr” package |
610 | + Change log |
611 | + 0.9 |
612 | + Pending in breezy-autotest-release |
613 | + since ... |
614 | + None |
615 | + 1.0 |
616 | + Published in breezy-autotest-release |
617 | + 2 seconds ago |
618 | + Testing!!! |
619 | + biscuit@canonical.com |
620 | + LP: #1234 |
621 | + celso.providelo@canonical.com |
622 | + Available diffs |
623 | + 0.9 to 1.0 |
624 | + (7 bytes) |
625 | |
626 | === modified file 'lib/lp/soyuz/templates/distributionsourcepackagerelease-changes.pt' |
627 | --- lib/lp/soyuz/templates/distributionsourcepackagerelease-changes.pt 2009-07-17 17:59:07 +0000 |
628 | +++ lib/lp/soyuz/templates/distributionsourcepackagerelease-changes.pt 2009-10-22 21:21:16 +0000 |
629 | @@ -20,10 +20,11 @@ |
630 | </div> |
631 | |
632 | <div class="boardCommentBody"> |
633 | - <div |
634 | - tal:attributes="id string:body_${context/name}_${context/version}" |
635 | - tal:content="structure context/sourcepackagerelease/@@+change_summary" /> |
636 | + <div tal:attributes="id string:body_${context/name}_${context/version}"> |
637 | + <pre style="margin: 0 0 0.3em 0" |
638 | + tal:attributes="id string:${context/name}_${context/version}" |
639 | + tal:content="structure context/change_summary" /> |
640 | + </div> |
641 | <tal:diffs replace="structure context/@@+diffs" /> |
642 | </div> |
643 | - |
644 | </div> |
645 | |
646 | === removed file 'lib/lp/soyuz/templates/sourcepackagerelease-change-summary.pt' |
647 | --- lib/lp/soyuz/templates/sourcepackagerelease-change-summary.pt 2009-07-17 17:59:07 +0000 |
648 | +++ lib/lp/soyuz/templates/sourcepackagerelease-change-summary.pt 1970-01-01 00:00:00 +0000 |
649 | @@ -1,10 +0,0 @@ |
650 | -<tal:root |
651 | - xmlns:tal="http://xml.zope.org/namespaces/tal" |
652 | - xmlns:metal="http://xml.zope.org/namespaces/metal" |
653 | - xmlns:i18n="http://xml.zope.org/namespaces/i18n" |
654 | - omit-tag=""> |
655 | - |
656 | -<pre style="margin: 0 0 0.3em 0" |
657 | - tal:attributes="id string:${context/name}_${context/version}" |
658 | - tal:content="structure view/change_summary" /> |
659 | -</tal:root> |
Hello there!
The branch at hand optimises pages with a long changelog/history like:
https:/ /launchpad. net/ubuntu/ +source/ linux/+ changelog
These pages time out on a regular basis due to the number of database
queries issued in order to fetch the bugs/persons needed for the so
called "linkification".
You can see what such a page looks like by following the link below
(since it's less prone to time out).
https:/ /launchpad. net/ubuntu/ +source/ bash/+changelog
The optimisations introduced are twofold:
- the numbers of the bugs that appear on the page are all extracted PI.linkify_ email() in order to avoid many/repeated
and a *single* database query that pre-loads them into the storm
cache is issued before the linkification starts.
- Analogously, the email addresses are extracted from the page and an
email address/person cache is provided to
FormattersA
database lookups.
Please note that
- the bulk of the code was merely refactored i.e. most of the private ageReleaseView` methods have been converted into
`SourcePack
functions so they can be reused.
- Deryck consented to having the getByNumbers() method added to IBugSet.
Pre-implementation calls with bigjools and jtv.
Tests to run:
bin/test -v -t xx-distribution sourcepackagere lease-pages. txt -t sourcepackage
More importantly, this branch was tested thoroughly on the Soyuz dogfood
system. The resulting improvements are as follows:
- the number of database queries was reduced by approx. 66%
- the overall page loading time was reduced by approx. 33%