launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #04396
[Merge] lp:~bac/launchpad/getnewcache into lp:launchpad
Brad Crittenden has proposed merging lp:~bac/launchpad/getnewcache into lp:launchpad.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #810128 in Launchpad itself: "Expose a LaunchpadView's JSON for refreshing"
https://bugs.launchpad.net/launchpad/+bug/810128
For more details, see:
https://code.launchpad.net/~bac/launchpad/getnewcache/+merge/69468
= Summary =
Add a namespace ++model++ so that using it returns a fresh JSON cache for the object or view referenced.
== Pre-implementation notes ==
Lots of conferring with Aaron and Gary.
== Tests ==
bin/test -vvt '(test_publisher|test_view_model)'
== Demo and Q/A ==
Go to any Launchpad page and append ++model++, e.g.
https://launchpad.net/~bac/++model++
https://launchpad.net/~bac/+edit/++model++
--
https://code.launchpad.net/~bac/launchpad/getnewcache/+merge/69468
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~bac/launchpad/getnewcache into lp:launchpad.
=== modified file 'lib/canonical/launchpad/webapp/configure.zcml'
--- lib/canonical/launchpad/webapp/configure.zcml 2011-06-08 18:39:38 +0000
+++ lib/canonical/launchpad/webapp/configure.zcml 2011-07-27 14:02:34 +0000
@@ -440,6 +440,25 @@
factory="canonical.launchpad.webapp.namespace.FormNamespaceView"
/>
+ <!-- Expose LaunchpadView methods. -->
+ <class class="canonical.launchpad.webapp.publisher.LaunchpadView">
+ <allow attributes="getCacheJson initialize" />
+ </class>
+
+ <!-- Create a namespace to render the model of any LaunchpadView-->
+ <view
+ name="model" type="*"
+ provides="zope.traversing.interfaces.ITraversable" for="*"
+ factory="canonical.launchpad.webapp.namespace.JsonModelNamespaceView"
+ permission="zope.Public"
+ />
+
+ <class class="canonical.launchpad.webapp.namespace.JsonModelNamespaceView">
+ <allow
+ attributes="__call__"
+ interface="zope.publisher.interfaces.browser.IBrowserPublisher" />
+ </class>
+
<!-- Registrations to support +haproxy status url. -->
<browser:page
for="canonical.launchpad.webapp.interfaces.ILaunchpadRoot"
=== modified file 'lib/canonical/launchpad/webapp/namespace.py'
--- lib/canonical/launchpad/webapp/namespace.py 2011-05-27 21:03:22 +0000
+++ lib/canonical/launchpad/webapp/namespace.py 2011-07-27 14:02:34 +0000
@@ -1,11 +1,23 @@
-# Copyright 2009 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
+__metaclass__ = type
+
+__all__ = [
+ 'FormNamespaceView',
+ 'JsonModelNamespaceView',
+ ]
+
+
from z3c.ptcompat import ViewPageTemplateFile
from zope.app.pagetemplate.viewpagetemplatefile import BoundPageTemplate
+from zope.app.publisher.browser import getDefaultViewName
+from zope.component import getMultiAdapter
from zope.security.proxy import removeSecurityProxy
from zope.traversing.interfaces import TraversalError
from zope.traversing.namespace import view
+from zope.interface import implements
+from zope.publisher.interfaces.browser import IBrowserPublisher
from lp.app.browser.launchpadform import LaunchpadFormView
@@ -28,7 +40,7 @@
context = removeSecurityProxy(self.context)
if isinstance(context, LaunchpadFormView):
- # Note: without explicitely creating the BoundPageTemplate here
+ # Note: without explicitly creating the BoundPageTemplate here
# the view fails to render.
context.index = BoundPageTemplate(FormNamespaceView.template,
context)
@@ -36,3 +48,37 @@
raise TraversalError("The URL does not correspond to a form.")
return self.context
+
+
+class JsonModelNamespaceView(view):
+ """A namespace view to handle traversals with ++model++."""
+
+ implements(IBrowserPublisher)
+
+ def traverse(self, name, ignored):
+ """Model traversal adapter.
+
+ This adapter allows any LaunchpadView to render its JSON cache.
+ """
+ return self
+
+ def browserDefault(self, request):
+ # Tell traversal to stop, already.
+ return self, None
+
+ def __call__(self):
+ """Return the JSON cache."""
+ if IBrowserPublisher.providedBy(self.context):
+ view = self.context
+ else:
+ defaultviewname = getDefaultViewName(
+ self.context, self.request)
+ view = getMultiAdapter(
+ (self.context, self.request), name=defaultviewname)
+ if view is None:
+ return
+ naked_view = removeSecurityProxy(view)
+ naked_view.initialize()
+ cache = naked_view.getCacheJSON()
+ self.request.response.setHeader('content-type', 'application/json')
+ return cache
=== modified file 'lib/canonical/launchpad/webapp/publisher.py'
--- lib/canonical/launchpad/webapp/publisher.py 2011-07-21 22:45:05 +0000
+++ lib/canonical/launchpad/webapp/publisher.py 2011-07-27 14:02:34 +0000
@@ -26,6 +26,7 @@
]
import httplib
+import simplejson
from zope.app import zapi
from zope.app.publisher.interfaces.xmlrpc import IXMLRPCView
@@ -51,7 +52,14 @@
)
from zope.traversing.browser.interfaces import IAbsoluteURL
+from lazr.restful import (
+ EntryResource,
+ ResourceJSONEncoder,
+ )
from lazr.restful.declarations import error_status
+from lazr.restful.interfaces import IJSONRequestCache
+
+from lazr.restful.tales import WebLayerAPI
from canonical.launchpad.layers import (
LaunchpadLayer,
@@ -340,6 +348,17 @@
info_message = property(_getInfoMessage, _setInfoMessage)
+ def getCacheJSON(self):
+ if self.user is not None:
+ cache = dict(IJSONRequestCache(self.request).objects)
+ else:
+ cache = dict()
+ if WebLayerAPI(self.context).is_entry:
+ cache['context'] = self.context
+ return simplejson.dumps(
+ cache, cls=ResourceJSONEncoder,
+ media_type=EntryResource.JSON_TYPE)
+
class LaunchpadXMLRPCView(UserAttributeCache):
"""Base class for writing XMLRPC view code."""
=== added file 'lib/canonical/launchpad/webapp/templates/launchpad-model.pt'
--- lib/canonical/launchpad/webapp/templates/launchpad-model.pt 1970-01-01 00:00:00 +0000
+++ lib/canonical/launchpad/webapp/templates/launchpad-model.pt 2011-07-27 14:02:34 +0000
@@ -0,0 +1,14 @@
+<div
+ xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:tal="http://xml.zope.org/namespaces/tal"
+ xmlns:metal="http://xml.zope.org/namespaces/metal"
+ xmlns:i18n="http://xml.zope.org/namespaces/i18n"
+ xml:lang="en"
+ lang="en"
+ dir="ltr"
+ i18n:domain="launchpad">
+
+ <h1>MODEL, DAMMIT</h1>
+ <tal:json replace="structure context/getCacheJSON" />
+
+</div>
=== modified file 'lib/canonical/launchpad/webapp/tests/test_publisher.py'
--- lib/canonical/launchpad/webapp/tests/test_publisher.py 2010-10-12 01:11:41 +0000
+++ lib/canonical/launchpad/webapp/tests/test_publisher.py 2011-07-27 14:02:34 +0000
@@ -5,10 +5,96 @@
DocTestSuite,
ELLIPSIS,
)
+from unittest import TestLoader, TestSuite
+
+from lazr.restful.interfaces import IJSONRequestCache
+import simplejson
+from zope.component import getUtility
+
+from canonical.testing.layers import DatabaseFunctionalLayer
+from canonical.launchpad.webapp.publisher import LaunchpadView
+from canonical.launchpad.webapp.servers import LaunchpadTestRequest
+from lp.services.worlddata.interfaces.country import ICountrySet
+from lp.testing import (
+ logout,
+ person_logged_in,
+ TestCaseWithFactory,
+ )
from canonical.launchpad.webapp import publisher
+class TestLaunchpadView(TestCaseWithFactory):
+
+ layer = DatabaseFunctionalLayer
+
+ def test_getCacheJSON_non_resource_context(self):
+ view = LaunchpadView(object(), LaunchpadTestRequest())
+ self.assertEqual('{}', view.getCacheJSON())
+
+ @staticmethod
+ def getCanada():
+ return getUtility(ICountrySet)['CA']
+
+ def assertIsCanada(self, json_dict):
+ self.assertIs(None, json_dict['description'])
+ self.assertEqual('CA', json_dict['iso3166code2'])
+ self.assertEqual('CAN', json_dict['iso3166code3'])
+ self.assertEqual('Canada', json_dict['name'])
+ self.assertIs(None, json_dict['title'])
+ self.assertContentEqual(
+ ['description', 'http_etag', 'iso3166code2', 'iso3166code3',
+ 'name', 'resource_type_link', 'self_link', 'title'],
+ json_dict.keys())
+
+ def test_getCacheJSON_resource_context(self):
+ view = LaunchpadView(self.getCanada(), LaunchpadTestRequest())
+ json_dict = simplejson.loads(view.getCacheJSON())['context']
+ self.assertIsCanada(json_dict)
+
+ def test_getCacheJSON_non_resource_object(self):
+ request = LaunchpadTestRequest()
+ view = LaunchpadView(object(), request)
+ IJSONRequestCache(request).objects['my_bool'] = True
+ with person_logged_in(self.factory.makePerson()):
+ self.assertEqual('{"my_bool": true}', view.getCacheJSON())
+
+ def test_getCacheJSON_resource_object(self):
+ request = LaunchpadTestRequest()
+ view = LaunchpadView(object(), request)
+ IJSONRequestCache(request).objects['country'] = self.getCanada()
+ with person_logged_in(self.factory.makePerson()):
+ json_dict = simplejson.loads(view.getCacheJSON())['country']
+ self.assertIsCanada(json_dict)
+
+ def test_getCacheJSON_context_overrides_objects(self):
+ request = LaunchpadTestRequest()
+ view = LaunchpadView(self.getCanada(), request)
+ IJSONRequestCache(request).objects['context'] = True
+ with person_logged_in(self.factory.makePerson()):
+ json_dict = simplejson.loads(view.getCacheJSON())['context']
+ self.assertIsCanada(json_dict)
+
+ def test_getCache_anonymous(self):
+ request = LaunchpadTestRequest()
+ view = LaunchpadView(self.getCanada(), request)
+ self.assertIs(None, view.user)
+ IJSONRequestCache(request).objects['my_bool'] = True
+ json_dict = simplejson.loads(view.getCacheJSON())
+ self.assertIsCanada(json_dict['context'])
+ self.assertFalse('my_bool' in json_dict)
+
+ def test_getCache_anonymous_obfuscated(self):
+ request = LaunchpadTestRequest()
+ branch = self.factory.makeBranch(name='user@domain')
+ logout()
+ view = LaunchpadView(branch, request)
+ self.assertIs(None, view.user)
+ self.assertNotIn('user@domain', view.getCacheJSON())
+
+
def test_suite():
- suite = DocTestSuite(publisher, optionflags=ELLIPSIS)
+ suite = TestSuite()
+ suite.addTest(DocTestSuite(publisher, optionflags=ELLIPSIS))
+ suite.addTest(TestLoader().loadTestsFromName(__name__))
return suite
=== added file 'lib/canonical/launchpad/webapp/tests/test_view_model.py'
--- lib/canonical/launchpad/webapp/tests/test_view_model.py 1970-01-01 00:00:00 +0000
+++ lib/canonical/launchpad/webapp/tests/test_view_model.py 2011-07-27 14:02:34 +0000
@@ -0,0 +1,167 @@
+# Copyright 2011 Canonical Ltd. All rights reserved.
+
+"""Tests for the user requested oops using ++oops++ traversal."""
+
+__metaclass__ = type
+
+
+from lazr.restful.interfaces import IJSONRequestCache
+from lazr.restful.utils import get_current_browser_request
+from simplejson import loads
+from testtools.matchers import KeysEqual
+from zope.configuration import xmlconfig
+
+from canonical.launchpad.webapp import LaunchpadView
+from canonical.launchpad.webapp.publisher import canonical_url
+from canonical.launchpad.webapp.namespace import JsonModelNamespaceView
+import canonical.launchpad.webapp.tests
+from canonical.testing.layers import DatabaseFunctionalLayer
+from lp.app.browser.launchpadform import LaunchpadFormView
+from lp.testing import (
+ ANONYMOUS,
+ BrowserTestCase,
+ login,
+ logout,
+ TestCaseWithFactory,
+ )
+
+
+class FakeView:
+ """A view object that just has a fake context and request."""
+ def __init__(self):
+ self.context = object()
+ self.request = object()
+
+
+class TestJsonModelNamespace(TestCaseWithFactory):
+ """Test that traversal to ++model++ returns a namespace."""
+ layer = DatabaseFunctionalLayer
+
+ def setUp(self):
+ TestCaseWithFactory.setUp(self)
+ login(ANONYMOUS)
+
+ def tearDown(self):
+ logout()
+ TestCaseWithFactory.tearDown(self)
+
+ def test_JsonModelNamespace_traverse_non_LPview(self):
+ # Test traversal for JSON model namespace,
+ # ++model++ for a non-LaunchpadView context.
+ request = get_current_browser_request()
+ context = object()
+ view = FakeView()
+ namespace = JsonModelNamespaceView(context, request)
+ result = namespace.traverse(view, None)
+ self.assertEqual(result, namespace)
+
+ def test_JsonModelNamespace_traverse_LPView(self):
+ # Test traversal for JSON model namespace,
+ # ++model++ for a non-LaunchpadView context.
+ request = get_current_browser_request()
+ context = object()
+ view = LaunchpadView(context, request)
+ namespace = JsonModelNamespaceView(view, request)
+ result = namespace.traverse(view, None)
+ self.assertEqual(result, namespace)
+
+ def test_JsonModelNamespace_traverse_LPFormView(self):
+ # Test traversal for JSON model namespace,
+ # ++model++ for a non-LaunchpadView context.
+ request = get_current_browser_request()
+ context = object()
+ view = LaunchpadFormView(context, request)
+ namespace = JsonModelNamespaceView(view, request)
+ result = namespace.traverse(view, None)
+ self.assertEqual(result, namespace)
+
+
+class BaseProductModelTestView(LaunchpadView):
+ def initialize(self):
+ # Ensure initialize does not put anything in the cache.
+ pass
+
+
+class TestJsonModelView(BrowserTestCase):
+
+ layer = DatabaseFunctionalLayer
+
+ def setUp(self):
+ TestCaseWithFactory.setUp(self)
+ login(ANONYMOUS)
+ self.product = self.factory.makeProduct(name="test-product")
+ self.url = canonical_url(self.product) + '/+modeltest/++model++'
+
+ def tearDown(self):
+ logout()
+ TestCaseWithFactory.tearDown(self)
+
+ def configZCML(self):
+ # Register the ZCML for our test view. Note the view class must be
+ # registered first.
+ xmlconfig.string("""
+ <configure
+ xmlns:browser="http://namespaces.zope.org/browser">
+ <include package="canonical.launchpad.webapp"
+ file="meta.zcml" />
+ <include package="zope.app.zcmlfiles" file="meta.zcml" />
+ <browser:page
+ name="+modeltest"
+ for="lp.registry.interfaces.product.IProduct"
+ class="canonical.launchpad.webapp.tests.ProductModelTestView"
+ permission="zope.Public"
+ />
+ </configure>""")
+
+ def test_JsonModel_default_cache(self):
+ # If nothing is added to the class by the view, the cache will only
+ # have the context.
+ class ProductModelTestView(BaseProductModelTestView):
+ pass
+ canonical.launchpad.webapp.tests.ProductModelTestView = \
+ ProductModelTestView
+ self.configZCML()
+ browser = self.getUserBrowser(self.url)
+ cache = loads(browser.contents)
+ self.assertEqual(['context'], cache.keys())
+
+ def test_JsonModel_custom_cache(self):
+ # Adding an item to the cache in the initialize method results in it
+ # being in the cache.
+ class ProductModelTestView(BaseProductModelTestView):
+ def initialize(self):
+ request = get_current_browser_request()
+ target_info = {}
+ target_info['title'] = "The Title"
+ cache = IJSONRequestCache(request).objects
+ cache['target_info'] = target_info
+ canonical.launchpad.webapp.tests.ProductModelTestView = \
+ ProductModelTestView
+ self.configZCML()
+ browser = self.getUserBrowser(self.url)
+ cache = loads(browser.contents)
+ self.assertThat(cache, KeysEqual('context', 'target_info'))
+
+ def test_JsonModel_custom_cache_wrong_method(self):
+ # Adding an item to the cache in some other method is not recognized,
+ # even if it called as part of normal rendering.
+ class ProductModelTestView(BaseProductModelTestView):
+ def initialize(self):
+ request = get_current_browser_request()
+ target_info = {}
+ target_info['title'] = "The Title"
+ cache = IJSONRequestCache(request).objects
+ cache['target_info'] = target_info
+
+ def render(self):
+ request = get_current_browser_request()
+ other_info = {}
+ other_info['spaz'] = "Stuff"
+ IJSONRequestCache(request).objects['other_info'] = other_info
+
+ canonical.launchpad.webapp.tests.ProductModelTestView = \
+ ProductModelTestView
+ self.configZCML()
+ browser = self.getUserBrowser(self.url)
+ cache = loads(browser.contents)
+ self.assertThat(cache, KeysEqual('context', 'target_info'))
=== modified file 'lib/lp/app/templates/base-layout-macros.pt'
--- lib/lp/app/templates/base-layout-macros.pt 2011-07-08 11:46:41 +0000
+++ lib/lp/app/templates/base-layout-macros.pt 2011-07-27 14:02:34 +0000
@@ -168,15 +168,9 @@
tal:content="string:LP.links['${key}'] =
'${links/?key/fmt:api_url}';">
</script>
- <script tal:repeat="key objects"
- tal:content="string:LP.cache['${key}'] =
- ${objects/?key/webservice:json};">
- </script>
</tal:cache>
- <script tal:condition="context/webservice:is_entry"
- tal:content="string:LP.cache['context'] =
- ${context/webservice:json/fmt:obfuscate-email};">
+ <script tal:content="string:LP.cache = ${view/getCacheJSON};">
</script>
</metal:lp-client-cache>
=== modified file 'lib/lp/app/webservice/marshallers.py'
--- lib/lp/app/webservice/marshallers.py 2011-07-12 10:02:51 +0000
+++ lib/lp/app/webservice/marshallers.py 2011-07-27 14:02:34 +0000
@@ -14,8 +14,10 @@
TextFieldMarshaller as LazrTextFieldMarshaller,
)
from zope.app.security.interfaces import IUnauthenticatedPrincipal
+from zope.component import getUtility
from lp.services.utils import obfuscate_email
+from canonical.launchpad.webapp.interfaces import ILaunchBag
class TextFieldMarshaller(LazrTextFieldMarshaller):
@@ -26,7 +28,7 @@
Return the value as is.
"""
- if (value is not None and
- IUnauthenticatedPrincipal.providedBy(self.request.principal)):
+
+ if (value is not None and getUtility(ILaunchBag).user is None):
return obfuscate_email(value)
return value
=== modified file 'lib/lp/blueprints/browser/configure.zcml'
--- lib/lp/blueprints/browser/configure.zcml 2011-06-19 13:32:15 +0000
+++ lib/lp/blueprints/browser/configure.zcml 2011-07-27 14:02:34 +0000
@@ -135,6 +135,7 @@
permission="zope.Public"/>
<browser:pages
for="lp.blueprints.interfaces.sprint.IHasSprints"
+ class="canonical.launchpad.webapp.LaunchpadView"
facet="overview"
permission="zope.Public">
<browser:page
=== modified file 'lib/lp/bugs/browser/configure.zcml'
--- lib/lp/bugs/browser/configure.zcml 2011-07-21 05:13:08 +0000
+++ lib/lp/bugs/browser/configure.zcml 2011-07-27 14:02:34 +0000
@@ -852,6 +852,7 @@
name="+index"/>
<browser:pages
for="lp.bugs.interfaces.bugtracker.IRemoteBug"
+ class="canonical.launchpad.webapp.LaunchpadView"
permission="zope.Public">
<browser:page
name="+index"
=== modified file 'lib/lp/bugs/browser/cve.py'
--- lib/lp/bugs/browser/cve.py 2011-02-24 15:30:54 +0000
+++ lib/lp/bugs/browser/cve.py 2011-07-27 14:02:34 +0000
@@ -20,6 +20,7 @@
canonical_url,
ContextMenu,
GetitemNavigation,
+ LaunchpadView,
Link,
)
from canonical.launchpad.webapp.batching import BatchNavigator
@@ -122,11 +123,10 @@
heading = 'Remove links to bug reports'
-class CveSetView:
+class CveSetView(LaunchpadView):
def __init__(self, context, request):
- self.context = context
- self.request = request
+ super(CveSetView, self).__init__(context, request)
self.notices = []
self.results = None
self.text = self.request.form.get('text', None)
=== modified file 'lib/lp/code/browser/bazaar.py'
--- lib/lp/code/browser/bazaar.py 2010-08-31 11:24:54 +0000
+++ lib/lp/code/browser/bazaar.py 2011-07-27 14:02:34 +0000
@@ -87,7 +87,7 @@
@cachedproperty
def short_product_tag_cloud(self):
"""Show a preview of the product tag cloud."""
- return BazaarProductView().products(
+ return BazaarProductView(None, None).products(
num_products=config.launchpad.code_homepage_product_cloud_size)
@@ -150,7 +150,7 @@
self.request.response.redirect(redirect_url, status=301)
-class BazaarProductView:
+class BazaarProductView(LaunchpadView):
"""Browser class for products gettable with Bazaar."""
def _make_distribution_map(self, values, percentile_map):
=== modified file 'lib/lp/registry/browser/codeofconduct.py'
--- lib/lp/registry/browser/codeofconduct.py 2010-11-23 23:22:27 +0000
+++ lib/lp/registry/browser/codeofconduct.py 2011-07-27 14:02:34 +0000
@@ -220,7 +220,7 @@
self.request = request
-class SignedCodeOfConductAdminView:
+class SignedCodeOfConductAdminView(LaunchpadView):
"""Admin Console for SignedCodeOfConduct Entries."""
def __init__(self, context, request):
=== modified file 'lib/lp/registry/browser/distributionsourcepackage.py'
--- lib/lp/registry/browser/distributionsourcepackage.py 2011-06-29 16:23:36 +0000
+++ lib/lp/registry/browser/distributionsourcepackage.py 2011-07-27 14:02:34 +0000
@@ -277,7 +277,7 @@
return Link('+changelog', text, icon="info")
-class DistributionSourcePackageBaseView:
+class DistributionSourcePackageBaseView(LaunchpadView):
"""Common features to all `DistributionSourcePackage` views."""
def releases(self):
@@ -633,7 +633,7 @@
cancel_url = next_url
-class DistributionSourcePackageHelpView:
+class DistributionSourcePackageHelpView(LaunchpadView):
"""A View to show Answers help."""
page_title = 'Help and support options'
=== modified file 'lib/lp/registry/browser/peoplemerge.py'
--- lib/lp/registry/browser/peoplemerge.py 2011-05-27 21:12:25 +0000
+++ lib/lp/registry/browser/peoplemerge.py 2011-07-27 14:02:34 +0000
@@ -342,19 +342,18 @@
return ''
-class RequestPeopleMergeMultipleEmailsView:
+class RequestPeopleMergeMultipleEmailsView(LaunchpadView):
"""Merge request view when dupe account has multiple email addresses."""
label = 'Merge Launchpad accounts'
page_title = label
def __init__(self, context, request):
- self.context = context
- self.request = request
+ super(RequestPeopleMergeMultipleEmailsView, self).__init__(
+ context, request)
self.form_processed = False
self.dupe = None
self.notified_addresses = []
- self.user = getUtility(ILaunchBag).user
def processForm(self):
dupe = self.request.form.get('dupe')
=== modified file 'lib/lp/registry/browser/sourcepackage.py'
--- lib/lp/registry/browser/sourcepackage.py 2011-06-29 16:23:36 +0000
+++ lib/lp/registry/browser/sourcepackage.py 2011-07-27 14:02:34 +0000
@@ -444,7 +444,7 @@
'The packaging link has already been deleted.')
-class SourcePackageView:
+class SourcePackageView(LaunchpadView):
"""A view for (distro series) source packages."""
def initialize(self):
=== modified file 'lib/lp/registry/browser/teammembership.py'
--- lib/lp/registry/browser/teammembership.py 2011-05-27 21:12:25 +0000
+++ lib/lp/registry/browser/teammembership.py 2011-07-27 14:02:34 +0000
@@ -40,12 +40,10 @@
return "%s's membership" % self.context.person.displayname
-class TeamMembershipEditView:
+class TeamMembershipEditView(LaunchpadView):
def __init__(self, context, request):
- self.context = context
- self.request = request
- self.user = getUtility(ILaunchBag).user
+ super(TeamMembershipEditView, self).__init__(context, request)
self.errormessage = ""
self.prefix = 'membership'
self.max_year = 2050
=== modified file 'lib/lp/registry/browser/tests/test_subscription_links.py'
--- lib/lp/registry/browser/tests/test_subscription_links.py 2011-06-22 14:09:43 +0000
+++ lib/lp/registry/browser/tests/test_subscription_links.py 2011-07-27 14:02:34 +0000
@@ -69,7 +69,7 @@
None, self.new_edit_link,
"Expected edit_bug_mail link missing")
# Ensure the LP.cache has been populated.
- self.assertTrue("LP.cache['administratedTeams']" in self.contents)
+ self.assertIn('LP.cache = {"administratedTeams": [', self.contents)
# Ensure the call to setup the subscription is in the HTML.
# Only check for the presence of setup's configuration step; more
# detailed checking is needlessly brittle.
=== modified file 'lib/lp/soyuz/browser/distroarchseriesbinarypackage.py'
--- lib/lp/soyuz/browser/distroarchseriesbinarypackage.py 2010-08-20 20:31:18 +0000
+++ lib/lp/soyuz/browser/distroarchseriesbinarypackage.py 2011-07-27 14:02:34 +0000
@@ -11,6 +11,7 @@
from canonical.launchpad.webapp import (
ApplicationMenu,
GetitemNavigation,
+ LaunchpadView,
)
from canonical.launchpad.webapp.breadcrumb import Breadcrumb
from canonical.lazr.utils import smartquote
@@ -31,11 +32,7 @@
usedfor = IDistroArchSeriesBinaryPackage
-class DistroArchSeriesBinaryPackageView:
-
- def __init__(self, context, request):
- self.context = context
- self.request = request
+class DistroArchSeriesBinaryPackageView(LaunchpadView):
@property
def page_title(self):
=== modified file 'lib/lp/soyuz/browser/distroarchseriesbinarypackagerelease.py'
--- lib/lp/soyuz/browser/distroarchseriesbinarypackagerelease.py 2010-08-20 20:31:18 +0000
+++ lib/lp/soyuz/browser/distroarchseriesbinarypackagerelease.py 2011-07-27 14:02:34 +0000
@@ -11,6 +11,7 @@
from canonical.launchpad.webapp import (
ApplicationMenu,
+ LaunchpadView,
Navigation,
)
from canonical.launchpad.webapp.breadcrumb import Breadcrumb
@@ -38,7 +39,7 @@
usedfor = IDistroArchSeriesBinaryPackageRelease
-class DistroArchSeriesBinaryPackageReleaseView:
+class DistroArchSeriesBinaryPackageReleaseView(LaunchpadView):
def __init__(self, context, request):
self.context = context
=== modified file 'lib/lp/soyuz/browser/distroseriesbinarypackage.py'
--- lib/lp/soyuz/browser/distroseriesbinarypackage.py 2010-08-20 20:31:18 +0000
+++ lib/lp/soyuz/browser/distroseriesbinarypackage.py 2011-07-27 14:02:34 +0000
@@ -12,6 +12,7 @@
from canonical.launchpad.webapp import (
ApplicationMenu,
+ LaunchpadView,
Navigation,
StandardLaunchpadFacets,
)
@@ -49,7 +50,7 @@
return self.context.binarypackagename.name
-class DistroSeriesBinaryPackageView:
+class DistroSeriesBinaryPackageView(LaunchpadView):
def __init__(self, context, request):
self.context = context
=== modified file 'lib/lp/soyuz/browser/distroseriessourcepackagerelease.py'
--- lib/lp/soyuz/browser/distroseriessourcepackagerelease.py 2010-08-20 20:31:18 +0000
+++ lib/lp/soyuz/browser/distroseriessourcepackagerelease.py 2011-07-27 14:02:34 +0000
@@ -10,6 +10,7 @@
from canonical.launchpad.webapp import (
ApplicationMenu,
+ LaunchpadView,
Navigation,
stepthrough,
)
@@ -49,7 +50,7 @@
return None
-class DistroSeriesSourcePackageReleaseView:
+class DistroSeriesSourcePackageReleaseView(LaunchpadView):
def __init__(self, context, request):
self.context = context
=== modified file 'lib/lp/soyuz/browser/sourcepackage.py'
--- lib/lp/soyuz/browser/sourcepackage.py 2011-05-12 12:09:17 +0000
+++ lib/lp/soyuz/browser/sourcepackage.py 2011-07-27 14:02:34 +0000
@@ -12,7 +12,10 @@
from zope.component import getUtility
-from canonical.launchpad.webapp import Navigation
+from canonical.launchpad.webapp import (
+ LaunchpadView,
+ Navigation,
+ )
from canonical.lazr.utils import smartquote
from lp.registry.interfaces.distribution import IDistributionSet
from lp.registry.interfaces.distroseries import IDistroSeriesSet
@@ -21,7 +24,7 @@
)
-class SourcePackageChangelogView:
+class SourcePackageChangelogView(LaunchpadView):
"""View class for source package change logs."""
page_title = "Change log"
@@ -32,7 +35,7 @@
return smartquote("Change logs for " + self.context.title)
-class SourcePackageCopyrightView:
+class SourcePackageCopyrightView(LaunchpadView):
"""A view to display a source package's copyright information."""
page_title = "Copyright"
=== modified file 'lib/lp/testing/__init__.py'
--- lib/lp/testing/__init__.py 2011-07-21 18:38:50 +0000
+++ lib/lp/testing/__init__.py 2011-07-27 14:02:34 +0000
@@ -15,6 +15,7 @@
'build_yui_unittest_suite',
'celebrity_logged_in',
'ExpectedException',
+ 'extract_lp_cache',
'FakeTime',
'get_lsb_information',
'is_logged_in',
@@ -1306,3 +1307,8 @@
self.caught_exc = exc_value
return super(ExpectedException, self).__exit__(
exc_type, exc_value, traceback)
+
+
+def extract_lp_cache(text):
+ match = re.search(r'<script>LP.cache = (\{.*\});</script>', text)
+ return simplejson.loads(match.group(1))
=== modified file 'lib/lp/translations/browser/sourcepackage.py'
--- lib/lp/translations/browser/sourcepackage.py 2011-04-21 19:56:36 +0000
+++ lib/lp/translations/browser/sourcepackage.py 2011-07-27 14:02:34 +0000
@@ -43,7 +43,7 @@
from lp.translations.model.translationpackagingjob import TranslationMergeJob
-class SourcePackageTranslationsView(TranslationsMixin,
+class SourcePackageTranslationsView(LaunchpadView, TranslationsMixin,
TranslationSharingDetailsMixin):
@property
=== modified file 'lib/lp/translations/browser/tests/test_sharing_details.py'
--- lib/lp/translations/browser/tests/test_sharing_details.py 2011-06-30 16:49:37 +0000
+++ lib/lp/translations/browser/tests/test_sharing_details.py 2011-07-27 14:02:34 +0000
@@ -26,6 +26,7 @@
from lp.testing import (
BrowserTestCase,
EventRecorder,
+ extract_lp_cache,
person_logged_in,
TestCaseWithFactory,
)
@@ -250,7 +251,7 @@
# If the packaging link is set and if an upstream series
# uses Launchpad translations but if the other conditions
# are not fulfilled, is_configuration_complete is False.
- self.configureSharing(translations_usage=ServiceUsage.LAUNCHPAD)
+ self.configureSharing(translations_usage = ServiceUsage.LAUNCHPAD)
self.assertFalse(self.view.is_configuration_complete)
def test_is_configuration_complete__no_auto_sync(self):
@@ -261,8 +262,8 @@
# but if the upstream series does not synchronize translations
# then is_configuration_complete is False.
self.configureSharing(
- set_upstream_branch=True,
- translations_usage=ServiceUsage.LAUNCHPAD)
+ set_upstream_branch = True,
+ translations_usage = ServiceUsage.LAUNCHPAD)
self.assertFalse(self.view.is_configuration_complete)
def test_is_configuration_complete__all_conditions_fulfilled(self):
@@ -273,9 +274,9 @@
# - the upstream series synchronizes translations
# then is_configuration_complete is True.
self.configureSharing(
- set_upstream_branch=True,
- translations_usage=ServiceUsage.LAUNCHPAD,
- translation_import_mode=
+ set_upstream_branch = True,
+ translations_usage = ServiceUsage.LAUNCHPAD,
+ translation_import_mode =
TranslationsBranchImportMode.IMPORT_TRANSLATIONS)
self.assertTrue(self.view.is_configuration_complete)
@@ -938,10 +939,12 @@
sourcepackage = self.makeFullyConfiguredSharing()[0]
anon_browser = self._getSharingDetailsViewBrowser(sourcepackage)
# Anonymous users don't get cached objects due to bug #740208
- self.assertNotIn("LP.cache['productseries'] =", anon_browser.contents)
+ self.assertNotIn(
+ 'productseries', extract_lp_cache(anon_browser.contents))
browser = self._getSharingDetailsViewBrowser(
sourcepackage, user=self.user)
- self.assertIn("LP.cache['productseries'] =", browser.contents)
+ self.assertIn(
+ 'productseries', extract_lp_cache(browser.contents))
def test_potlist_only_ubuntu(self):
# Without a packaging link, only Ubuntu templates are listed.
=== modified file 'lib/lp/translations/browser/translationgroup.py'
--- lib/lp/translations/browser/translationgroup.py 2010-11-23 23:22:27 +0000
+++ lib/lp/translations/browser/translationgroup.py 2011-07-27 14:02:34 +0000
@@ -57,7 +57,7 @@
text = u"Translation groups"
-class TranslationGroupSetView:
+class TranslationGroupSetView(LaunchpadView):
"""Translation groups overview."""
page_title = "Translation groups"
label = page_title
=== modified file 'lib/lp/translations/browser/translations.py'
--- lib/lp/translations/browser/translations.py 2011-05-27 19:53:20 +0000
+++ lib/lp/translations/browser/translations.py 2011-07-27 14:02:34 +0000
@@ -56,13 +56,9 @@
rootsite='answers')
-class RosettaApplicationView(TranslationsMixin):
+class RosettaApplicationView(LaunchpadView, TranslationsMixin):
"""View for various top-level Translations pages."""
- def __init__(self, context, request):
- self.context = context
- self.request = request
-
@property
def ubuntu_translationseries(self):
ubuntu = getUtility(ILaunchpadCelebrities).ubuntu