← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~sinzui/launchpad/answers-api-1 into lp:launchpad

 

Curtis Hovey has proposed merging lp:~sinzui/launchpad/answers-api-1 into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  Bug #108246 in Launchpad itself: "SourcePackage shouldn't directly provide IQuestionTarget"
  https://bugs.launchpad.net/launchpad/+bug/108246
  Bug #780078 in Launchpad itself: "export answer contact methods to the API"
  https://bugs.launchpad.net/launchpad/+bug/780078

For more details, see:
https://code.launchpad.net/~sinzui/launchpad/answers-api-1/+merge/60804

Export answer contact methods to the API.

    Launchpad bug:
        https://bugs.launchpad.net/bugs/780078
        https://bugs.launchpad.net/bugs/108246
    Pre-implementation: wgrant, jcsackett

The QuestionTarget interfaces need reorganisation and The targets must
descend from them to enable th exported methods to work for projects
and distros.

ADDENDUM
After working 2 hours to update ISourcePackage to inherit from
IQuestionTarget, I decided that this was pointless. We have known for 4
years that this should not be the case. It is easier to bug 108246;
source packages should not be question targets.

90% of the diff is mechanical changes where code is moved or deleted.
The substantial changes are the +gethelp refactoring. The existing
tests demonstrate that this refactoring is successful.

--------------------------------------------------------------------

RULES

    * Make IProduct, IDistribution, ISourcePackage descend from
      IQuestionTarget
    * Compose IQuestionTarget from smaller interfaces that define permissions.
    * Move the +gethelp page to IDistributionSourcePackage, ensure the
      the existing URL do not break.
    * Remove IQuestionTarget from ISourcePackage.


QA

    * Run this script:
    from launchpadlib.launchpad import Launchpad
    lp = Launchpad.login_with(
        'test', 'https://api.qastaging.launchpad.net/', version='devel')
    project = lp.projects['launchpad']
    user = lp.people['sinzui']
    for language in lp.languages:
        if language.code == 'fr':
            break
    print "Adding %s to %s" % (language.code, user.name)
    user.addLanguage(language=language)
    print project.canUserAlterAnswerContact(person=user)
    # expect True
    print project.addAnswerContact(person=user)
    # expect True
    for lang in project.getSupportedLanguages():
        print lang.code
    for contact in project.getAnswerContactsForLanguage(language=language):
        print contact.name
    # Expect sinzui


LINT

    lib/lp/answers/interfaces/questiontarget.py
    lib/lp/answers/templates/sourcepackage-gethelp.pt
    lib/lp/answers/tests/test_doc.py
    lib/lp/bugs/tests/test_bugtarget.py
    lib/lp/registry/configure.zcml
    lib/lp/registry/browser/configure.zcml
    lib/lp/registry/browser/distributionsourcepackage.py
    lib/lp/registry/browser/sourcepackage.py
    lib/lp/registry/interfaces/distribution.py
    lib/lp/registry/interfaces/distributionsourcepackage.py
    lib/lp/registry/interfaces/product.py
    lib/lp/registry/model/distribution.py
    lib/lp/registry/model/distributionsourcepackage.py
    lib/lp/registry/model/product.py
    lib/lp/registry/model/sourcepackage.py


TEST

    ./bin/test -vv -t questiontarget -t question-add


IMPLEMENTATION

Split IQuestionTarget into Public and AnyPerson interfaces. Updated
IDistribution, IDistributionSourcePackage, and IProduct to inherit
IQuestionTarget.
    lib/lp/answers/interfaces/questiontarget.py
    lib/lp/registry/configure.zcml
    lib/lp/registry/interfaces/distribution.py
    lib/lp/registry/interfaces/distributionsourcepackage.py
    lib/lp/registry/interfaces/product.py
    lib/lp/registry/interfaces/sourcepackage.py
    lib/lp/registry/model/distribution.py
    lib/lp/registry/model/distributionsourcepackage.py
    lib/lp/registry/model/product.py
    lib/lp/registry/model/sourcepackage.py

Removed IQuestionTarget from ISourcePackage and added a redirect to ensure
stories/question-add.txt continues to work.
    lib/lp/registry/browser/configure.zcml
    lib/lp/registry/browser/sourcepackage.py
    lib/lp/registry/interfaces/sourcepackage.py
    lib/lp/registry/model/sourcepackage.py

Removed unneeded tests and test setups.
Removed the test that verified that a question created for a source package
was really created on a distribution source package
    lib/lp/answers/doc/questiontarget-sourcepackage.txt
    lib/lp/answers/tests/test_doc.py
    lib/lp/bugs/tests/test_bugtarget.py

Moved +gethelp to IDistributionSourcePackage. stories/question-add verifies
that the user still arrives at a page and can create a question.
    lib/lp/registry/browser/distributionsourcepackage.py

Removed the request to link the package to a project. The user just
came form his desktop to get help. There is near zero chance that the user has
packaging experience and Launchpad knowledge to do this.
    lib/lp/answers/templates/sourcepackage-gethelp.pt
-- 
https://code.launchpad.net/~sinzui/launchpad/answers-api-1/+merge/60804
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~sinzui/launchpad/answers-api-1 into lp:launchpad.
=== removed file 'lib/lp/answers/doc/questiontarget-sourcepackage.txt'
--- lib/lp/answers/doc/questiontarget-sourcepackage.txt	2011-05-09 16:35:10 +0000
+++ lib/lp/answers/doc/questiontarget-sourcepackage.txt	1970-01-01 00:00:00 +0000
@@ -1,141 +0,0 @@
-IQuestionTarget for SourcePackage
-=================================
-
-The implementation of IQuestionTarget by SourcePackage and
-DistributionSourcePackage contain some small differences with the other
-ones. (See questiontarget.txt for the generic interface description.)
-
-
-ISourcePackage.target
-.....................
-
-The target attribute of questions created on SourcePackage will be that
-of the DistributionSourcePackage (and not the SourcePackage):
-
-    >>> login('no-priv@xxxxxxxxxxxxx')
-    >>> from lp.registry.interfaces.distribution import IDistributionSet
-    >>> from lp.registry.interfaces.person import IPersonSet
-    >>> ubuntu = getUtility(IDistributionSet).getByName('ubuntu')
-    >>> firefox = ubuntu.currentseries.getSourcePackage('mozilla-firefox')
-
-    >>> question = firefox.getQuestion(3)
-    >>> question.target == firefox
-    False
-
-    >>> question.target == ubuntu.getSourcePackage('mozilla-firefox')
-    True
-
-
-get()
-.....
-
-The question created on a SourcePackage or DistributionSourcePackage can
-also be retrieved via the distribution.
-
-    >>> ubuntu.getQuestion(3) == question
-    True
-
-    # Create a new question on the evolution source package.
-
-    >>> sample_person = getUtility(IPersonSet).getByName('name16')
-    >>> evolution = ubuntu.getSourcePackage('evolution')
-    >>> evolution_question = evolution.newQuestion(
-    ...     sample_person, 'Evolution crashed',
-    ...     'Surprise, surprise! Evolution crashed... again...'
-    ...     'while clicking on a thread expander.')
-
-    >>> ubuntu.getQuestion(evolution_question.id) == evolution_question
-        True
-
-
-Support Contacts
-................
-
-The support contacts of a SourcePackage contain all the support contacts
-from its containing distribution.
-
-    >>> list(ubuntu.answer_contacts)
-    []
-
-    >>> list(evolution.answer_contacts)
-    []
-
-    >>> ubuntu.addAnswerContact(sample_person, sample_person)
-    True
-
-    >>> [person.name for person in evolution.answer_contacts]
-    [u'name16']
-
-It is possible to obtain the list of IPerson registed as support
-contacts specifically for the source package through the
-direct_answer_contacts attributes.
-
-    >>> [person.name for person in evolution.direct_answer_contacts]
-    []
-
-A user can still subscribe as support contact for specific sourcepackage
-even if he's already a support contact for the distribution.
-
-    >>> evolution.addAnswerContact(sample_person, sample_person)
-    True
-
-    >>> [person.name for person in evolution.direct_answer_contacts]
-    [u'name16']
-
-But he's only listed once in the support contacts list.
-
-    >>> [person.name for person in evolution.answer_contacts]
-    [u'name16']
-
-The removeAnswerContact() method can only be used to remove the user
-from the sourcepackage support contact list, not from the ubuntu
-distribution list:
-
-    >>> evolution.removeAnswerContact(sample_person, sample_person)
-    True
-
-    >>> [person.name for person in evolution.direct_answer_contacts]
-    []
-
-    >>> [person.name for person in evolution.answer_contacts]
-    [u'name16']
-
-    >>> evolution.removeAnswerContact(sample_person, sample_person)
-    False
-
-    >>> [person.name for person in evolution.answer_contacts]
-    [u'name16']
-
-The same principle applies to DistributionSourcePackage:
-
-    >>> [person.name for person in evolution.direct_answer_contacts]
-    []
-
-    >>> [person.name for person in evolution.answer_contacts]
-    [u'name16']
-
-    >>> evolution.addAnswerContact(sample_person, sample_person)
-    True
-
-    >>> [person.name for person in evolution.direct_answer_contacts]
-    [u'name16']
-
-    >>> [person.name for person in evolution.answer_contacts]
-    [u'name16']
-
-    >>> evolution.removeAnswerContact(sample_person, sample_person)
-    True
-
-    >>> [person.name for person in evolution.answer_contacts]
-    [u'name16']
-
-    >>> evolution.removeAnswerContact(sample_person, sample_person)
-    False
-
-    >>> [person.name for person in evolution.direct_answer_contacts]
-    []
-
-    >>> [person.name for person in evolution.answer_contacts]
-    [u'name16']
-
-

=== modified file 'lib/lp/answers/interfaces/questiontarget.py'
--- lib/lp/answers/interfaces/questiontarget.py	2011-05-06 18:11:56 +0000
+++ lib/lp/answers/interfaces/questiontarget.py	2011-05-12 15:26:36 +0000
@@ -48,10 +48,83 @@
 from lp.services.worlddata.interfaces.language import ILanguage
 
 
-class IQuestionTarget(ISearchableByQuestionOwner):
-    """An object that can have a new question asked about it."""
-
-    export_as_webservice_entry(as_of='devel')
+class IQuestionTargetPublic(ISearchableByQuestionOwner):
+    """Methods that anonymous in user can access."""
+
+    @operation_parameters(
+        question_id=Int(title=_('Question Number'), required=True))
+    @export_read_operation()
+    @operation_for_version('devel')
+    def getQuestion(question_id):
+        """Return the question by its id, if it is applicable to this target.
+
+        :question_id: A question id.
+
+        If there is no such question number for this target, return None
+        """
+
+    def findSimilarQuestions(title):
+        """Return questions similar to title.
+
+        Return a list of question similar to the title provided. These
+        questions should be found using a fuzzy search. The list should be
+        ordered from the most similar question to the least similar question.
+
+        :title: A phrase
+        """
+
+    @operation_parameters(
+        language=Reference(ILanguage))
+    @operation_returns_collection_of(IPerson)
+    @export_read_operation()
+    @operation_for_version('devel')
+    def getAnswerContactsForLanguage(language):
+        """Return the list of Persons that provide support for a language.
+
+        An answer contact supports questions in his preferred languages.
+        """
+
+    def getAnswerContactRecipients(language):
+        """Return an `INotificationRecipientSet` of answer contacts.
+
+        :language: an ILanguage or None. When language is none, all
+                   answer contacts are returned.
+
+        Return an INotificationRecipientSet of the answer contacts and the
+        reason they are recipients of an email. The answer contacts are
+        selected by their language and the fact that they are answer contacts
+        for the QuestionTarget.
+        """
+
+    @operation_returns_collection_of(ILanguage)
+    @export_read_operation()
+    @operation_for_version('devel')
+    def getSupportedLanguages():
+        """Return a list of languages spoken by at the answer contacts.
+
+        An answer contact is considered to speak a given language if that
+        language is listed as one of his preferred languages.
+        """
+
+    answer_contacts = List(
+        title=_("Answer Contacts"),
+        description=_(
+            "Persons that are willing to provide support for this target. "
+            "They receive email notifications about each new question as "
+            "well as for changes to any questions related to this target."),
+        value_type=PublicPersonChoice(vocabulary="ValidPersonOrTeam"))
+
+    direct_answer_contacts = List(
+        title=_("Direct Answer Contacts"),
+        description=_(
+            "IPersons that registered as answer contacts explicitely on "
+            "this target. (answer_contacts may include answer contacts "
+            "inherited from other context.)"),
+        value_type=PublicPersonChoice(vocabulary="ValidPersonOrTeam"))
+
+
+class IQuestionTargetView(Interface):
+    """Methods that logged in user can access."""
 
     def newQuestion(owner, title, description, language=None,
                     datecreated=None):
@@ -87,28 +160,6 @@
         """
 
     @operation_parameters(
-        question_id=Int(title=_('Question Number'), required=True))
-    @export_read_operation()
-    @operation_for_version('devel')
-    def getQuestion(question_id):
-        """Return the question by its id, if it is applicable to this target.
-
-        :question_id: A question id.
-
-        If there is no such question number for this target, return None
-        """
-
-    def findSimilarQuestions(title):
-        """Return questions similar to title.
-
-        Return a list of question similar to the title provided. These
-        questions should be found using a fuzzy search. The list should be
-        ordered from the most similar question to the least similar question.
-
-        :title: A phrase
-        """
-
-    @operation_parameters(
         person=PublicPersonChoice(
             title=_('The user or an administered team'), required=True,
             vocabulary='ValidPersonOrTeam'))
@@ -159,54 +210,10 @@
             answer contact.
         """
 
-    @operation_parameters(
-        language=Reference(ILanguage))
-    @operation_returns_collection_of(IPerson)
-    @export_read_operation()
-    @operation_for_version('devel')
-    def getAnswerContactsForLanguage(language):
-        """Return the list of Persons that provide support for a language.
-
-        An answer contact supports questions in his preferred languages.
-        """
-
-    def getAnswerContactRecipients(language):
-        """Return an `INotificationRecipientSet` of answer contacts.
-
-        :language: an ILanguage or None. When language is none, all
-                   answer contacts are returned.
-
-        Return an INotificationRecipientSet of the answer contacts and the
-        reason they are recipients of an email. The answer contacts are
-        selected by their language and the fact that they are answer contacts
-        for the QuestionTarget.
-        """
-
-    @operation_returns_collection_of(ILanguage)
-    @export_read_operation()
-    @operation_for_version('devel')
-    def getSupportedLanguages():
-        """Return a list of languages spoken by at the answer contacts.
-
-        An answer contact is considered to speak a given language if that
-        language is listed as one of his preferred languages.
-        """
-
-    answer_contacts = List(
-        title=_("Answer Contacts"),
-        description=_(
-            "Persons that are willing to provide support for this target. "
-            "They receive email notifications about each new question as "
-            "well as for changes to any questions related to this target."),
-        value_type=PublicPersonChoice(vocabulary="ValidPersonOrTeam"))
-
-    direct_answer_contacts = List(
-        title=_("Direct Answer Contacts"),
-        description=_(
-            "IPersons that registered as answer contacts explicitely on "
-            "this target. (answer_contacts may include answer contacts "
-            "inherited from other context.)"),
-        value_type=PublicPersonChoice(vocabulary="ValidPersonOrTeam"))
+
+class IQuestionTarget(IQuestionTargetPublic, IQuestionTargetView):
+    """An object that can have a new question asked about it."""
+    export_as_webservice_entry(as_of='devel')
 
 
 # These schemas are only used by browser/questiontarget.py and should really

=== modified file 'lib/lp/answers/templates/sourcepackage-gethelp.pt'
--- lib/lp/answers/templates/sourcepackage-gethelp.pt	2009-09-13 20:51:35 +0000
+++ lib/lp/answers/templates/sourcepackage-gethelp.pt	2011-05-12 15:26:36 +0000
@@ -50,17 +50,6 @@
     <a href="http://ubuntu.com/support/supportoptions/local";>support
     in languages other than English</a>.
   </p>
-
-  <tal:needupstream condition="not: context/productseries">
-  <h2>Help us help you</h2>
-  <p class="helpwanted message" tal:condition="not: context/productseries">
-    Launchpad doesn&#8217;t know which project and series this package
-    belongs to.
-    If you can, please <a href="+edit-packaging">let us know</a>,
-    so we can provide you and other people with relevant help sources.
-    Thanks!
-  </p>
-  </tal:needupstream>
   </div>
 </div>
 </body>

=== modified file 'lib/lp/answers/tests/test_doc.py'
--- lib/lp/answers/tests/test_doc.py	2010-10-04 19:50:45 +0000
+++ lib/lp/answers/tests/test_doc.py	2011-05-12 15:26:36 +0000
@@ -99,7 +99,6 @@
         'questiontarget.txt',
         [('product', productSetUp),
          ('distribution', distributionSetUp),
-         ('sourcepackage', sourcepackageSetUp),
          ('distributionsourcepackage', distributionsourcepackageSetUp),
          ]),
 

=== modified file 'lib/lp/bugs/tests/test_bugtarget.py'
--- lib/lp/bugs/tests/test_bugtarget.py	2011-04-22 15:18:02 +0000
+++ lib/lp/bugs/tests/test_bugtarget.py	2011-05-12 15:26:36 +0000
@@ -178,16 +178,6 @@
     test.globs['question_target'] = ubuntu.getSourcePackage('mozilla-firefox')
 
 
-def sourcePackageForQuestionSetUp(test):
-    """Setup the `ISourcePackage` test for QuestionTarget testing."""
-    setUp(test)
-    ubuntu = getUtility(IDistributionSet).getByName('ubuntu')
-    warty = ubuntu.getSeries('warty')
-    test.globs['bugtarget'] = warty.getSourcePackage('mozilla-firefox')
-    test.globs['filebug'] = sourcepackage_filebug_for_question
-    test.globs['question_target'] = ubuntu.getSourcePackage('mozilla-firefox')
-
-
 class TestBugTargetSearchTasks(TestCaseWithFactory):
     """Tests of IHasBugs.searchTasks()."""
 
@@ -293,7 +283,6 @@
         distributionSetUp,
         distributionSourcePackageSetUp,
         distributionSeriesSetUp,
-        sourcePackageForQuestionSetUp,
         ]
 
     for setUpMethod in setUpMethods:
@@ -302,7 +291,6 @@
             layer=DatabaseFunctionalLayer)
         suite.addTest(test)
 
-    setUpMethods.remove(sourcePackageForQuestionSetUp)
     setUpMethods.append(sourcePackageSetUp)
     setUpMethods.append(projectSetUp)
 

=== modified file 'lib/lp/registry/browser/configure.zcml'
--- lib/lp/registry/browser/configure.zcml	2011-04-17 18:00:45 +0000
+++ lib/lp/registry/browser/configure.zcml	2011-05-12 15:26:36 +0000
@@ -508,9 +508,17 @@
         name="+edit"
         facet="overview"
         template="../../app/templates/generic-edit.pt"/>
+    <browser:page
+        for="lp.registry.interfaces.distributionsourcepackage.IDistributionSourcePackage"
+        permission="zope.Public"
+        class="lp.registry.browser.distributionsourcepackage.DistributionSourcePackageHelpView"
+        name="+gethelp"
+        facet="answers"
+        template="../../answers/templates/sourcepackage-gethelp.pt"/>
     <browser:menus
         classes="
             DistributionSourcePackageActionMenu
+            DistributionSourcePackageAnswersMenu
             DistributionSourcePackageBugsMenu
             DistributionSourcePackageFacets
             DistributionSourcePackageOverviewMenu"
@@ -2088,10 +2096,6 @@
         for="lp.registry.interfaces.sourcepackage.ISourcePackage"
         layer="lp.bugs.publisher.BugsLayer"
         name="+bugs"/>
-    <browser:defaultView
-        for="lp.registry.interfaces.sourcepackage.ISourcePackage"
-        layer="lp.answers.publisher.AnswersLayer"
-        name="+questions"/>
     <browser:navigation
         module="lp.registry.browser.sourcepackage"
         classes="
@@ -2137,18 +2141,10 @@
         facet="overview"
         class="lp.registry.browser.sourcepackage.SourcePackageUpstreamConnectionsView"
         template="../templates/sourcepackage-upstream-connections.pt"/>
-    <browser:page
-        for="lp.registry.interfaces.sourcepackage.ISourcePackage"
-        permission="zope.Public"
-        class="lp.registry.browser.sourcepackage.SourcePackageHelpView"
-        name="+gethelp"
-        facet="answers"
-        template="../../answers/templates/sourcepackage-gethelp.pt"/>
     <browser:menus
         classes="
             SourcePackageFacets
-            SourcePackageOverviewMenu
-            SourcePackageAnswersMenu"
+            SourcePackageOverviewMenu"
         module="lp.registry.browser.sourcepackage"/>
     <browser:navigation
         module="lp.registry.browser.productrelease"

=== modified file 'lib/lp/registry/browser/distributionsourcepackage.py'
--- lib/lp/registry/browser/distributionsourcepackage.py	2011-04-26 15:44:26 +0000
+++ lib/lp/registry/browser/distributionsourcepackage.py	2011-05-12 15:26:36 +0000
@@ -5,10 +5,12 @@
 
 __all__ = [
     'distribution_from_distributionsourcepackage',
+    'DistributionSourcePackageAnswersMenu',
     'DistributionSourcePackageBreadcrumb',
     'DistributionSourcePackageChangelogView',
     'DistributionSourcePackageEditView',
     'DistributionSourcePackageFacets',
+    'DistributionSourcePackageHelpView',
     'DistributionSourcePackageNavigation',
     'DistributionSourcePackageOverviewMenu',
     'DistributionSourcePackagePublishingHistoryView',
@@ -52,6 +54,7 @@
 from canonical.launchpad.webapp.sorting import sorted_dotted_numbers
 from canonical.lazr.utils import smartquote
 from lp.answers.browser.questiontarget import (
+    QuestionTargetAnswersMenu,
     QuestionTargetFacetMixin,
     QuestionTargetTraversalMixin,
     )
@@ -170,6 +173,17 @@
         return links
 
 
+class DistributionSourcePackageAnswersMenu(QuestionTargetAnswersMenu):
+
+    usedfor = IDistributionSourcePackage
+    facet = 'answers'
+
+    links = QuestionTargetAnswersMenu.links + ['gethelp']
+
+    def gethelp(self):
+        return Link('+gethelp', 'Help and support options', icon='info')
+
+
 class DistributionSourcePackageNavigation(Navigation,
     BugTargetTraversalMixin, HasCustomLanguageCodesTraversalMixin,
     QuestionTargetTraversalMixin,
@@ -600,3 +614,9 @@
         return canonical_url(self.context)
 
     cancel_url = next_url
+
+
+class DistributionSourcePackageHelpView:
+    """A View to show Answers help."""
+
+    page_title = 'Help and support options'

=== modified file 'lib/lp/registry/browser/sourcepackage.py'
--- lib/lp/registry/browser/sourcepackage.py	2011-05-12 12:41:00 +0000
+++ lib/lp/registry/browser/sourcepackage.py	2011-05-12 15:26:36 +0000
@@ -11,7 +11,6 @@
     'SourcePackageBreadcrumb',
     'SourcePackageChangeUpstreamView',
     'SourcePackageFacets',
-    'SourcePackageHelpView',
     'SourcePackageNavigation',
     'SourcePackageOverviewMenu',
     'SourcePackageRemoveUpstreamView',
@@ -80,10 +79,6 @@
 from canonical.launchpad.webapp.menu import structured
 from canonical.launchpad.webapp.publisher import LaunchpadView
 from canonical.lazr.utils import smartquote
-from lp.answers.browser.questiontarget import (
-    QuestionTargetAnswersMenu,
-    QuestionTargetFacetMixin,
-    )
 from lp.app.browser.launchpadform import (
     action,
     custom_widget,
@@ -180,9 +175,7 @@
     @stepto('+filebug')
     def filebug(self):
         """Redirect to the IDistributionSourcePackage +filebug page."""
-        sourcepackage = self.context
-        distro_sourcepackage = sourcepackage.distribution.getSourcePackage(
-            sourcepackage.name)
+        distro_sourcepackage = self.context.distribution_sourcepackage
 
         redirection_url = canonical_url(
             distro_sourcepackage, view_name='+filebug')
@@ -190,6 +183,13 @@
             redirection_url += '?no-redirect'
         return self.redirectSubTree(redirection_url, status=303)
 
+    @stepto('+gethelp')
+    def gethelp(self):
+        """Redirect to the IDistributionSourcePackage +gethelp page."""
+        dsp = self.context.distribution_sourcepackage
+        redirection_url = canonical_url(dsp, view_name='+gethelp')
+        return redirection(redirection_url)
+
 
 @adapter(ISourcePackage)
 @implementer(IServiceUsage)
@@ -207,10 +207,10 @@
         return smartquote('"%s" source package') % (self.context.name)
 
 
-class SourcePackageFacets(QuestionTargetFacetMixin, StandardLaunchpadFacets):
+class SourcePackageFacets(StandardLaunchpadFacets):
 
     usedfor = ISourcePackage
-    enable_only = ['overview', 'bugs', 'branches', 'answers', 'translations']
+    enable_only = ['overview', 'bugs', 'branches', 'translations']
 
 
 class SourcePackageOverviewMenu(ApplicationMenu):
@@ -260,17 +260,6 @@
         return packaging.userCanDelete()
 
 
-class SourcePackageAnswersMenu(QuestionTargetAnswersMenu):
-
-    usedfor = ISourcePackage
-    facet = 'answers'
-
-    links = QuestionTargetAnswersMenu.links + ['gethelp']
-
-    def gethelp(self):
-        return Link('+gethelp', 'Help and support options', icon='info')
-
-
 class SourcePackageChangeUpstreamStepOne(ReturnToReferrerMixin, StepView):
     """A view to set the `IProductSeries` of a sourcepackage."""
     schema = Interface
@@ -549,12 +538,6 @@
         return list(self.context.getCurrentTranslationTemplates())
 
 
-class SourcePackageHelpView:
-    """A View to show Answers help."""
-
-    page_title = 'Help and support options'
-
-
 class SourcePackageAssociationPortletView(LaunchpadFormView):
     """A view for linking to an upstream package."""
 

=== modified file 'lib/lp/registry/configure.zcml'
--- lib/lp/registry/configure.zcml	2011-05-05 18:15:23 +0000
+++ lib/lp/registry/configure.zcml	2011-05-12 15:26:36 +0000
@@ -513,25 +513,11 @@
 
         <!-- IQuestionTarget -->
 
-        <allow
-            attributes="
-                getQuestion
-                searchQuestions
-                findSimilarQuestions
-                answer_contacts
-                direct_answer_contacts
-                getSupportedLanguages
-                getQuestionLanguages
-                getAnswerContactsForLanguage
-                getAnswerContactRecipients"/>
+        <allow interface="lp.answers.interfaces.questiontarget.IQuestionTargetPublic"/>
         <require
-            permission="launchpad.AnyPerson"
-            attributes="
-                newQuestion
-                createQuestionFromBug
-                canUserAlterAnswerContact
-                addAnswerContact
-                removeAnswerContact"/>
+          permission="launchpad.AnyPerson"
+          interface="lp.answers.interfaces.questiontarget.IQuestionTargetView"/>
+
         <require
             permission="launchpad.BugSupervisor"
             set_attributes="
@@ -1254,25 +1240,10 @@
 
         <!-- IQuestionTarget -->
 
-        <allow
-            attributes="
-                getQuestion
-                searchQuestions
-                findSimilarQuestions
-                answer_contacts
-                direct_answer_contacts
-                getSupportedLanguages
-                getQuestionLanguages
-                getAnswerContactsForLanguage
-                getAnswerContactRecipients"/>
+        <allow interface="lp.answers.interfaces.questiontarget.IQuestionTargetPublic"/>
         <require
-            permission="launchpad.AnyPerson"
-            attributes="
-                newQuestion
-                createQuestionFromBug
-                canUserAlterAnswerContact
-                addAnswerContact
-                removeAnswerContact"/>
+          permission="launchpad.AnyPerson"
+          interface="lp.answers.interfaces.questiontarget.IQuestionTargetView"/>
 
         <!-- IFAQTarget -->
 
@@ -1536,26 +1507,10 @@
 
         <!-- IQuestionTarget -->
 
-        <allow
-            attributes="
-                getQuestion
-                searchQuestions
-                findSimilarQuestions
-                answer_contacts
-                direct_answer_contacts
-                getSupportedLanguages
-                getQuestionLanguages
-                getAnswerContactsForLanguage
-                getAnswerContactRecipients"/>
+        <allow interface="lp.answers.interfaces.questiontarget.IQuestionTargetPublic"/>
         <require
-            permission="launchpad.AnyPerson"
-            attributes="
-                newQuestion
-                createQuestionFromBug
-                canUserAlterAnswerContact
-                addAnswerContact
-                removeAnswerContact"/>
-
+          permission="launchpad.AnyPerson"
+          interface="lp.answers.interfaces.questiontarget.IQuestionTargetView"/>
 
         <!-- IFAQTarget -->
 
@@ -1631,28 +1586,6 @@
             interface="lp.bugs.interfaces.bugtarget.IHasBugHeat"/>
         <allow
             interface="lp.soyuz.interfaces.buildrecords.IHasBuildRecords"/>
-
-        <!-- IQuestionTarget -->
-
-        <allow
-            attributes="
-                getQuestion
-                searchQuestions
-                findSimilarQuestions
-                answer_contacts
-                direct_answer_contacts
-                getQuestionLanguages
-                getAnswerContactsForLanguage
-                getSupportedLanguages
-                getAnswerContactRecipients"/>
-        <require
-            permission="launchpad.AnyPerson"
-            attributes="
-                newQuestion
-                createQuestionFromBug
-                canUserAlterAnswerContact
-                addAnswerContact
-                removeAnswerContact"/>
     </class>
     <securedutility
         component="lp.registry.model.sourcepackage.SourcePackage"

=== modified file 'lib/lp/registry/interfaces/distribution.py'
--- lib/lp/registry/interfaces/distribution.py	2011-04-14 20:40:03 +0000
+++ lib/lp/registry/interfaces/distribution.py	2011-05-12 15:26:36 +0000
@@ -57,6 +57,7 @@
     IHasAppointedDriver,
     IHasDrivers,
     )
+from lp.answers.interfaces.questiontarget import IQuestionTarget
 from lp.app.errors import NameLookupFailed
 from lp.app.interfaces.headings import IRootContext
 from lp.app.interfaces.launchpad import (
@@ -641,7 +642,7 @@
 
 class IDistribution(
     IDistributionEditRestricted, IDistributionPublic, IHasBugSupervisor,
-    IRootContext, IStructuralSubscriptionTarget):
+    IQuestionTarget, IRootContext, IStructuralSubscriptionTarget):
     """An operating system distribution.
 
     Launchpadlib example: retrieving the current version of a package in a

=== modified file 'lib/lp/registry/interfaces/distributionsourcepackage.py'
--- lib/lp/registry/interfaces/distributionsourcepackage.py	2011-01-21 08:12:29 +0000
+++ lib/lp/registry/interfaces/distributionsourcepackage.py	2011-05-12 15:26:36 +0000
@@ -31,6 +31,7 @@
     )
 
 from canonical.launchpad import _
+from lp.answers.interfaces.questiontarget import IQuestionTarget
 from lp.bugs.interfaces.bugtarget import (
     IBugTarget,
     IHasOfficialBugTags,
@@ -48,8 +49,9 @@
 
 
 class IDistributionSourcePackage(IBugTarget, IHasBranches, IHasMergeProposals,
+                                 IHasOfficialBugTags,
                                  IStructuralSubscriptionTarget,
-                                 IHasOfficialBugTags):
+                                 IQuestionTarget):
     """Represents a source package in a distribution.
 
     Create IDistributionSourcePackages by invoking

=== modified file 'lib/lp/registry/interfaces/product.py'
--- lib/lp/registry/interfaces/product.py	2011-04-12 23:25:45 +0000
+++ lib/lp/registry/interfaces/product.py	2011-05-12 15:26:36 +0000
@@ -77,6 +77,7 @@
     IHasLogo,
     IHasMugshot,
     )
+from lp.answers.interfaces.questiontarget import IQuestionTarget
 from lp.app.errors import NameLookupFailed
 from lp.app.interfaces.headings import IRootContext
 from lp.app.interfaces.launchpad import (
@@ -815,7 +816,8 @@
 class IProduct(
     IHasBugSupervisor, IProductEditRestricted,
     IProductModerateRestricted, IProductDriverRestricted,
-    IProductPublic, IRootContext, IStructuralSubscriptionTarget):
+    IProductPublic, IQuestionTarget, IRootContext,
+    IStructuralSubscriptionTarget):
     """A Product.
 
     The Launchpad Registry describes the open source world as ProjectGroups

=== modified file 'lib/lp/registry/model/distribution.py'
--- lib/lp/registry/model/distribution.py	2011-04-26 16:25:00 +0000
+++ lib/lp/registry/model/distribution.py	2011-05-12 15:26:36 +0000
@@ -70,7 +70,6 @@
 from canonical.launchpad.webapp.url import urlparse
 from lp.answers.interfaces.faqtarget import IFAQTarget
 from lp.answers.enums import QUESTION_STATUS_DEFAULT_SEARCH
-from lp.answers.interfaces.questiontarget import IQuestionTarget
 from lp.answers.model.faq import (
     FAQ,
     FAQSearch,
@@ -219,7 +218,7 @@
     implements(
         IDistribution, IFAQTarget, IHasBugHeat, IHasBugSupervisor,
         IHasBuildRecords, IHasIcon, IHasLogo, IHasMugshot, ILaunchpadUsage,
-        IQuestionTarget, IServiceUsage)
+        IServiceUsage)
 
     _table = 'Distribution'
     _defaultOrder = 'name'

=== modified file 'lib/lp/registry/model/distributionsourcepackage.py'
--- lib/lp/registry/model/distributionsourcepackage.py	2011-04-12 06:21:39 +0000
+++ lib/lp/registry/model/distributionsourcepackage.py	2011-05-12 15:26:36 +0000
@@ -38,7 +38,6 @@
 from canonical.launchpad.database.emailaddress import EmailAddress
 from canonical.launchpad.interfaces.lpstorm import IStore
 from canonical.lazr.utils import smartquote
-from lp.answers.interfaces.questiontarget import IQuestionTarget
 from lp.bugs.interfaces.bugtarget import IHasBugHeat
 from lp.bugs.interfaces.bugtask import UNRESOLVED_BUGTASK_STATUSES
 from lp.bugs.model.bug import (
@@ -140,8 +139,7 @@
     """
 
     implements(
-        IDistributionSourcePackage, IHasBugHeat, IHasCustomLanguageCodes,
-        IQuestionTarget)
+        IDistributionSourcePackage, IHasBugHeat, IHasCustomLanguageCodes)
 
     bug_reporting_guidelines = DistributionSourcePackageProperty(
         'bug_reporting_guidelines')

=== modified file 'lib/lp/registry/model/product.py'
--- lib/lp/registry/model/product.py	2011-04-26 16:25:00 +0000
+++ lib/lp/registry/model/product.py	2011-05-12 15:26:36 +0000
@@ -81,7 +81,6 @@
 from canonical.lazr.utils import safe_hasattr
 from lp.answers.interfaces.faqtarget import IFAQTarget
 from lp.answers.enums import QUESTION_STATUS_DEFAULT_SEARCH
-from lp.answers.interfaces.questiontarget import IQuestionTarget
 from lp.answers.model.faq import (
     FAQ,
     FAQSearch,
@@ -309,7 +308,7 @@
     implements(
         IFAQTarget, IHasBugHeat, IHasBugSupervisor, IHasCustomLanguageCodes,
         IHasIcon, IHasLogo, IHasMugshot, ILaunchpadUsage, IProduct,
-        IQuestionTarget, IServiceUsage)
+        IServiceUsage)
 
     _table = 'Product'
 

=== modified file 'lib/lp/registry/model/sourcepackage.py'
--- lib/lp/registry/model/sourcepackage.py	2011-05-09 18:57:18 +0000
+++ lib/lp/registry/model/sourcepackage.py	2011-05-12 15:26:36 +0000
@@ -33,7 +33,6 @@
 from canonical.lazr.utils import smartquote
 from canonical.launchpad.webapp.interfaces import ILaunchBag
 from lp.answers.enums import QUESTION_STATUS_DEFAULT_SEARCH
-from lp.answers.interfaces.questiontarget import IQuestionTarget
 from lp.answers.model.question import (
     QuestionTargetMixin,
     QuestionTargetSearch,
@@ -187,10 +186,9 @@
         return sorted(answer_contacts, key=attrgetter('displayname'))
 
 
-class SourcePackage(BugTargetBase, SourcePackageQuestionTargetMixin,
+class SourcePackage(BugTargetBase, HasBugHeatMixin, HasCodeImportsMixin,
                     HasTranslationImportsMixin, HasTranslationTemplatesMixin,
-                    HasBranchesMixin, HasMergeProposalsMixin,
-                    HasBugHeatMixin, HasCodeImportsMixin):
+                    HasBranchesMixin, HasMergeProposalsMixin):
     """A source package, e.g. apache2, in a distroseries.
 
     This object is not a true database object, but rather attempts to
@@ -199,7 +197,7 @@
     """
 
     implements(
-        ISourcePackage, IHasBugHeat, IHasBuildRecords, IQuestionTarget)
+        ISourcePackage, IHasBugHeat, IHasBuildRecords)
 
     classProvides(ISourcePackageFactory)