launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #00760
[Merge] lp:~sinzui/launchpad/official-services-0 into lp:launchpad/devel
Curtis Hovey has proposed merging lp:~sinzui/launchpad/official-services-0 into lp:launchpad/devel.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
This is my first branch to state the location of a project's official services
on the its app pages. This branch adds the unknown service presentation to
Answers
lp:~sinzui/launchpad/official-services-0
Diff size: 639
Launchpad bug:
https://bugs.launchpad.net/bugs/597738
Test command: ./bin/test -vv \
-t test_questiontarget -t TestDistributionSourcePackageFormatterAPI
-t faq-browse -t question-browse -t question-search-multiple
Pre-implementation: jml, bac
Target release: 10.09
State that Launchpad does not know where Support is hosted
----------------------------------------------------------
This branch deals with the unknown condition for Launchpad Answers. A simple
page explaining the condition is shown. There is a link the Ubuntu DSP if
the project is linked to an Ubuntu package. Project owners can see a link
to configure Answers.
I decided not to work on the remote condition because it requires a schema
change to record the remote forum or mailing list. We will consider
addressing this issue in another branch.
Rules
-----
* When a service is unknown, the root page must say so and the feature
can be enabled. The page must explain what the app provides and how
it can be enabled. It may need to explain that the feature can be
enabled without being official
* Enabled can be explicit in the case of answers
* Answers needs an explanation of the service.
* Questions defines the template in python. This makes changing the
template based on the object state easy, but the Person views need to
be decoupled or redefined since it is also a questioncollection.
* NB. I added DistributionSourcePackageFormatterAPI so that there is an
easy way to create a link to a DSP.
QA
--
* Visit https://answers.edge.launchpad.net/dtdparser
* Verify that the page explains that Launchpad does not know where
support requests are handled.
* Follow the configure support link and set support to Launchpad.
* Verify that the answers page is now enabled.
* Visit https://answers.edge.launchpad.net/gedit
* Verify that the page explains that Launchpad does not know where
support requests are handled.
* Verify there is a link to ask a question at
https://answers.edge.launchpad.net/ubuntu/+source/gedit
Lint
----
Linting changed files:
lib/lp/answers/browser/questiontarget.py
lib/lp/answers/browser/tests/test_questiontarget.py
lib/lp/answers/stories/faq-browse-and-search.txt
lib/lp/answers/stories/question-browse-and-search.txt
lib/lp/answers/stories/question-search-multiple-languages.txt
lib/lp/answers/templates/unknown-support.pt
lib/lp/registry/browser/configure.zcml
lib/lp/registry/browser/distribution.py
lib/lp/registry/browser/distributionsourcepackage.py
lib/lp/registry/browser/person.py
lib/lp/registry/browser/tests/test_distributionsourcepackage.py
NB. lint reports lots line length and header issues in the doctests. I
can fix these before I land the branch.
Test
----
* lib/lp/answers/browser/tests/test_questiontarget.py
* Added tests for template conditions
* Added tests for DSP that the page can link to.
* Added tests to verify when the configuration link can appear.
* lib/lp/answers/stories/faq-browse-and-search.txt
* Enabled Launchpad Answers to keep the test working.
* lib/lp/answers/stories/question-browse-and-search.txt
* Enabled Launchpad Answers to keep the test working.
* lib/lp/answers/stories/question-search-multiple-languages.txt
* Enabled Launchpad Answers to keep the test working.
* lib/lp/registry/browser/tests/test_distributionsourcepackage.py
* Added a test to verify the DSP link formatter.
Implementation
--------------
* lib/lp/answers/browser/questiontarget.py
* Added a selected_template property that allows the view to select
the template based on whether the project officially use answers.
* Added properties to get the packages linked to this project that
can provide links to ask questions in Ubuntu.
* Added a helper for the template to know when to show the link to
configure support.
* lib/lp/answers/templates/unknown-support.pt
* Added a template for the unknown support page.
* lib/lp/registry/browser/configure.zcml
* Registered the link formatter for DSPs
* lib/lp/registry/browser/distribution.py
* Added a link to configure_answers.
* lib/lp/registry/browser/distributionsourcepackage.py
* Added a DistributionSourcePackageFormatterAPI to format DSP links.
* lib/lp/registry/browser/person.py
* Updated PersonSearchQuestionsView to define only one template for it
to use.
* Updated all other person-questioncollection views to use the
updated template as a base. This also allowed me to remove a redundant
class attr.
--
https://code.launchpad.net/~sinzui/launchpad/official-services-0/+merge/33675
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~sinzui/launchpad/official-services-0 into lp:launchpad/devel.
=== modified file 'lib/lp/answers/browser/questiontarget.py'
--- lib/lp/answers/browser/questiontarget.py 2010-08-20 20:31:18 +0000
+++ lib/lp/answers/browser/questiontarget.py 2010-08-25 17:41:21 +0000
@@ -29,6 +29,7 @@
from zope.app.form.browser import DropdownWidget
from zope.component import (
getUtility,
+ getMultiAdapter,
queryMultiAdapter,
)
from zope.formlib import form
@@ -61,10 +62,12 @@
stepto,
urlappend,
)
+from canonical.launchpad.webapp.authorization import check_permission
from canonical.launchpad.webapp.batching import BatchNavigator
from canonical.launchpad.webapp.breadcrumb import Breadcrumb
from canonical.launchpad.webapp.menu import structured
from canonical.widgets import LabeledMultiCheckBoxWidget
+from lp.app.errors import NotFoundError
from lp.answers.browser.faqcollection import FAQCollectionMenu
from lp.answers.interfaces.faqcollection import IFAQCollection
from lp.answers.interfaces.questioncollection import (
@@ -77,8 +80,8 @@
IQuestionTarget,
ISearchQuestionsForm,
)
-from lp.app.errors import NotFoundError
from lp.registry.interfaces.distribution import IDistribution
+from lp.registry.interfaces.product import IProduct
from lp.registry.interfaces.projectgroup import IProjectGroup
from lp.services.fields import PublicPersonChoice
from lp.services.worlddata.interfaces.language import ILanguageSet
@@ -188,6 +191,30 @@
orientation='horizontal')
template = ViewPageTemplateFile('../templates/question-listing.pt')
+ unknown_template = ViewPageTemplateFile('../templates/unknown-support.pt')
+
+ @property
+ def selected_template(self):
+ """The template to render the presentation.
+
+ Subclasses can redefine this property to choose their own template.
+ """
+ if IQuestionSet.providedBy(self.context):
+ return self.template
+ involvement = getMultiAdapter(
+ (self.context, self.request), name='+get-involved')
+ if involvement.official_answers:
+ # Primary contexts that officially use answers have a
+ # search and listing presentation.
+ return self.template
+ else:
+ # Primary context that do not officially use answers have an
+ # an explanation about about the current state.
+ return self.unknown_template
+
+ def render(self):
+ """See `LaunchpadView`."""
+ return self.selected_template()
@property
def page_title(self):
@@ -477,6 +504,31 @@
canonical_url(sourcepackage, rootsite='answers'),
question.sourcepackagename.name)
+ @property
+ def ubuntu_packages(self):
+ """The Ubuntu `IDistributionSourcePackage`s linked to the context.
+
+ If the context is an `IProduct` and it has `IPackaging` links to
+ Ubuntu, a list is returned. Otherwise None is returned
+ """
+ if IProduct.providedBy(self.context):
+ ubuntu = getUtility(ILaunchpadCelebrities).ubuntu
+ packages = [
+ package for package in self.context.distrosourcepackages
+ if package.distribution == ubuntu]
+ if len(packages) > 0:
+ return packages
+ return None
+
+ @property
+ def can_configure_answers(self):
+ """Can the user configure answers for the `IQuestionTarget`."""
+ target = self.context
+ if IProduct.providedBy(target) or IDistribution.providedBy(target):
+ return check_permission('launchpad.Edit', self.context)
+ else:
+ return False
+
class QuestionCollectionMyQuestionsView(SearchQuestionsView):
"""SearchQuestionsView specialization for the 'My questions' report.
=== added file 'lib/lp/answers/browser/tests/test_questiontarget.py'
--- lib/lp/answers/browser/tests/test_questiontarget.py 1970-01-01 00:00:00 +0000
+++ lib/lp/answers/browser/tests/test_questiontarget.py 2010-08-25 17:41:21 +0000
@@ -0,0 +1,188 @@
+# Copyright 2010 Canonical Ltd. This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Test questiontarget views."""
+
+__metaclass__ = type
+
+import os
+
+from BeautifulSoup import BeautifulSoup
+
+from zope.component import getUtility
+
+from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
+from canonical.testing import DatabaseFunctionalLayer
+from lp.answers.interfaces.questioncollection import IQuestionSet
+from lp.app.enums import ServiceUsage
+from lp.testing import login_person, person_logged_in, TestCaseWithFactory
+from lp.testing.views import create_initialized_view
+
+
+class TestSearchQuestionsView(TestCaseWithFactory):
+
+ layer = DatabaseFunctionalLayer
+
+ def linkPackage(self, product, name):
+ # A helper to setup a legitimate Packaging link between a product
+ # and an Ubuntu source package.
+ hoary = getUtility(ILaunchpadCelebrities).ubuntu['hoary']
+ sourcepackagename = self.factory.makeSourcePackageName(name)
+ sourcepackage = self.factory.makeSourcePackage(
+ sourcepackagename=sourcepackagename, distroseries=hoary)
+ self.factory.makeSourcePackagePublishingHistory(
+ sourcepackagename=sourcepackagename, distroseries=hoary)
+ product.development_focus.setPackaging(
+ hoary, sourcepackagename, product.owner)
+
+
+class TestSearchQuestionsViewCanConfigureAnswers(TestSearchQuestionsView):
+
+ def test_can_configure_answers_product_no_edit_permission(self):
+ product = self.factory.makeProduct()
+ view = create_initialized_view(product, '+questions')
+ self.assertEqual(False, view.can_configure_answers)
+
+ def test_can_configure_answers_product_with_edit_permission(self):
+ product = self.factory.makeProduct()
+ login_person(product.owner)
+ view = create_initialized_view(product, '+questions')
+ self.assertEqual(True, view.can_configure_answers)
+
+ def test_can_configure_answers_distribution_no_edit_permission(self):
+ distribution = self.factory.makeDistribution()
+ view = create_initialized_view(distribution, '+questions')
+ self.assertEqual(False, view.can_configure_answers)
+
+ def test_can_configure_answers_distribution_with_edit_permission(self):
+ distribution = self.factory.makeDistribution()
+ login_person(distribution.owner)
+ view = create_initialized_view(distribution, '+questions')
+ self.assertEqual(True, view.can_configure_answers)
+
+ def test_can_configure_answers_projectgroup_with_edit_permission(self):
+ project_group = self.factory.makeProject()
+ login_person(project_group.owner)
+ view = create_initialized_view(project_group, '+questions')
+ self.assertEqual(False, view.can_configure_answers)
+
+ def test_can_configure_answers_dsp_with_edit_permission(self):
+ dsp = self.factory.makeDistributionSourcePackage()
+ login_person(dsp.distribution.owner)
+ view = create_initialized_view(dsp, '+questions')
+ self.assertEqual(False, view.can_configure_answers)
+
+
+class TestSearchQuestionsViewSelectedTemplate(TestSearchQuestionsView):
+ """Test the behaviour of SearchQuestionsView.selected_template"""
+
+ def assertViewTemplate(self, context, file_name):
+ view = create_initialized_view(context, '+questions')
+ self.assertEqual(
+ file_name, os.path.basename(view.selected_template.filename))
+
+ def test_template_product_answers_usage_unknown(self):
+ product = self.factory.makeProduct()
+ self.assertViewTemplate(product, 'unknown-support.pt')
+
+ def test_template_product_answers_usage_launchpad(self):
+ product = self.factory.makeProduct()
+ with person_logged_in(product.owner) as owner:
+ product.answers_usage = ServiceUsage.LAUNCHPAD
+ self.assertViewTemplate(product, 'question-listing.pt')
+
+ def test_template_projectgroup_answers_usage_unknown(self):
+ product = self.factory.makeProduct()
+ project_group = self.factory.makeProject(owner=product.owner)
+ with person_logged_in(product.owner) as owner:
+ product.project = project_group
+ self.assertViewTemplate(project_group, 'unknown-support.pt')
+
+ def test_template_projectgroup_answers_usage_launchpad(self):
+ product = self.factory.makeProduct()
+ project_group = self.factory.makeProject(owner=product.owner)
+ with person_logged_in(product.owner) as owner:
+ product.project = project_group
+ product.answers_usage = ServiceUsage.LAUNCHPAD
+ self.assertViewTemplate(project_group, 'question-listing.pt')
+
+ def test_template_distribution_answers_usage_unknown(self):
+ distribution = self.factory.makeDistribution()
+ self.assertViewTemplate(distribution, 'unknown-support.pt')
+
+ def test_template_distribution_answers_usage_launchpad(self):
+ distribution = self.factory.makeDistribution()
+ with person_logged_in(distribution.owner) as owner:
+ distribution.answers_usage = ServiceUsage.LAUNCHPAD
+ self.assertViewTemplate(distribution, 'question-listing.pt')
+
+ def test_template_DSP_answers_usage_unknown(self):
+ dsp = self.factory.makeDistributionSourcePackage()
+ self.assertViewTemplate(dsp, 'unknown-support.pt')
+
+ def test_template_DSP_answers_usage_launchpad(self):
+ dsp = self.factory.makeDistributionSourcePackage()
+ with person_logged_in(dsp.distribution.owner) as owner:
+ dsp.distribution.answers_usage = ServiceUsage.LAUNCHPAD
+ self.assertViewTemplate(dsp, 'question-listing.pt')
+
+ def test_template_question_set(self):
+ question_set = getUtility(IQuestionSet)
+ self.assertViewTemplate(question_set, 'question-listing.pt')
+
+
+class TestSearchQuestionsView_ubuntu_packages(TestSearchQuestionsView):
+ """Test the behaviour of SearchQuestionsView.ubuntu_packages."""
+
+ def test_nonproduct_ubuntu_packages(self):
+ distribution = self.factory.makeDistribution()
+ view = create_initialized_view(distribution, '+questions')
+ packages = view.ubuntu_packages
+ self.assertEqual(None, packages)
+
+ def test_product_ubuntu_packages_unlinked(self):
+ product = self.factory.makeProduct()
+ view = create_initialized_view(product, '+questions')
+ packages = view.ubuntu_packages
+ self.assertEqual(None, packages)
+
+ def test_product_ubuntu_packages_linked(self):
+ product = self.factory.makeProduct()
+ self.linkPackage(product, 'cow')
+ view = create_initialized_view(product, '+questions')
+ packages = view.ubuntu_packages
+ self.assertEqual(1, len(packages))
+ self.assertEqual('cow', packages[0].name)
+
+
+class TestSearchQuestionsViewUnknown(TestSearchQuestionsView):
+ """Test the behaviour of SearchQuestionsView unknown support."""
+
+ def setUp(self):
+ super(TestSearchQuestionsViewUnknown, self).setUp()
+ self.product = self.factory.makeProduct()
+ self.view = create_initialized_view(self.product, '+questions')
+
+ def assertCommonPageElements(self, content):
+ robots = content.find('meta', attrs={'name': 'robots'})
+ self.assertEqual('noindex,nofollow', robots['content'])
+ self.assertTrue(content.find(True, id='support-unknown') is not None)
+
+ def test_any_question_target_any_user(self):
+ content = BeautifulSoup(self.view())
+ self.assertCommonPageElements(content)
+
+ def test_product_with_packaging_elements(self):
+ self.linkPackage(self.product, 'cow')
+ content = BeautifulSoup(self.view())
+ self.assertCommonPageElements(content)
+ self.assertTrue(content.find(True, id='ubuntu-support') is not None)
+
+ def test_product_with_edit_permission(self):
+ login_person(self.product.owner)
+ self.view = create_initialized_view(
+ self.product, '+questions', principal=self.product.owner)
+ content = BeautifulSoup(self.view())
+ self.assertCommonPageElements(content)
+ self.assertTrue(
+ content.find(True, id='configure-support') is not None)
=== modified file 'lib/lp/answers/stories/faq-browse-and-search.txt'
--- lib/lp/answers/stories/faq-browse-and-search.txt 2009-10-31 15:04:48 +0000
+++ lib/lp/answers/stories/faq-browse-and-search.txt 2010-08-25 17:41:21 +0000
@@ -10,6 +10,17 @@
find her problem there (surely playing a DVD must be common thing to do
with a computer these days).
+ >>> # Kubuntu must enable answers to access questions.
+ >>> from zope.component import getUtility
+ >>> from lp.app.enums import ServiceUsage
+ >>> from lp.registry.interfaces.distribution import IDistributionSet
+
+ >>> login('admin@xxxxxxxxxxxxx')
+ >>> getUtility(IDistributionSet)['kubuntu'].answers_usage = (
+ ... ServiceUsage.LAUNCHPAD)
+ >>> transaction.commit()
+ >>> logout()
+
>>> browser.open('http://answers.launchpad.dev/kubuntu')
>>> browser.getLink('All FAQs').click()
=== modified file 'lib/lp/answers/stories/question-browse-and-search.txt'
--- lib/lp/answers/stories/question-browse-and-search.txt 2010-04-16 15:06:55 +0000
+++ lib/lp/answers/stories/question-browse-and-search.txt 2010-08-25 17:41:21 +0000
@@ -10,6 +10,17 @@
system and goes to the Kubuntu's support page in Launchpad to see if
somebody had a similar problem.
+ >>> # Kubuntu must enable answers to access questions.
+ >>> from zope.component import getUtility
+ >>> from lp.app.enums import ServiceUsage
+ >>> from lp.registry.interfaces.distribution import IDistributionSet
+
+ >>> login('admin@xxxxxxxxxxxxx')
+ >>> getUtility(IDistributionSet)['kubuntu'].answers_usage = (
+ ... ServiceUsage.LAUNCHPAD)
+ >>> transaction.commit()
+ >>> logout()
+
>>> browser.open('http://launchpad.dev/kubuntu')
>>> browser.getLink('Answers').click()
@@ -398,6 +409,14 @@
If the user didn't make any questions on the product, a message
informing him of this fact is displayed.
+ >>> # gnmomebaker must enable answers to access questions.
+ >>> from lp.registry.interfaces.product import IProductSet
+ >>> login('admin@xxxxxxxxxxxxx')
+ >>> getUtility(IProductSet)['gnomebaker'].answers_usage = (
+ ... ServiceUsage.LAUNCHPAD)
+ >>> transaction.commit()
+ >>> logout()
+
>>> sample_person_browser.open(
... 'http://launchpad.dev/gnomebaker/+questions')
>>> sample_person_browser.getLink('My questions').click()
=== modified file 'lib/lp/answers/stories/question-search-multiple-languages.txt'
--- lib/lp/answers/stories/question-search-multiple-languages.txt 2009-09-23 14:40:53 +0000
+++ lib/lp/answers/stories/question-search-multiple-languages.txt 2010-08-25 17:41:21 +0000
@@ -86,6 +86,17 @@
When the project has no questions to search, we do not show the
language controls.
+ >>> # Kubuntu must enable answers to access questions.
+ >>> from zope.component import getUtility
+ >>> from lp.app.enums import ServiceUsage
+ >>> from lp.registry.interfaces.distribution import IDistributionSet
+
+ >>> login('admin@xxxxxxxxxxxxx')
+ >>> getUtility(IDistributionSet)['kubuntu'].answers_usage = (
+ ... ServiceUsage.LAUNCHPAD)
+ >>> transaction.commit()
+ >>> logout()
+
>>> anon_browser.open('http://launchpad.dev/kubuntu/+questions')
>>> anon_browser.getControl(name='field.language')
Traceback (most recent call last):
=== added file 'lib/lp/answers/templates/unknown-support.pt'
--- lib/lp/answers/templates/unknown-support.pt 1970-01-01 00:00:00 +0000
+++ lib/lp/answers/templates/unknown-support.pt 2010-08-25 17:41:21 +0000
@@ -0,0 +1,44 @@
+<html
+ 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"
+ metal:use-macro="view/macro:page/main_only"
+ i18n:domain="launchpad">
+ <head>
+ <tal:head_epilogue metal:fill-slot="head_epilogue">
+ <meta name="robots" content="noindex,nofollow" />
+ </tal:head_epilogue>
+ </head>
+
+ <body>
+ <div metal:fill-slot="main">
+ <div class="top-portlet">
+ <p id="support-unknown">
+ <strong>Launchpad does not know where
+ <tal:project replace="context/displayname" />
+ tracks support requests.</strong>
+ </p>
+
+ <p id="ubuntu-support"
+ tal:define="packages view/ubuntu_packages"
+ tal:condition="packages">
+ <tal:project replace="context/displayname" /> questions are also
+ tracked in: <tal:packages repeat="package packages">
+ <tal:package replace="structure package/fmt:link" /><tal:comma
+ condition="not:repeat/package/end">, </tal:comma></tal:packages>.
+ </p>
+
+ <p id="configure-support"
+ tal:condition="view/can_configure_answers">
+ Launchpad allows your project to track questions and create FAQs.
+ <br /><a class="sprite maybe"
+ href="https://help.launchpad.net/Answers">Getting started
+ with support tracking in Launchpad</a>.
+ <br /><a tal:replace="structure context/menu:overview/configure_answers/fmt:link" />
+ </p>
+ </div>
+ </div>
+ </body>
+</html>
+
=== modified file 'lib/lp/registry/browser/configure.zcml'
--- lib/lp/registry/browser/configure.zcml 2010-08-13 21:30:24 +0000
+++ lib/lp/registry/browser/configure.zcml 2010-08-25 17:41:21 +0000
@@ -470,6 +470,12 @@
DistributionSourcePackageFacets
DistributionSourcePackageOverviewMenu"
module="lp.registry.browser.distributionsourcepackage"/>
+ <adapter
+ for="lp.registry.interfaces.distributionsourcepackage.IDistributionSourcePackage"
+ provides="zope.traversing.interfaces.IPathAdapter"
+ factory="lp.registry.browser.distributionsourcepackage.DistributionSourcePackageFormatterAPI"
+ name="fmt"
+ />
<browser:url
for="lp.registry.interfaces.commercialsubscription.ICommercialSubscription"
path_expression="string:+commercialsubscription/${id}"
=== modified file 'lib/lp/registry/browser/distribution.py'
--- lib/lp/registry/browser/distribution.py 2010-08-23 09:10:10 +0000
+++ lib/lp/registry/browser/distribution.py 2010-08-25 17:41:21 +0000
@@ -319,7 +319,8 @@
'builds', 'cdimage_mirrors', 'archive_mirrors',
'pending_review_mirrors', 'disabled_mirrors',
'unofficial_mirrors', 'newmirror', 'announce', 'announcements',
- 'ppas',]
+ 'ppas', 'configure_answers',
+ ]
@enabled_with_permission('launchpad.Edit')
def branding(self):
@@ -424,6 +425,12 @@
text = 'Personal Package Archives'
return Link('+ppas', text, icon='info')
+ @enabled_with_permission('launchpad.Edit')
+ def configure_answers(self):
+ text = 'Configure support tracker'
+ summary = 'Allow users to ask questions on this project'
+ return Link('+edit', text, summary, icon='edit')
+
class DerivativeDistributionOverviewMenu(DistributionOverviewMenu):
@@ -582,6 +589,7 @@
return self.has_exact_matches
+
class DistributionView(HasAnnouncementsView, FeedsMixin, UsesLaunchpadMixin):
"""Default Distribution view class."""
=== modified file 'lib/lp/registry/browser/distributionsourcepackage.py'
--- lib/lp/registry/browser/distributionsourcepackage.py 2010-08-20 20:31:18 +0000
+++ lib/lp/registry/browser/distributionsourcepackage.py 2010-08-25 17:41:21 +0000
@@ -44,6 +44,7 @@
NavigationMenu,
)
from canonical.launchpad.webapp.sorting import sorted_dotted_numbers
+from canonical.launchpad.webapp.tales import CustomizableFormatter
from canonical.lazr.utils import smartquote
from lp.answers.browser.questiontarget import (
QuestionTargetFacetMixin,
@@ -71,8 +72,18 @@
)
from lp.soyuz.interfaces.packagediff import IPackageDiffSet
from lp.translations.browser.customlanguagecode import (
- HasCustomLanguageCodesTraversalMixin,
- )
+ HasCustomLanguageCodesTraversalMixin)
+
+
+class DistributionSourcePackageFormatterAPI(CustomizableFormatter):
+ """Adapt IDistributionSourcePackage objects to a formatted string."""
+
+ _link_permission = 'zope.Public'
+ _link_summary_template = '%(displayname)s'
+
+ def _link_summary_values(self):
+ displayname = self._context.displayname
+ return {'displayname': displayname}
class DistributionSourcePackageBreadcrumb(Breadcrumb):
=== modified file 'lib/lp/registry/browser/person.py'
--- lib/lp/registry/browser/person.py 2010-08-23 03:25:20 +0000
+++ lib/lp/registry/browser/person.py 2010-08-25 17:41:21 +0000
@@ -4968,13 +4968,16 @@
class PersonSearchQuestionsView(SearchQuestionsView):
- """View used to search and display questions in which an IPerson is
- involved.
- """
+ """View to search and display questions that involve an `IPerson`."""
display_target_column = True
@property
+ def selected_template(self):
+ # Persons always show the default template.
+ return self.template
+
+ @property
def pageheading(self):
"""See `SearchQuestionsView`."""
return _('Questions involving $name',
@@ -4988,11 +4991,9 @@
mapping=dict(name=self.context.displayname))
-class SearchAnsweredQuestionsView(SearchQuestionsView):
+class SearchAnsweredQuestionsView(PersonSearchQuestionsView):
"""View used to search and display questions answered by an IPerson."""
- display_target_column = True
-
def getDefaultFilter(self):
"""See `SearchQuestionsView`."""
return dict(participation=QuestionParticipation.ANSWERER)
@@ -5011,11 +5012,9 @@
mapping=dict(name=self.context.displayname))
-class SearchAssignedQuestionsView(SearchQuestionsView):
+class SearchAssignedQuestionsView(PersonSearchQuestionsView):
"""View used to search and display questions assigned to an IPerson."""
- display_target_column = True
-
def getDefaultFilter(self):
"""See `SearchQuestionsView`."""
return dict(participation=QuestionParticipation.ASSIGNEE)
@@ -5034,11 +5033,9 @@
mapping=dict(name=self.context.displayname))
-class SearchCommentedQuestionsView(SearchQuestionsView):
+class SearchCommentedQuestionsView(PersonSearchQuestionsView):
"""View used to search and show questions commented on by an IPerson."""
- display_target_column = True
-
def getDefaultFilter(self):
"""See `SearchQuestionsView`."""
return dict(participation=QuestionParticipation.COMMENTER)
@@ -5057,11 +5054,9 @@
mapping=dict(name=self.context.displayname))
-class SearchCreatedQuestionsView(SearchQuestionsView):
+class SearchCreatedQuestionsView(PersonSearchQuestionsView):
"""View used to search and display questions created by an IPerson."""
- display_target_column = True
-
def getDefaultFilter(self):
"""See `SearchQuestionsView`."""
return dict(participation=QuestionParticipation.OWNER)
@@ -5080,11 +5075,9 @@
mapping=dict(name=self.context.displayname))
-class SearchNeedAttentionQuestionsView(SearchQuestionsView):
+class SearchNeedAttentionQuestionsView(PersonSearchQuestionsView):
"""View used to search and show questions needing an IPerson attention."""
- display_target_column = True
-
def getDefaultFilter(self):
"""See `SearchQuestionsView`."""
return dict(needs_attention=True)
@@ -5102,11 +5095,9 @@
mapping=dict(name=self.context.displayname))
-class SearchSubscribedQuestionsView(SearchQuestionsView):
+class SearchSubscribedQuestionsView(PersonSearchQuestionsView):
"""View used to search and show questions subscribed to by an IPerson."""
- display_target_column = True
-
def getDefaultFilter(self):
"""See `SearchQuestionsView`."""
return dict(participation=QuestionParticipation.SUBSCRIBER)
=== added file 'lib/lp/registry/browser/tests/test_distributionsourcepackage.py'
--- lib/lp/registry/browser/tests/test_distributionsourcepackage.py 1970-01-01 00:00:00 +0000
+++ lib/lp/registry/browser/tests/test_distributionsourcepackage.py 2010-08-25 17:41:21 +0000
@@ -0,0 +1,26 @@
+# Copyright 2010 Canonical Ltd. This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Test distributionsourcepackage views."""
+
+__metaclass__ = type
+
+from zope.component import getUtility
+
+from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
+from canonical.testing import DatabaseFunctionalLayer
+from lp.testing import test_tales, TestCaseWithFactory
+
+
+class TestDistributionSourcePackageFormatterAPI(TestCaseWithFactory):
+
+ layer = DatabaseFunctionalLayer
+
+ def test_link(self):
+ sourcepackagename = self.factory.makeSourcePackageName('mouse')
+ ubuntu = getUtility(ILaunchpadCelebrities).ubuntu
+ dsp = ubuntu.getSourcePackage('mouse')
+ markup = (
+ u'<a href="/ubuntu/+source/mouse" class="sprite package-source">'
+ u'mouse in ubuntu</a>')
+ self.assertEqual(markup, test_tales('dsp/fmt:link', dsp=dsp))