launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #00377
[Merge] lp:~adeuring/launchpad/bug-39674-use-proxied-lfa into lp:launchpad
Abel Deuring has proposed merging lp:~adeuring/launchpad/bug-39674-use-proxied-lfa into lp:launchpad.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
This branch is one step to fix bug 39674: Attachments of private bugreports are public
It adds a navigation class for IBugAttachment that allows to traverse URLs like https://bugs.launchpad.dev/firefox/+bug/1/+attachment/1/+files/foo.txt ; these URLs will return Librarian files via StreamOrRedirectLibraryFileAliasView.
If the flag "restricted" of a LibraryFileAlias is set, this view returns the content of the Librarian file if the user can view the parent object of the Lirarian file, i.e., the bug attachment. Otherwise, it redirects to the public Librarian URL.
The branch also replaces the "simple" Librarian URLs (http://launchpadlibrarian.net/...) on the main bug pages by the new URLs; the new URLs are generated by instances of ProxiedLibraryFileAlias. (There are a few more places where download URLs of bug attachments are shown, like the "+patches" view or bug change notification mails. I'll the change these URLs in a later branch.)
I will merge this branch into db-devel: The next branch (actually setting the "restricted" flag of Librarian files for private bugs) would break the access to restricted Librarian files on production, so there is no reason to merge this branch to devel.
tests:
bin/test -t test_bugattachment_file_access
is the main unit test for the new way to access the content of bug attachments, but the branch affects a number of other tests too, see the diff.
(A note about the somewhat convoluted way to set the "restricted" flag in test_access_to_restircted_file():
+ lfa_with_parent = getMultiAdapter(
+ (self.bugattachment.libraryfile, self.bugattachment),
+ ILibraryFileAliasWithParent)
+ lfa_with_parent.restricted = True
This flag should be automatically flipped when the "private" flag of a bug is flipped; I will add the necessary code to Bug.setPrivate() in another branch.)
= Launchpad lint =
Checking for conflicts and issues in changed files.
Linting changed files:
lib/lp/bugs/browser/bug.py
lib/lp/bugs/browser/bugattachment.py
lib/lp/bugs/browser/bugcomment.py
lib/lp/bugs/browser/configure.zcml
lib/lp/bugs/browser/tests/bug-views.txt
lib/lp/bugs/browser/tests/test_bugattachment_file_access.py
lib/lp/bugs/browser/tests/test_bugview.py
lib/lp/bugs/doc/bugattachments.txt
lib/lp/bugs/interfaces/bugattachment.py
lib/lp/bugs/model/bugattachment.py
lib/lp/bugs/stories/bugattachments/10-add-bug-attachment.txt
lib/lp/bugs/stories/bugattachments/20-edit-bug-attachment.txt
lib/lp/bugs/stories/bugattachments/xx-attachments-to-bug-report.txt
lib/lp/bugs/stories/bugattachments/xx-delete-bug-attachment.txt
lib/lp/bugs/stories/guided-filebug/xx-filebug-attachments.txt
lib/lp/bugs/templates/bug-portlet-attachments.pt
lib/lp/bugs/templates/bugcomment-box.pt
./lib/lp/bugs/browser/bug.py
483: E202 whitespace before ']'
496: E202 whitespace before ']'
989: E302 expected 2 blank lines, found 1
./lib/lp/bugs/browser/tests/bug-views.txt
0: narrative uses a moin header.
2: narrative uses a moin header.
114: narrative uses a moin header.
157: want exceeds 78 characters.
161: narrative uses a moin header.
212: narrative uses a moin header.
214: narrative uses a moin header.
262: narrative uses a moin header.
298: narrative uses a moin header.
426: narrative uses a moin header.
655: want has trailing whitespace.
691: narrative uses a moin header.
791: narrative uses a moin header.
941: narrative uses a moin header.
./lib/lp/bugs/stories/bugattachments/10-add-bug-attachment.txt
3: source has bad indentation.
11: source has bad indentation.
16: source has bad indentation.
22: source has bad indentation.
25: source exceeds 78 characters.
32: source has bad indentation.
37: source has bad indentation.
42: source has bad indentation.
46: source has bad indentation.
51: source has bad indentation.
59: source has bad indentation.
70: source has bad indentation.
78: source has bad indentation.
84: source has bad indentation.
100: source has bad indentation.
115: source has bad indentation.
121: source has bad indentation.
129: source has bad indentation.
139: source has bad indentation.
145: source has bad indentation.
157: source has bad indentation.
171: source has bad indentation.
177: source has bad indentation.
184: source has bad indentation.
194: source has bad indentation.
199: source has bad indentation.
204: source has bad indentation.
214: source has bad indentation.
229: source has bad indentation.
244: source has bad indentation.
./lib/lp/bugs/stories/bugattachments/xx-attachments-to-bug-report.txt
0: narrative uses a moin header.
54: narrative uses a moin header.
73: want has trailing whitespace.
./lib/lp/bugs/stories/bugattachments/xx-delete-bug-attachment.txt
0: narrative uses a moin header.
./lib/lp/bugs/stories/guided-filebug/xx-filebug-attachments.txt
0: narrative uses a moin header.
7: narrative uses a moin header.
53: narrative uses a moin header.
63: source exceeds 78 characters.
I did not bother to fix all the tests I touched; the lint complaints for lib/lp/bugs/browser/bug.py are wrong: We need whitespace before the closing bracket of multi-line list definitions, if my memory about our conding standards is right. And I think that the empty lines 984, 985 are what "make lint" is missing.
--
https://code.launchpad.net/~adeuring/launchpad/bug-39674-use-proxied-lfa/+merge/31539
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~adeuring/launchpad/bug-39674-use-proxied-lfa into lp:launchpad.
=== modified file 'lib/lp/bugs/browser/bug.py'
--- lib/lp/bugs/browser/bug.py 2010-07-22 12:17:41 +0000
+++ lib/lp/bugs/browser/bug.py 2010-08-02 13:45:58 +0000
@@ -49,6 +49,7 @@
from canonical.cachedproperty import cachedproperty
from canonical.launchpad import _
+from canonical.launchpad.browser.librarian import ProxiedLibraryFileAlias
from canonical.launchpad.webapp.interfaces import ILaunchBag, NotFoundError
from lp.bugs.interfaces.bug import IBug, IBugSet
from lp.bugs.interfaces.bugattachment import BugAttachmentType
@@ -472,16 +473,28 @@
@property
def regular_attachments(self):
"""The list of bug attachments that are not patches."""
- return [attachment
- for attachment in self.context.attachments
- if attachment.type != BugAttachmentType.PATCH]
+ return [
+ {
+ 'attachment': attachment,
+ 'file': ProxiedLibraryFileAlias(
+ attachment.libraryfile, attachment),
+ }
+ for attachment in self.context.attachments
+ if attachment.type != BugAttachmentType.PATCH
+ ]
@property
def patches(self):
"""The list of bug attachments that are patches."""
- return [attachment
- for attachment in self.context.attachments
- if attachment.type == BugAttachmentType.PATCH]
+ return [
+ {
+ 'attachment': attachment,
+ 'file': ProxiedLibraryFileAlias(
+ attachment.libraryfile, attachment),
+ }
+ for attachment in self.context.attachments
+ if attachment.type == BugAttachmentType.PATCH
+ ]
class BugView(LaunchpadView, BugViewMixin):
=== modified file 'lib/lp/bugs/browser/bugattachment.py'
--- lib/lp/bugs/browser/bugattachment.py 2010-03-30 20:56:51 +0000
+++ lib/lp/bugs/browser/bugattachment.py 2010-08-02 13:45:58 +0000
@@ -1,5 +1,4 @@
-
-# Copyright 2009 Canonical Ltd. This software is licensed under the
+# Copyright 2010 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Bug attachment views."""
@@ -7,6 +6,7 @@
__metaclass__ = type
__all__ = [
'BugAttachmentContentCheck',
+ 'BugAttachmentFileNavigation',
'BugAttachmentSetNavigation',
'BugAttachmentEditView',
'BugAttachmentURL',
@@ -18,8 +18,9 @@
from zope.component import getUtility
from zope.contenttype import guess_content_type
+from canonical.launchpad.browser.librarian import FileNavigationMixin
from canonical.launchpad.webapp import (
- canonical_url, custom_widget, GetitemNavigation)
+ canonical_url, custom_widget, GetitemNavigation, Navigation)
from canonical.launchpad.interfaces.librarian import ILibraryFileAliasSet
from canonical.launchpad.webapp.interfaces import ILaunchBag
from lp.bugs.interfaces.bugattachment import (
@@ -34,6 +35,8 @@
from canonical.widgets.itemswidgets import LaunchpadBooleanRadioWidget
+from lp.bugs.interfaces.bugattachment import IBugAttachment
+
class BugAttachmentContentCheck:
"""A mixin class that checks the consistency of patch flag and file type.
@@ -222,3 +225,9 @@
def is_patch(self):
"""True if this attachment contains a patch, else False."""
return self.context.type == BugAttachmentType.PATCH
+
+
+class BugAttachmentFileNavigation(Navigation, FileNavigationMixin):
+ """Traversal to +files/${filename}."""
+
+ usedfor = IBugAttachment
=== modified file 'lib/lp/bugs/browser/bugcomment.py'
--- lib/lp/bugs/browser/bugcomment.py 2010-06-22 12:53:50 +0000
+++ lib/lp/bugs/browser/bugcomment.py 2010-08-02 13:45:58 +0000
@@ -26,6 +26,7 @@
from lp.bugs.interfaces.bugmessage import (
IBugComment, IBugMessageSet)
from lp.registry.interfaces.person import IPersonSet
+from canonical.launchpad.browser.librarian import ProxiedLibraryFileAlias
from canonical.launchpad.webapp import canonical_url, LaunchpadView
from canonical.launchpad.webapp.interfaces import ILaunchBag
from canonical.launchpad.webapp.authorization import check_permission
@@ -265,13 +266,22 @@
self.comment.index, self.context.bug.id)
-class BugCommentBoxView(LaunchpadView):
+class BugCommentBoxViewMixin:
+ """A class which provides proxied Librarian URLs for bug attachments."""
+
+ def proxiedUrlOfLibraryFileAlias(self, attachment):
+ """Return the proxied URL for the Librarian file of the attachment."""
+ return ProxiedLibraryFileAlias(
+ attachment.libraryfile, attachment).http_url
+
+
+class BugCommentBoxView(LaunchpadView, BugCommentBoxViewMixin):
"""Render a comment box with reply field collapsed."""
expand_reply_box = False
-class BugCommentBoxExpandedReplyView(LaunchpadView):
+class BugCommentBoxExpandedReplyView(LaunchpadView, BugCommentBoxViewMixin):
"""Render a comment box with reply field expanded."""
expand_reply_box = True
=== modified file 'lib/lp/bugs/browser/configure.zcml'
--- lib/lp/bugs/browser/configure.zcml 2010-06-25 13:34:37 +0000
+++ lib/lp/bugs/browser/configure.zcml 2010-08-02 13:45:58 +0000
@@ -767,7 +767,7 @@
<browser:navigation
module="lp.bugs.browser.bugattachment"
classes="
- BugAttachmentSetNavigation"/>
+ BugAttachmentSetNavigation BugAttachmentFileNavigation"/>
<browser:page
name="+bugsupervisor"
for="lp.bugs.interfaces.bugsupervisor.IHasBugSupervisor"
=== modified file 'lib/lp/bugs/browser/tests/bug-views.txt'
--- lib/lp/bugs/browser/tests/bug-views.txt 2010-07-22 12:17:41 +0000
+++ lib/lp/bugs/browser/tests/bug-views.txt 2010-08-02 13:45:58 +0000
@@ -263,8 +263,11 @@
== Bug Attachments ==
We show bug attachments in two lists: patches and non-patch attachments.
-Sequences of patch and non-patch attachments are provided by the
-properties `patches` and `regular_attachments` of the class BugView.
+Sequences with data about patch and non-patch attachments are provided
+by the properties `patches` and `regular_attachments` of the class
+BugView. The elements of the sequences are dictionaries containing
+the the attachment itself and a ProxiedLibraryFileAlias for the
+librarian file of the attachment.
>>> from lp.bugs.browser.bug import BugView
>>> login('foo.bar@xxxxxxxxxxxxx')
@@ -279,10 +282,18 @@
>>> patch_2 = factory.makeBugAttachment(
... bug=bug_seven, description='patch 2', is_patch=True)
>>> view = BugView(bug_seven, request)
- >>> [attachment.title for attachment in view.regular_attachments]
+ >>> [attachment['attachment'].title
+ ... for attachment in view.regular_attachments]
[u'attachment 1', u'attachment 2']
- >>> [patch.title for patch in view.patches]
+ >>> [patch['attachment'].title for patch in view.patches]
[u'patch 1', u'patch 2']
+ >>> [attachment['file'].http_url
+ ... for attachment in view.regular_attachments]
+ [u'http://bugs.launchpad.dev/firefox/+bug/5/+attachment/1/+files/...',
+ u'http://bugs.launchpad.dev/firefox/+bug/5/+attachment/2/+files/...']
+ >>> [patch['file'].http_url for patch in view.patches]
+ [u'http://bugs.launchpad.dev/firefox/+bug/5/+attachment/3/+files/...',
+ u'http://bugs.launchpad.dev/firefox/+bug/5/+attachment/4/+files/...']
== Bug Navigation ==
=== added file 'lib/lp/bugs/browser/tests/test_bugattachment_file_access.py'
--- lib/lp/bugs/browser/tests/test_bugattachment_file_access.py 1970-01-01 00:00:00 +0000
+++ lib/lp/bugs/browser/tests/test_bugattachment_file_access.py 2010-08-02 13:45:58 +0000
@@ -0,0 +1,106 @@
+# Copyright 2010 Canonical Ltd. This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+__metaclass__ = type
+
+import re
+import transaction
+
+from zope.component import getMultiAdapter, getUtility
+from zope.publisher.interfaces import NotFound
+from zope.security.interfaces import Unauthorized
+
+from canonical.launchpad.browser.librarian import (
+ StreamOrRedirectLibraryFileAliasView)
+from canonical.launchpad.interfaces import ILaunchBag
+from canonical.launchpad.interfaces.librarian import (
+ ILibraryFileAliasWithParent)
+from canonical.testing import LaunchpadFunctionalLayer
+from canonical.launchpad.webapp.publisher import RedirectionView
+from canonical.launchpad.webapp.servers import LaunchpadTestRequest
+
+from lp.bugs.browser.bugattachment import BugAttachmentFileNavigation
+from lp.testing import login_person, TestCaseWithFactory
+
+
+class TestAccessToBugAttachmentFiles(TestCaseWithFactory):
+ """Tests of traversal to and access of files of bug attachments."""
+
+ layer = LaunchpadFunctionalLayer
+
+ def setUp(self):
+ super(TestAccessToBugAttachmentFiles, self).setUp()
+ self.bug_owner = self.factory.makePerson()
+ getUtility(ILaunchBag).clear()
+ login_person(self.bug_owner)
+ self.bug = self.factory.makeBug(owner=self.bug_owner)
+ self.bugattachment = self.factory.makeBugAttachment(
+ bug=self.bug, filename='foo.txt', data='file content')
+
+ def test_traversal_to_lfa_of_bug_attachment(self):
+ # Traversing to the URL provided by a ProxiedLibraryFileAlias of a
+ # bug attachament returns a StreamOrRedirectLibraryFileAliasView.
+ request = LaunchpadTestRequest()
+ request.setTraversalStack(['foo.txt'])
+ navigation = BugAttachmentFileNavigation(
+ self.bugattachment, request)
+ view = navigation.publishTraverse(request, '+files')
+ self.assertIsInstance(view, StreamOrRedirectLibraryFileAliasView)
+
+ def test_traversal_to_lfa_of_bug_attachment_wrong_filename(self):
+ # If the filename provided in the URL does not match the
+ # filename of the LibraryFileAlias, a NotFound error is raised.
+ request = LaunchpadTestRequest()
+ request.setTraversalStack(['nonsense'])
+ navigation = BugAttachmentFileNavigation(self.bugattachment, request)
+ self.assertRaises(
+ NotFound, navigation.publishTraverse, request, '+files')
+
+ def test_access_to_unrestricted_file(self):
+ # Requests of unrestricted files are redirected to Librarian URLs.
+ request = LaunchpadTestRequest()
+ request.setTraversalStack(['foo.txt'])
+ navigation = BugAttachmentFileNavigation(
+ self.bugattachment, request)
+ view = navigation.publishTraverse(request, '+files')
+ next_view, traversal_path = view.browserDefault(request)
+ self.assertIsInstance(next_view, RedirectionView)
+ mo = re.match(
+ '^http://localhost:58000/\d+/foo.txt$', next_view.target)
+ self.assertIsNot(None, mo)
+
+ def test_access_to_restircted_file(self):
+ # Requests of restricted files are handled by ProxiedLibraryFileAlias.
+ lfa_with_parent = getMultiAdapter(
+ (self.bugattachment.libraryfile, self.bugattachment),
+ ILibraryFileAliasWithParent)
+ lfa_with_parent.restricted = True
+ self.bug.setPrivate(True, self.bug_owner)
+ transaction.commit()
+ request = LaunchpadTestRequest()
+ request.setTraversalStack(['foo.txt'])
+ navigation = BugAttachmentFileNavigation(self.bugattachment, request)
+ view = navigation.publishTraverse(request, '+files')
+ next_view, traversal_path = view.browserDefault(request)
+ self.assertEqual(view, next_view)
+ file_ = next_view()
+ file_.seek(0)
+ self.assertEqual('file content', file_.read())
+
+ def test_access_to_restircted_file_unauthorized(self):
+ # If a user cannot access the bug attachment itself, he can neither
+ # access the restricted Librarian file.
+ lfa_with_parent = getMultiAdapter(
+ (self.bugattachment.libraryfile, self.bugattachment),
+ ILibraryFileAliasWithParent)
+ lfa_with_parent.restricted = True
+ self.bug.setPrivate(True, self.bug_owner)
+ transaction.commit()
+ user = self.factory.makePerson()
+ login_person(user)
+ self.assertRaises(Unauthorized, getattr, self.bugattachment, 'title')
+ request = LaunchpadTestRequest()
+ request.setTraversalStack(['foo.txt'])
+ navigation = BugAttachmentFileNavigation(self.bugattachment, request)
+ self.assertRaises(
+ Unauthorized, navigation.publishTraverse, request, '+files')
=== modified file 'lib/lp/bugs/browser/tests/test_bugview.py'
--- lib/lp/bugs/browser/tests/test_bugview.py 2010-04-07 11:28:32 +0000
+++ lib/lp/bugs/browser/tests/test_bugview.py 2010-08-02 13:45:58 +0000
@@ -39,7 +39,7 @@
removeSecurityProxy(attachment.libraryfile).content = None
self.assertEqual(
['regular attachment'],
- [attachment.title
+ [attachment['attachment'].title
for attachment in self.view.regular_attachments])
def test_patches_dont_include_invalid_records(self):
@@ -54,7 +54,8 @@
removeSecurityProxy(patch.libraryfile).content = None
self.assertEqual(
['patch'],
- [attachment.title for attachment in self.view.patches])
+ [attachment['attachment'].title
+ for attachment in self.view.patches])
def test_suite():
=== modified file 'lib/lp/bugs/doc/bugattachments.txt'
--- lib/lp/bugs/doc/bugattachments.txt 2010-04-08 14:34:58 +0000
+++ lib/lp/bugs/doc/bugattachments.txt 2010-08-02 13:45:58 +0000
@@ -211,7 +211,8 @@
If the request contains no attachment description the filename should be used.
- >>> filecontent = StringIO("No, sir. That's one bonehead name, but that ain't me any more.")
+ >>> filecontent = StringIO(
+ ... "No, sir. That's one bonehead name, but that ain't me any more.")
>>> filecontent.filename = 'RA.txt'
>>> add_request = LaunchpadTestRequest(
... method="POST",
@@ -596,3 +597,22 @@
>>> removeSecurityProxy(attachment.libraryfile).content = None
>>> [attachment.title for attachment in bug.attachments]
[u'foobar']
+
+
+Miscellaneous
+-------------
+
+The method IBugAttachment.getFileByName() returns the Librarian file.
+
+ >>> attachment.libraryfile.filename
+ u'foobar'
+ >>> attachment.getFileByName('foobar')
+ <LibraryFileAlias at...
+
+A NotFoundError is raised if the file name passed to getFileByName()
+does not match the file name of the Librarian file.
+
+ >>> attachment.getFileByName('nonsense')
+ Traceback (most recent call last):
+ ...
+ NotFoundError: 'nonsense'
=== modified file 'lib/lp/bugs/interfaces/bugattachment.py'
--- lib/lp/bugs/interfaces/bugattachment.py 2010-02-02 14:54:24 +0000
+++ lib/lp/bugs/interfaces/bugattachment.py 2010-08-02 13:45:58 +0000
@@ -99,6 +99,14 @@
The library file content for this attachment is set to None.
"""
+ def getFileByName(filename):
+ """Return the `ILibraryFileAlias for the given file name.
+
+ NotFoundError is raised if the given filename does not match
+ libraryfile.filename.
+ """
+
+
# Need to do this here because of circular imports.
IMessage['bugattachments'].value_type.schema = IBugAttachment
=== modified file 'lib/lp/bugs/model/bugattachment.py'
--- lib/lp/bugs/model/bugattachment.py 2010-01-20 17:09:40 +0000
+++ lib/lp/bugs/model/bugattachment.py 2010-08-02 13:45:58 +0000
@@ -59,6 +59,12 @@
self.libraryfile.content = None
super(BugAttachment, self).destroySelf()
+ def getFileByName(self, filename):
+ """See IBugAttachment."""
+ if filename == self.libraryfile.filename:
+ return self.libraryfile
+ raise NotFoundError(filename)
+
class BugAttachmentSet:
"""A set for bug attachments."""
=== modified file 'lib/lp/bugs/stories/bugattachments/10-add-bug-attachment.txt'
--- lib/lp/bugs/stories/bugattachments/10-add-bug-attachment.txt 2010-04-06 06:05:25 +0000
+++ lib/lp/bugs/stories/bugattachments/10-add-bug-attachment.txt 2010-08-02 13:45:58 +0000
@@ -41,7 +41,7 @@
>>> link = user_browser.getLink('Some information')
>>> link.url
- 'http://localhost:58000/.../foo.txt'
+ 'http://bugs.launchpad.dev/firefox/+bug/1/+attachment/1/+files/foo.txt'
>>> 'Added some information' in user_browser.contents
True
=== modified file 'lib/lp/bugs/stories/bugattachments/20-edit-bug-attachment.txt'
--- lib/lp/bugs/stories/bugattachments/20-edit-bug-attachment.txt 2010-03-30 20:56:51 +0000
+++ lib/lp/bugs/stories/bugattachments/20-edit-bug-attachment.txt 2010-08-02 13:45:58 +0000
@@ -1,7 +1,8 @@
We can also edit the attachment details, let's navigate to that page.
+ >>> import re
>>> user_browser.open('http://bugs.launchpad.dev/firefox/+bug/1')
- >>> user_browser.getLink(url='+attachment/1').click()
+ >>> user_browser.getLink(url=re.compile(r'[+]attachment/1$')).click()
>>> user_browser.url
'http://bugs.launchpad.dev/firefox/+bug/1/+attachment/1'
@@ -31,7 +32,7 @@
We can edit the attachment to be a patch.
- >>> user_browser.getLink(url='+attachment/1').click()
+ >>> user_browser.getLink(url=re.compile(r'[+]attachment/1$')).click()
>>> patch_control = user_browser.getControl(
... 'This attachment contains a solution (patch) for this bug')
>>> patch_control.selected = True
=== modified file 'lib/lp/bugs/stories/bugattachments/xx-attachments-to-bug-report.txt'
--- lib/lp/bugs/stories/bugattachments/xx-attachments-to-bug-report.txt 2010-03-15 14:34:49 +0000
+++ lib/lp/bugs/stories/bugattachments/xx-attachments-to-bug-report.txt 2010-08-02 13:45:58 +0000
@@ -50,7 +50,7 @@
text/plain)
>>> link = browser.getLink('sample data')
>>> print link.url
- http://localhost:58000/.../test.txt
+ http://bugs.launchpad.dev/jokosher/+bug/11/+attachment/1/+files/test.txt
== Patch attachments are shown before non-patch attachments ==
=== modified file 'lib/lp/bugs/stories/bugattachments/xx-delete-bug-attachment.txt'
--- lib/lp/bugs/stories/bugattachments/xx-delete-bug-attachment.txt 2009-11-27 13:09:06 +0000
+++ lib/lp/bugs/stories/bugattachments/xx-delete-bug-attachment.txt 2010-08-02 13:45:58 +0000
@@ -36,7 +36,7 @@
attachment.
>>> import re
- >>> user_browser.getLink(url=re.compile(r'[+]attachment/\d')).click()
+ >>> user_browser.getLink(url=re.compile(r'[+]attachment/\d$')).click()
>>> print user_browser.title
Bug #2...
>>> user_browser.getControl('Delete Attachment') is not None
=== modified file 'lib/lp/bugs/stories/guided-filebug/xx-filebug-attachments.txt'
--- lib/lp/bugs/stories/guided-filebug/xx-filebug-attachments.txt 2009-09-23 09:03:22 +0000
+++ lib/lp/bugs/stories/guided-filebug/xx-filebug-attachments.txt 2010-08-02 13:45:58 +0000
@@ -48,7 +48,7 @@
A description of the attachment
>>> user_browser.getLink('A description of the attachment').url
- 'http://localhost:58000/.../example.txt'
+ 'http://bugs.launchpad.dev/firefox/+bug/.../+attachment/1/+files/ex...'
== Empty Attachment Fields ==
=== modified file 'lib/lp/bugs/templates/bug-portlet-attachments.pt'
--- lib/lp/bugs/templates/bug-portlet-attachments.pt 2010-03-15 03:29:51 +0000
+++ lib/lp/bugs/templates/bug-portlet-attachments.pt 2010-08-02 13:45:58 +0000
@@ -8,13 +8,13 @@
<ul>
<li class="download-attachment"
tal:repeat="attachment view/patches">
- <a tal:attributes="href attachment/libraryfile/http_url"
- tal:content="attachment/title"
+ <a tal:attributes="href attachment/file/http_url"
+ tal:content="attachment/attachment/title"
class="sprite haspatch-icon">
Attachment Title
</a>
<small>
- (<a tal:attributes="href attachment/fmt:url">edit</a>)
+ (<a tal:attributes="href attachment/attachment/fmt:url">edit</a>)
</small>
</li>
</ul>
@@ -31,13 +31,13 @@
<ul>
<li class="download-attachment"
tal:repeat="attachment view/regular_attachments">
- <a tal:attributes="href attachment/libraryfile/http_url"
- tal:content="attachment/title"
+ <a tal:attributes="href attachment/file/http_url"
+ tal:content="attachment/attachment/title"
class="sprite download-icon">
Attachment Title
</a>
<small>
- (<a tal:attributes="href attachment/fmt:url">edit</a>)
+ (<a tal:attributes="href attachment/attachment/fmt:url">edit</a>)
</small>
</li>
</ul>
=== modified file 'lib/lp/bugs/templates/bugcomment-box.pt'
--- lib/lp/bugs/templates/bugcomment-box.pt 2010-06-22 12:53:50 +0000
+++ lib/lp/bugs/templates/bugcomment-box.pt 2010-08-02 13:45:58 +0000
@@ -58,7 +58,7 @@
<ul tal:condition="comment/bugattachments" style="margin-bottom: 1em">
<li tal:repeat="attachment comment/bugattachments"
class="download-attachment">
- <a tal:attributes="href attachment/libraryfile/http_url"
+ <a tal:attributes="href python: view.proxiedUrlOfLibraryFileAlias(attachment)"
tal:content="attachment/title"
class="sprite download-icon">foo.txt</a>
(<span
@@ -69,7 +69,7 @@
<ul tal:condition="comment/patches" style="margin-bottom: 1em">
<li tal:repeat="attachment comment/patches" class="download-attachment">
- <a tal:attributes="href attachment/libraryfile/http_url"
+ <a tal:attributes="href python: view.proxiedUrlOfLibraryFileAlias(attachment)"
tal:content="attachment/title" class="sprite haspatch-icon">foo.txt</a>
(<span
tal:replace="attachment/libraryfile/content/filesize/fmt:bytes" />,
Follow ups