← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~edwin-grubbs/launchpad/bug-597738-bug-service-status into lp:launchpad/devel

 

Edwin Grubbs has proposed merging lp:~edwin-grubbs/launchpad/bug-597738-bug-service-status into lp:launchpad/devel with lp:~jcsackett/launchpad/deprecate-official_codehosting as a prerequisite.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  #597738 Launchpad must state the project's official services
  https://bugs.launchpad.net/bugs/597738


Summary
-------

This branch provides the user with more information about filing bugs on
the bugs.launchpad.net/$project page. This branch also takes advantage
of the new bug_tracking_usage attribute that returns an enum as opposed
to the old official_malone boolean. If the project is not using
Launchpad as the bug tracker, the page will also inform the user of any
ubuntu packages linked to the project under which bugs can be filed.

This branch is dependent on
lp:~jcsackett/launchpad/deprecate-official_codehosting

Implementation details
----------------------

lib/lp/answers/browser/questiontarget.py
lib/lp/answers/browser/tests/test_questiontarget.py
lib/lp/answers/templates/unknown-support.pt
lib/lp/bugs/browser/bugtarget.py
lib/lp/bugs/stories/bugs/xx-front-page-info.txt
lib/lp/bugs/templates/bugtarget-bugs.pt
lib/lp/registry/doc/product.txt
lib/lp/registry/interfaces/product.py
lib/lp/registry/model/product.py

Tests
-----

./bin/test -vv -t 'xx-front-page-info.txt|doc/product.txt'

Demo and Q/A
------------

* Open http://launchpad.dev/firefox/+configure-bugtracker
  * Switch the "Bugs are tracked" field between the values.
* Open http://bugs.launchpad.dev/firefox
  * If bugs are tracked in launchpad, a bug table should be shown.
  * If bugs are tracked externally, the page should contain:
      <meta name="robots" content="noindex,nofollow"/>
      <strong>Bugs are tracked in _SOME BUGTRACKER_.</strong>
      
      <a>Getting started with bug tracking in Launchpad.</a>
      Firefox bug reports are also tracked in <a>mozilla-firefox in Ubuntu</a>.

  * If launchpad doesn't know where bugs are tracked, it should contain:
      <meta name="robots" content="noindex,nofollow"/>
      <strong>
      Launchpad does not know where to forward bug reports to contact the*
      developers of Mozilla Firefox.
      </strong>

      <a>Getting started with bug tracking in Launchpad.</a>
      Firefox bug reports are tracked in <a>mozilla-firefox in Ubuntu</a>.
-- 
https://code.launchpad.net/~edwin-grubbs/launchpad/bug-597738-bug-service-status/+merge/34250
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~edwin-grubbs/launchpad/bug-597738-bug-service-status into lp:launchpad/devel.
=== modified file 'lib/canonical/launchpad/doc/vocabularies.txt'
--- lib/canonical/launchpad/doc/vocabularies.txt	2010-08-26 08:02:08 +0000
+++ lib/canonical/launchpad/doc/vocabularies.txt	2010-08-31 21:27:50 +0000
@@ -259,9 +259,9 @@
 
     >>> mozilla_project = getUtility(IProjectGroupSet).getByName('mozilla')
     >>> for product in mozilla_project.products:
-    ...     print "%s: %s" % (product.name, product.official_malone)
-    firefox: True
-    thunderbird: False
+    ...     print "%s: %s" % (product.name, product.bug_tracking_usage.name)
+    firefox: LAUNCHPAD
+    thunderbird: UNKNOWN
 
     >>> mozilla_products_vocabulary = vocabulary_registry.get(
     ...     mozilla_project,'ProjectProductsUsingMalone')

=== modified file 'lib/canonical/launchpad/vocabularies/dbobjects.py'
--- lib/canonical/launchpad/vocabularies/dbobjects.py	2010-08-26 08:02:08 +0000
+++ lib/canonical/launchpad/vocabularies/dbobjects.py	2010-08-31 21:27:50 +0000
@@ -85,6 +85,7 @@
     SQLObjectVocabularyBase,
     )
 from lp.app.browser.stringformatter import FormattersAPI
+from lp.app.enums import ServiceUsage
 from lp.blueprints.model.specification import Specification
 from lp.blueprints.model.sprint import Sprint
 from lp.bugs.interfaces.bugtask import IBugTask
@@ -231,6 +232,7 @@
     These are branches that the user is guaranteed to be able to push
     to.
     """
+
     def __init__(self, context=None):
         """Pass a Person as context, or anything else for the current user."""
         super(HostedBranchRestrictedOnOwnerVocabulary, self).__init__(context)
@@ -338,6 +340,7 @@
     This vocabulary contains all the languages known to Launchpad,
     excluding English and non-visible languages.
     """
+
     def __contains__(self, language):
         """See `IVocabulary`.
 
@@ -379,7 +382,7 @@
     return SimpleVocabulary([
         SimpleTerm(product, product.name, title=product.displayname)
         for product in project.products
-        if product.official_malone])
+        if product.bug_tracking_usage == ServiceUsage.LAUNCHPAD])
 
 
 class TranslationGroupVocabulary(NamedSQLObjectVocabulary):
@@ -413,14 +416,12 @@
         if context.productseries != None:
             self._filter = AND(
                 POTemplate.iscurrent == True,
-                POTemplate.productseries == context.productseries
-            )
+                POTemplate.productseries == context.productseries)
         else:
             self._filter = AND(
                 POTemplate.iscurrent == True,
                 POTemplate.distroseries == context.distroseries,
-                POTemplate.sourcepackagename == context.sourcepackagename
-            )
+                POTemplate.sourcepackagename == context.sourcepackagename)
         super(TranslationTemplateVocabulary, self).__init__(context)
 
     def toTerm(self, obj):
@@ -661,7 +662,8 @@
         return Distribution.selectBy(official_malone=True).count()
 
     def __contains__(self, obj):
-        return IDistribution.providedBy(obj) and obj.official_malone
+        return (IDistribution.providedBy(obj)
+                and obj.bug_tracking_usage == ServiceUsage.LAUNCHPAD)
 
     def getQuery(self):
         return None

=== modified file 'lib/lp/answers/browser/questiontarget.py'
--- lib/lp/answers/browser/questiontarget.py	2010-08-26 17:45:46 +0000
+++ lib/lp/answers/browser/questiontarget.py	2010-08-31 21:27:50 +0000
@@ -506,22 +506,6 @@
                 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

=== modified file 'lib/lp/answers/browser/tests/test_questiontarget.py'
--- lib/lp/answers/browser/tests/test_questiontarget.py	2010-08-26 17:45:46 +0000
+++ lib/lp/answers/browser/tests/test_questiontarget.py	2010-08-31 21:27:50 +0000
@@ -133,30 +133,6 @@
         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."""
 

=== modified file 'lib/lp/answers/templates/unknown-support.pt'
--- lib/lp/answers/templates/unknown-support.pt	2010-08-05 17:14:05 +0000
+++ lib/lp/answers/templates/unknown-support.pt	2010-08-31 21:27:50 +0000
@@ -21,9 +21,9 @@
         </p>
 
         <p id="ubuntu-support"
-          tal:define="packages view/ubuntu_packages"
+          tal:define="packages context/ubuntu_packages | nothing"
           tal:condition="packages">
-          <tal:project replace="context/displayname" /> questions are also
+          <tal:project replace="context/displayname" /> questions are
           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>.

=== modified file 'lib/lp/bugs/browser/bugalsoaffects.py'
--- lib/lp/bugs/browser/bugalsoaffects.py	2010-08-20 20:31:18 +0000
+++ lib/lp/bugs/browser/bugalsoaffects.py	2010-08-31 21:27:50 +0000
@@ -56,6 +56,7 @@
 from canonical.widgets.itemswidgets import LaunchpadRadioWidget
 from canonical.widgets.popup import SearchForUpstreamPopupWidget
 from canonical.widgets.textwidgets import StrippedTextWidget
+from lp.app.enums import ServiceUsage
 from lp.bugs.interfaces.bug import IBug
 from lp.bugs.interfaces.bugtask import (
     BugTaskImportance,
@@ -319,10 +320,11 @@
             if bug_watch is None:
                 bug_watch = task_added.bug.addWatch(
                     extracted_bugtracker, extracted_bug, self.user)
-            if not target.official_malone:
+            if target.bug_tracking_usage != ServiceUsage.LAUNCHPAD:
                 task_added.bugwatch = bug_watch
 
-        if (not target.official_malone and task_added.bugwatch is not None
+        if (target.bug_tracking_usage != ServiceUsage.LAUNCHPAD
+            and task_added.bugwatch is not None
             and (task_added.bugwatch.bugtracker.bugtrackertype !=
                  BugTrackerType.EMAILADDRESS)):
             # A remote bug task gets its status from a bug watch, so
@@ -371,7 +373,7 @@
 
         if (not bug_url and
             not self.request.get('ignore_missing_remote_bug') and
-            not target.official_malone):
+            target.bug_tracking_usage != ServiceUsage.LAUNCHPAD):
             # We have no URL for the remote bug and the target does not use
             # Launchpad for bug tracking, so we warn the user this is not
             # optimal and ask for his confirmation.
@@ -404,7 +406,7 @@
         """
         target = self.getTarget(data)
         bug_url = data.get('bug_url')
-        if bug_url and target.official_malone:
+        if bug_url and target.bug_tracking_usage == ServiceUsage.LAUNCHPAD:
             self.addError(
                 "Bug watches can not be added for %s, as it uses Launchpad"
                 " as its official bug tracker. Alternatives are to add a"

=== modified file 'lib/lp/bugs/browser/bugtarget.py'
--- lib/lp/bugs/browser/bugtarget.py	2010-08-22 18:31:30 +0000
+++ lib/lp/bugs/browser/bugtarget.py	2010-08-31 21:27:50 +0000
@@ -92,11 +92,15 @@
     GhostWidget,
     ProductBugTrackerWidget,
     )
+from lp.app.enums import ServiceUsage
 from lp.app.errors import (
     NotFoundError,
     UnexpectedFormData,
     )
-from lp.app.interfaces.launchpad import ILaunchpadUsage
+from lp.app.interfaces.launchpad import (
+    ILaunchpadUsage,
+    IServiceUsage,
+    )
 from lp.bugs.browser.bugrole import BugRoleMixin
 from lp.bugs.browser.bugtask import BugTaskSearchListingView
 from lp.bugs.interfaces.apportjob import IProcessApportBlobJobSource
@@ -384,7 +388,7 @@
         # actually uses Malone for its bug tracking.
         product_or_distro = self.getProductOrDistroFromContext()
         if (product_or_distro is not None and
-            not product_or_distro.official_malone):
+            product_or_distro.bug_tracking_usage != ServiceUsage.LAUNCHPAD):
             self.setFieldError(
                 'bugtarget',
                 "%s does not use Launchpad as its bug tracker " %
@@ -426,10 +430,11 @@
         if IProjectGroup.providedBy(self.context):
             products_using_malone = [
                 product for product in self.context.products
-                if product.official_malone]
+                if product.bug_tracking_usage == ServiceUsage.LAUNCHPAD]
             return len(products_using_malone) > 0
         else:
-            return self.getMainContext().official_malone
+            bug_tracking_usage = self.getMainContext().bug_tracking_usage
+            return bug_tracking_usage == ServiceUsage.LAUNCHPAD
 
     def getMainContext(self):
         if IDistributionSourcePackage.providedBy(self.context):
@@ -1084,7 +1089,7 @@
     def products_using_malone(self):
         return [
             product for product in self.context.products
-            if product.official_malone]
+            if product.bug_tracking_usage == ServiceUsage.LAUNCHPAD]
 
     @property
     def default_product(self):
@@ -1247,13 +1252,13 @@
             bug_statuses_to_show.append(BugTaskStatus.FIXRELEASED)
 
     @property
-    def uses_launchpad_bugtracker(self):
-        """Whether this distro or product tracks bugs in launchpad.
+    def bug_tracking_usage(self):
+        """Whether the context tracks bugs in launchpad.
 
-        :returns: boolean
+        :returns: ServiceUsage enum value
         """
-        launchpad_usage = ILaunchpadUsage(self.context)
-        return launchpad_usage.official_malone
+        service_usage = IServiceUsage(self.context)
+        return service_usage.bug_tracking_usage
 
     @property
     def external_bugtracker(self):
@@ -1273,7 +1278,7 @@
 
         :returns: str which may contain HTML.
         """
-        if self.uses_launchpad_bugtracker:
+        if self.bug_tracking_usage == ServiceUsage.LAUNCHPAD:
             return 'Launchpad'
         elif self.external_bugtracker:
             return BugTrackerFormatterAPI(self.external_bugtracker).link(None)

=== modified file 'lib/lp/bugs/browser/bugtask.py'
--- lib/lp/bugs/browser/bugtask.py	2010-08-27 05:34:32 +0000
+++ lib/lp/bugs/browser/bugtask.py	2010-08-31 21:27:50 +0000
@@ -119,15 +119,11 @@
     SimpleVocabulary,
     )
 from zope.security.interfaces import Unauthorized
-from zope.security.proxy import (
-    isinstance as zope_isinstance,
-    removeSecurityProxy,
-    )
+from zope.security.proxy import isinstance as zope_isinstance
 from zope.traversing.interfaces import IPathAdapter
 
 from canonical.cachedproperty import cachedproperty
 from canonical.config import config
-from canonical.database.sqlbase import cursor
 from canonical.launchpad import (
     _,
     helpers,
@@ -193,6 +189,7 @@
     )
 from canonical.widgets.project import ProjectScopeWidget
 from lp.answers.interfaces.questiontarget import IQuestionTarget
+from lp.app.enums import ServiceUsage
 from lp.app.errors import (
     NotFoundError,
     UnexpectedFormData,
@@ -275,6 +272,7 @@
                 PersonFormatterAPI(context.assignee).link(None))
     return render
 
+
 @component.adapter(IBugTask, IReference, IWebServiceClientRequest)
 @implementer(IFieldHTMLRenderer)
 def bugtarget_renderer(context, field, request):
@@ -289,6 +287,7 @@
         return html
     return render
 
+
 def unique_title(title):
     """Canonicalise a message title to help identify messages with new
     information in their titles.
@@ -1082,7 +1081,7 @@
             activity_and_comments.append({
                 'activity': activity_dict,
                 'date': date,
-                'person': activity_dict[0]['activity'][0].person
+                'person': activity_dict[0]['activity'][0].person,
                 })
 
         activity_and_comments.sort(key=itemgetter('date'))
@@ -1668,7 +1667,7 @@
 
         new_product = data.get('product')
         if (old_product is None or old_product == new_product or
-            not bugtask.pillar.official_malone):
+            bugtask.pillar.bug_tracking_usage != ServiceUsage.LAUNCHPAD):
             # Either the product wasn't changed, we're dealing with a #
             # distro task, or the bugtask's product doesn't use Launchpad,
             # which means the product can't be changed.
@@ -1823,9 +1822,11 @@
                 #     Launchpad status, but it's not trivial to do at the
                 #     moment. I will fix this later.
                 bugtask.transitionToStatus(
-                    BugTaskStatus.UNKNOWN, bug_importer)
+                    BugTaskStatus.UNKNOWN,
+                    bug_importer)
                 bugtask.transitionToImportance(
-                    BugTaskImportance.UNKNOWN,  bug_importer)
+                    BugTaskImportance.UNKNOWN,
+                    bug_importer)
                 bugtask.transitionToAssignee(None)
 
         if changed:
@@ -1994,7 +1995,7 @@
         """
         if not IProduct.providedBy(self.context):
             return None
-        if self.context.official_malone:
+        if self.context.bug_tracking_usage == ServiceUsage.LAUNCHPAD:
             return None
         return "%s?field.status_upstream=pending_bugwatch" % (
             canonical_url(self.context, view_name='+bugs'))
@@ -2081,7 +2082,7 @@
         """
         if not IProduct.providedBy(self.context):
             return None
-        if self.context.official_malone:
+        if self.context.bug_tracking_usage == ServiceUsage.LAUNCHPAD:
             return None
         params = get_default_search_params(self.user)
         params.pending_bugwatch_elsewhere = True
@@ -2359,9 +2360,12 @@
         bugtask_listing_item.review_action_widget = CustomWidgetFactory(
             NominationReviewActionWidget)
         setUpWidget(
-            bugtask_listing_item, 'review_action',
-            review_action_field, IInputWidget,
-            value=NominatedBugReviewAction.NO_CHANGE, context=bug_nomination)
+            bugtask_listing_item,
+            'review_action',
+            review_action_field,
+            IInputWidget,
+            value=NominatedBugReviewAction.NO_CHANGE,
+            context=bug_nomination)
 
         return bugtask_listing_item
 
@@ -2913,7 +2917,7 @@
 
             if fieldname != "orderby":
                 sortlink += "%s&" % urllib.urlencode(
-                    {fieldname : fieldvalue}, doseq=True)
+                    {fieldname: fieldvalue}, doseq=True)
 
         sorted, ascending = self._getSortStatus(colname)
         if sorted and ascending:
@@ -2997,7 +3001,7 @@
         """Is the context a Product that does not use Malone?"""
         return (
             IProduct.providedBy(self.context)
-            and not self.context.official_malone)
+            and self.context.bug_tracking_usage != ServiceUsage.LAUNCHPAD)
 
     def _upstreamContext(self):
         """Is this page being viewed in an upstream context?
@@ -3065,7 +3069,6 @@
             return None
 
 
-
 class BugNominationsView(BugTaskSearchListingView):
     """View for accepting/declining bug nominations."""
 
@@ -3129,8 +3132,7 @@
                 declined += 1
             else:
                 raise AssertionError(
-                    'Unknown NominatedBugReviewAction: %r' % (
-                        review_action,))
+                    'Unknown NominatedBugReviewAction: %r' % review_action)
 
         if accepted > 0:
             self.request.response.addInfoNotification(
@@ -3197,7 +3199,7 @@
         else:
             raise AssertionError('Uknown context type: %s' % self.context)
 
-        return u"".join("%d\n" % bug_id for bug_id in 
+        return u"".join("%d\n" % bug_id for bug_id in
             getUtility(IBugTaskSet).searchBugIds(search_params))
 
 
@@ -3370,8 +3372,7 @@
                 self._getTableRowView(
                     nomination, is_converted_to_question, False)
                 for nomination in target_nominations
-                if nomination.status != BugNominationStatus.APPROVED
-                )
+                if nomination.status != BugNominationStatus.APPROVED)
 
         # Fill the ValidPersonOrTeamCache cache (using getValidPersons()),
         # so that checking person.is_valid_person, when rendering the
@@ -3849,6 +3850,7 @@
 
 class BugTaskExpirableListingView(LaunchpadView):
     """View for listing Incomplete bugs that can expire."""
+
     @property
     def can_show_expirable_bugs(self):
         """Return True or False if expirable bug listing can be shown."""

=== modified file 'lib/lp/bugs/browser/distribution_upstream_bug_report.py'
--- lib/lp/bugs/browser/distribution_upstream_bug_report.py	2010-08-20 20:31:18 +0000
+++ lib/lp/bugs/browser/distribution_upstream_bug_report.py	2010-08-31 21:27:50 +0000
@@ -6,8 +6,8 @@
 __metaclass__ = type
 
 __all__ = [
-    'DistributionUpstreamBugReport'
-]
+    'DistributionUpstreamBugReport',
+    ]
 
 from operator import attrgetter
 
@@ -17,6 +17,7 @@
     LaunchpadView,
     )
 from canonical.launchpad.webapp.url import urlappend
+from lp.app.enums import ServiceUsage
 from lp.bugs.browser.bugtask import get_buglisting_search_filter_url
 
 # TODO: fix column sorting to work for the different colspans, or
@@ -145,9 +146,11 @@
         - dssp: an IDistributionSeriesSourcepackage
         - product: an IProduct
         - bugtracker: convenience holder for the product's bugtracker
-        - official_malone: convenience boolean for IProduct.official_malone
+        - bug_tracking_usage: convenience enum for
+            IProduct.bug_tracking_usage
         - *_url: convenience URLs
     """
+
     def __init__(self, dsp, dssp, product, open_bugs, triaged_bugs,
                  upstream_bugs, watched_bugs, bugs_with_upstream_patches):
         BugReportData.__init__(self, open_bugs, triaged_bugs, upstream_bugs,
@@ -162,9 +165,12 @@
         self.open_bugs_url = urlappend(
             dsp_bugs_url, get_buglisting_search_filter_url())
 
-        self.official_malone = bool(product and product.official_malone)
-        self.branch = (
-            product and product.development_focus.branch)
+        if product is not None:
+            self.bug_tracking_usage = product.bug_tracking_usage
+            self.branch = product.development_focus.branch
+        else:
+            self.bug_tracking_usage = ServiceUsage.UNKNOWN
+            self.branch = None
 
         # If a product is specified, build some convenient links to
         # pages which allow filling out required information. The
@@ -180,7 +186,7 @@
             # Create a 'bugtracker_name' attribute for searching.
             if self.bugtracker is not None:
                 self.bugtracker_name = self.bugtracker.title
-            elif self.product.official_malone:
+            elif self.product.bug_tracking_usage == ServiceUsage.LAUNCHPAD:
                 self.bugtracker_name = 'Launchpad'
             else:
                 self.bugtracker_name = None
@@ -374,4 +380,3 @@
                 dsp, dssp, product, open, triaged, upstream, watched,
                 bugs_with_upstream_patches)
             self._data.append(item)
-

=== modified file 'lib/lp/bugs/browser/tests/bugtask-adding-views.txt'
--- lib/lp/bugs/browser/tests/bugtask-adding-views.txt	2010-07-26 12:49:23 +0000
+++ lib/lp/bugs/browser/tests/bugtask-adding-views.txt	2010-08-31 21:27:50 +0000
@@ -224,8 +224,8 @@
 offical bug tracker.
 
     >>> evolution_task = bug_four.bugtasks[0]
-    >>> evolution_task.target.official_malone
-    True
+    >>> evolution_task.target.bug_tracking_usage
+    <DBItem ServiceUsage.LAUNCHPAD, (20) Launchpad>
 
     >>> transaction.commit()
 
@@ -275,8 +275,8 @@
 are set to the default values.
 
     >>> alsa_task = bug_four.bugtasks[0]
-    >>> alsa_task.target.official_malone
-    False
+    >>> alsa_task.target.bug_tracking_usage
+    <DBItem ServiceUsage.UNKNOWN, (10) Unknown>
     >>> alsa_task.status.title
     'New'
     >>> alsa_task.importance.title
@@ -368,8 +368,8 @@
     >>> alsa_task = bug_four.bugtasks[0]
     >>> alsa_task.bugtargetname
     u'alsa-utils'
-    >>> alsa_task.product.official_malone
-    False
+    >>> alsa_task.product.bug_tracking_usage
+    <DBItem ServiceUsage.UNKNOWN, (10) Unknown>
     >>> alsa_task.bugwatch == bug_four.watches[0]
     True
 

=== modified file 'lib/lp/bugs/browser/tests/test_bugtarget_configure.py'
--- lib/lp/bugs/browser/tests/test_bugtarget_configure.py	2010-08-20 20:31:18 +0000
+++ lib/lp/bugs/browser/tests/test_bugtarget_configure.py	2010-08-31 21:27:50 +0000
@@ -6,6 +6,7 @@
 __metaclass__ = type
 
 from canonical.testing import DatabaseFunctionalLayer
+from lp.app.enums import ServiceUsage
 from lp.testing import (
     login_person,
     TestCaseWithFactory,
@@ -59,7 +60,9 @@
         self.assertEqual([], view.errors)
         self.assertEqual(self.owner, self.product.bug_supervisor)
         self.assertEqual(self.owner, self.product.security_contact)
-        self.assertTrue(self.product.official_malone)
+        self.assertEqual(
+            ServiceUsage.LAUNCHPAD,
+            self.product.bug_tracking_usage)
         self.assertTrue(self.product.enable_bug_expiration)
         self.assertEqual('sf-boing', self.product.remote_product)
         self.assertEqual('guidelines', self.product.bug_reporting_guidelines)

=== modified file 'lib/lp/bugs/model/bug.py'
--- lib/lp/bugs/model/bug.py	2010-08-29 22:05:15 +0000
+++ lib/lp/bugs/model/bug.py	2010-08-31 21:27:50 +0000
@@ -108,6 +108,7 @@
     MAIN_STORE,
     )
 from lp.answers.interfaces.questiontarget import IQuestionTarget
+from lp.app.enums import ServiceUsage
 from lp.app.errors import (
     NotFoundError,
     UserCannotUnsubscribePerson,
@@ -1170,7 +1171,7 @@
         if len(non_invalid_bugtasks) != 1:
             return None
         [valid_bugtask] = non_invalid_bugtasks
-        if valid_bugtask.pillar.official_malone:
+        if valid_bugtask.pillar.bug_tracking_usage == ServiceUsage.LAUNCHPAD:
             return valid_bugtask
         else:
             return None

=== modified file 'lib/lp/bugs/model/bugtask.py'
--- lib/lp/bugs/model/bugtask.py	2010-08-30 19:30:45 +0000
+++ lib/lp/bugs/model/bugtask.py	2010-08-31 21:27:50 +0000
@@ -17,7 +17,8 @@
     'bugtask_sort_key',
     'get_bug_privacy_filter',
     'get_related_bugtasks_search_params',
-    'search_value_to_where_condition']
+    'search_value_to_where_condition',
+    ]
 
 
 import datetime
@@ -92,6 +93,7 @@
     IStoreSelector,
     MAIN_STORE,
     )
+from lp.app.enums import ServiceUsage
 from lp.app.errors import NotFoundError
 from lp.bugs.interfaces.bug import IBugSet
 from lp.bugs.interfaces.bugattachment import BugAttachmentType
@@ -157,16 +159,30 @@
 from lp.soyuz.model.publishing import SourcePackagePublishingHistory
 from lp.soyuz.model.sourcepackagerelease import SourcePackageRelease
 
-debbugsseveritymap = {
-    None: BugTaskImportance.UNDECIDED,
-    'wishlist': BugTaskImportance.WISHLIST,
-    'minor': BugTaskImportance.LOW,
-    'normal': BugTaskImportance.MEDIUM,
-    'important': BugTaskImportance.HIGH,
-    'serious': BugTaskImportance.HIGH,
-    'grave': BugTaskImportance.HIGH,
-    'critical': BugTaskImportance.CRITICAL,
-    }
+<<<<<<< TREE
+debbugsseveritymap = {
+    None: BugTaskImportance.UNDECIDED,
+    'wishlist': BugTaskImportance.WISHLIST,
+    'minor': BugTaskImportance.LOW,
+    'normal': BugTaskImportance.MEDIUM,
+    'important': BugTaskImportance.HIGH,
+    'serious': BugTaskImportance.HIGH,
+    'grave': BugTaskImportance.HIGH,
+    'critical': BugTaskImportance.CRITICAL,
+    }
+=======
+
+debbugsseveritymap = {
+    None: BugTaskImportance.UNDECIDED,
+    'wishlist': BugTaskImportance.WISHLIST,
+    'minor': BugTaskImportance.LOW,
+    'normal': BugTaskImportance.MEDIUM,
+    'important': BugTaskImportance.HIGH,
+    'serious': BugTaskImportance.HIGH,
+    'grave': BugTaskImportance.HIGH,
+    'critical': BugTaskImportance.CRITICAL,
+    }
+>>>>>>> MERGE-SOURCE
 
 
 def bugtask_sort_key(bugtask):
@@ -460,13 +476,19 @@
         sourcepackagename=self.sourcepackagename,
         distribution=self.distribution,
         distroseries=self.distroseries)
-    utility_iface = {
+    utility_iface_dict = {
         'productID': IProductSet,
         'productseriesID': IProductSeriesSet,
         'sourcepackagenameID': ISourcePackageNameSet,
         'distributionID': IDistributionSet,
+<<<<<<< TREE
         'distroseriesID': IDistroSeriesSet,
         }[attr]
+=======
+        'distroseriesID': IDistroSeriesSet,
+        }
+    utility_iface = utility_iface_dict[attr]
+>>>>>>> MERGE-SOURCE
     if value is None:
         target_params[attr[:-2]] = None
     else:
@@ -655,7 +677,7 @@
     # one thing they often have to filter for is completeness. We maintain
     # this single canonical query string here so that it does not have to be
     # cargo culted into Product, Distribution, ProductSeries etc
-    completeness_clause =  """
+    completeness_clause = """
         BugTask.status IN ( %s )
         """ % ','.join([str(a.value) for a in RESOLVED_BUGTASK_STATUSES])
 
@@ -845,7 +867,7 @@
         """See `IBugTask`"""
         # XXX sinzui 2007-10-04 bug=149009:
         # This property is not needed. Code should inline this implementation.
-        return self.pillar.official_malone
+        return (self.pillar.bug_tracking_usage == ServiceUsage.LAUNCHPAD)
 
     def transitionToMilestone(self, new_milestone, user):
         """See `IBugTask`."""
@@ -1178,7 +1200,7 @@
         if IUpstreamBugTask.providedBy(self):
             header_value = 'product=%s;' % self.target.name
         elif IProductSeriesBugTask.providedBy(self):
-            header_value = 'product=%s; productseries=%s;' %  (
+            header_value = 'product=%s; productseries=%s;' % (
                 self.productseries.product.name, self.productseries.name)
         elif IDistroBugTask.providedBy(self):
             header_value = ((
@@ -2219,7 +2241,7 @@
                     (BugTask, Product, SourcePackageName, Bug),
                     AutoTables(SQL("1=1"), tables),
                     query)
-                decorator=lambda row:bugtask_decorator(row[0])
+                decorator=lambda row: bugtask_decorator(row[0])
             resultset.order_by(orderby)
             return DecoratedResultSet(resultset, result_decorator=decorator)
 

=== modified file 'lib/lp/bugs/stories/bugs/xx-front-page-info.txt'
--- lib/lp/bugs/stories/bugs/xx-front-page-info.txt	2010-05-08 15:29:56 +0000
+++ lib/lp/bugs/stories/bugs/xx-front-page-info.txt	2010-08-31 21:27:50 +0000
@@ -19,7 +19,8 @@
     >>> anon_browser.open('http://bugs.launchpad.dev/test-project')
     >>> uses_malone_p = find_tag_by_id(anon_browser.contents, 'no-malone')
     >>> print extract_text(uses_malone_p)
-    Simple Test Project does not use Launchpad for bug tracking.
+    Launchpad does not know where to forward bug reports to contact the
+    developers of Simple Test Project.
 
 Only users who have permission to do so can enable bug tracking
 for a project.
@@ -112,8 +113,7 @@
     Advanced search
 
 Projects that use an external bug tracker will list the tracker on a
-bugs home page in addition to the message that the project does not
-use Launchpad for bug tracking.
+bugs home page.
 
     >>> login('foo.bar@xxxxxxxxxxxxx')
     >>> some_tracker = factory.makeBugTracker(
@@ -121,9 +121,6 @@
     >>> test_project.bugtracker = some_tracker
     >>> logout()
     >>> anon_browser.open('http://bugs.launchpad.dev/test-project')
-    >>> uses_malone_p = find_tag_by_id(anon_browser.contents, 'no-malone')
-    >>> print extract_text(uses_malone_p)
-    Simple Test Project does not use Launchpad for bug tracking.
     >>> tracker_text = find_tag_by_id(anon_browser.contents, 'bugtracker')
     >>> print extract_text(tracker_text)
     Bugs are tracked in tracker.example.com/.

=== modified file 'lib/lp/bugs/templates/bug-create-question.pt'
--- lib/lp/bugs/templates/bug-create-question.pt	2009-08-31 09:58:28 +0000
+++ lib/lp/bugs/templates/bug-create-question.pt	2010-08-31 21:27:50 +0000
@@ -17,7 +17,7 @@
                 A question was already created from this bug.
               </tal:question>
               <tal:uses-malone
-                condition="not: context/pillar/official_malone">
+                condition="not: context/pillar/bug_tracking_usage/enumvalue:LAUNCHPAD">
                 <tal:target
                   replace="context/target/displayname">Firefox</tal:target>
                 does not use Launchpad to track bugs.

=== modified file 'lib/lp/bugs/templates/bugtarget-bugs.pt'
--- lib/lp/bugs/templates/bugtarget-bugs.pt	2010-08-04 11:01:15 +0000
+++ lib/lp/bugs/templates/bugtarget-bugs.pt	2010-08-31 21:27:50 +0000
@@ -10,12 +10,15 @@
   i18n:domain="malone"
 >
   <metal:block fill-slot="head_epilogue">
+    <meta tal:condition="not: view/bug_tracking_usage/enumvalue:LAUNCHPAD"
+          name="robots" content="noindex,nofollow" />
     <style type="text/css">
       p#more-hot-bugs {float:right; margin-top:7px;}
     </style>
   </metal:block>
   <body>
-    <tal:side metal:fill-slot="side" condition="view/uses_launchpad_bugtracker">
+    <tal:side metal:fill-slot="side"
+              condition="view/bug_tracking_usage/enumvalue:LAUNCHPAD">
       <div id="involvement" class="portlet">
         <ul class="involvement">
           <li style="border: none">
@@ -95,7 +98,8 @@
 
 
 
-      <tal:uses_malone condition="view/uses_launchpad_bugtracker">
+      <tal:uses_launchpad_bugtracker
+        condition="view/bug_tracking_usage/enumvalue:LAUNCHPAD">
       <tal:has_bugtasks condition="context/has_bugtasks">
         <div class="search-box" style="margin-bottom:2em">
           <metal:search
@@ -162,27 +166,55 @@
 
         <p id="no-bugs-report"><a href="+filebug">Report a bug.</a></p>
       </tal:no_hot_bugs>
-    </tal:uses_malone>
-
-    <tal:not_uses_malone condition="not: view/uses_launchpad_bugtracker"
-     tal:define ="configure_bugtracker context/menu:overview/configure_bugtracker | nothing">
-      <p id="no-malone"><strong><tal:project_title replace="context/title" /> does not use Launchpad for
-        bug tracking.</strong></p>
-          <p tal:condition="view/external_bugtracker"
-             id="bugtracker"><strong>Bugs are tracked in
-            <tal:bugtracker replace="structure view/bugtracker" />.</strong>
-          </p>
-
-        <p tal:condition="context/required:launchpad.Edit"
-           id="no-malone-edit"
-           >
-          <a tal:condition="configure_bugtracker"
-             tal:replace="structure configure_bugtracker/fmt:link"/>
-          <a tal:condition="not: configure_bugtracker"
-             tal:attributes="href string:${context/fmt:url/+edit}">
-            Enable bug tracking.</a>
-        </p>
-    </tal:not_uses_malone>
+    </tal:uses_launchpad_bugtracker>
+
+    <p id="no-malone"
+       tal:condition="view/bug_tracking_usage/enumvalue:UNKNOWN">
+      <strong>
+        Launchpad does not know where to forward bug reports to contact the
+        developers of <tal:project_title replace="context/title" />.
+      </strong>
+    </p>
+
+    <p tal:condition="view/external_bugtracker"
+       id="bugtracker">
+      <strong>Bugs are tracked in
+        <tal:bugtracker replace="structure view/bugtracker" />.
+      </strong>
+    </p>
+
+    <tal:also_in_ubuntu
+      condition="not: view/bug_tracking_usage/enumvalue:LAUNCHPAD">
+      <p tal:define="packages context/ubuntu_packages | nothing"
+         tal:condition="packages">
+        <tal:displayname replace="context/displayname"/>
+        bug reports are
+        <tal:also condition="view/external_bugtracker">also</tal:also>
+        tracked in:
+        <tal:packages repeat="package packages">
+          <span style="white-space: nowrap"
+                tal:content="structure package/fmt:link" /><tal:comma
+          condition="not:repeat/package/end">,</tal:comma></tal:packages>.
+      </p>
+    </tal:also_in_ubuntu>
+
+    <div
+      tal:condition="not: view/bug_tracking_usage/enumvalue:LAUNCHPAD"
+      tal:define="configure_bugtracker context/menu:overview/configure_bugtracker | nothing">
+      <a class="sprite maybe"
+        href="https://help.launchpad.net/Bugs";>Getting started
+      with bug tracking in Launchpad</a>.
+
+      <p tal:condition="context/required:launchpad.Edit"
+          id="no-malone-edit"
+          >
+        <a tal:condition="configure_bugtracker"
+          tal:replace="structure configure_bugtracker/fmt:link"/>
+        <a tal:condition="not: configure_bugtracker"
+          tal:attributes="href string:${context/fmt:url/+edit}">
+          Enable bug tracking.</a>
+      </p>
+    </div>
 
     </div><!-- main -->
   </body>

=== modified file 'lib/lp/bugs/templates/bugtarget-macros-filebug.pt'
--- lib/lp/bugs/templates/bugtarget-macros-filebug.pt	2010-07-01 06:14:05 +0000
+++ lib/lp/bugs/templates/bugtarget-macros-filebug.pt	2010-08-31 21:27:50 +0000
@@ -298,7 +298,7 @@
           </p>
         </tal:singular>
         <ul class="product-bug-options" tal:repeat="product context/products">
-          <li condition="product/official_malone">
+          <li condition="product/bug_tracking_usage/enumvalue:LAUNCHPAD">
             <tal:link replace="structure product/fmt:link" />
             <ul class="bulleted">
               <tal:external-tracker

=== modified file 'lib/lp/bugs/templates/bugtask-requestfix-upstream.pt'
--- lib/lp/bugs/templates/bugtask-requestfix-upstream.pt	2010-07-23 16:00:36 +0000
+++ lib/lp/bugs/templates/bugtask-requestfix-upstream.pt	2010-08-31 21:27:50 +0000
@@ -50,7 +50,7 @@
         </div>
       </div>
 
-      <div id="upstream-text" tal:condition="not: product/official_malone"
+      <div id="upstream-text" tal:condition="not: product/bug_tracking_usage/enumvalue:LAUNCHPAD"
           tal:define="widgets view/bugwatch_widgets;
                       bugtracker product/getExternalBugTracker">
         <p tal:condition="bugtracker">
@@ -156,14 +156,14 @@
       </p>
 
       <tal:no_bug_supervisor tal:condition="not:product/bug_supervisor">
-        <p tal:condition="product/official_malone">
+        <p tal:condition="product/bug_tracking_usage/enumvalue:LAUNCHPAD">
           <a tal:replace="structure product/owner/fmt:link">Sample Person</a>, the
              <tal:product tal:replace="product/displayname">Firefox</tal:product>
              registrant, will be notified about this bug.
 
         </p>
 
-        <p tal:condition="not: product/official_malone">
+        <p tal:condition="not: product/bug_tracking_usage/enumvalue:LAUNCHPAD">
           There is no bug supervisor for
           <tal:product tal:replace="product/displayname">Firefox</tal:product>.
           This means that there is nobody upstream we can notify about

=== modified file 'lib/lp/bugs/templates/distribution-upstream-bug-report.pt'
--- lib/lp/bugs/templates/distribution-upstream-bug-report.pt	2010-02-16 17:59:59 +0000
+++ lib/lp/bugs/templates/distribution-upstream-bug-report.pt	2010-08-31 21:27:50 +0000
@@ -166,10 +166,12 @@
                 </td>
               </tal:has-bugtracker>
               <tal:has-no-bugtracker condition="not: item/bugtracker">
-                <td tal:condition="item/official_malone" align="center">
+                <td tal:condition="item/bug_tracking_usage/enumvalue:LAUNCHPAD"
+                  align="center">
                     <img src="/@@/yes" title="Launchpad" />
                 </td>
-                <td tal:condition="not: item/official_malone" align="center">
+                <td tal:condition="not: item/bug_tracking_usage/enumvalue:LAUNCHPAD"
+                  align="center">
                     <img src="/@@/no" title="Unknown" />
                     <a tal:condition="item/product/required:launchpad.Edit"
                        tal:attributes="href item/product_edit_url">
@@ -225,11 +227,11 @@
                  tal:content="item/upstream_bugs_delta"></a>
             </td>
             <tal:upstream-in-launchpad
-                condition="item/official_malone">
+                condition="item/bug_tracking_usage/enumvalue:LAUNCHPAD">
                 <td colspan="4" class="good">&nbsp;</td>
             </tal:upstream-in-launchpad>
             <tal:upstream-not-in-launchpad
-                condition="not: item/official_malone">
+                condition="not: item/bug_tracking_usage/enumvalue:LAUNCHPAD">
               <td tal:attributes="class string:amount ${item/watched_bugs_class}"
                   tal:content="item/watched_bugs" />
               <td tal:attributes="class string:amount ${item/watched_bugs_class}"

=== modified file 'lib/lp/bugs/tests/bugtarget-questiontarget.txt'
--- lib/lp/bugs/tests/bugtarget-questiontarget.txt	2009-06-12 16:36:02 +0000
+++ lib/lp/bugs/tests/bugtarget-questiontarget.txt	2010-08-31 21:27:50 +0000
@@ -43,8 +43,8 @@
 prerequisite for a bug to become a question is that the bugtarget's
 pillar must use Launchpad to track bugs.
 
-    >>> bug.affected_pillars[0].official_malone
-    True
+    >>> bug.affected_pillars[0].bug_tracking_usage
+    <DBItem ServiceUsage.LAUNCHPAD, (20) Launchpad>   
     >>> bug.canBeAQuestion()
     True
 
@@ -56,8 +56,8 @@
     >>> firefox = firefox_bug.bugtasks[0].target
     >>> IQuestionTarget.providedBy(firefox)
     True
-    >>> firefox.distribution.official_malone
-    False
+    >>> firefox.distribution.bug_tracking_usage
+    <DBItem ServiceUsage.UNKNOWN, (10) Unknown>
     >>> firefox_bug.canBeAQuestion()
     False
 

=== modified file 'lib/lp/bugs/tests/test_bugtask_1.py'
--- lib/lp/bugs/tests/test_bugtask_1.py	2010-08-20 20:31:18 +0000
+++ lib/lp/bugs/tests/test_bugtask_1.py	2010-08-31 21:27:50 +0000
@@ -17,6 +17,7 @@
     )
 from canonical.launchpad.webapp.interfaces import ILaunchBag
 from canonical.testing import DatabaseFunctionalLayer
+from lp.app.enums import ServiceUsage
 from lp.bugs.interfaces.bug import IBugSet
 from lp.bugs.interfaces.bugtask import (
     BugTaskStatus,
@@ -73,7 +74,9 @@
         # Mark an upstream task on bug #1 "Fix Released"
         bug_one = bugset.get(1)
         firefox_upstream = self._getBugTaskByTarget(bug_one, firefox)
-        self.assert_(firefox_upstream.product.official_malone)
+        self.assertEqual(
+            ServiceUsage.LAUNCHPAD,
+            firefox_upstream.product.bug_tracking_usage)
         self.old_firefox_status = firefox_upstream.status
         firefox_upstream.transitionToStatus(
             BugTaskStatus.FIXRELEASED, getUtility(ILaunchBag).user)
@@ -127,12 +130,10 @@
         """
         non_malone_using_bugtasks = [
             related_task for related_task in bugtask.related_tasks
-            if not related_task.target_uses_malone
-            ]
+            if not related_task.target_uses_malone]
         pending_bugwatch_bugtasks = [
             related_bugtask for related_bugtask in non_malone_using_bugtasks
-            if related_bugtask.bugwatch is None
-            ]
+            if related_bugtask.bugwatch is None]
         self.assert_(
             len(pending_bugwatch_bugtasks) > 0,
             'Bugtask %s on %s has no related bug watches elsewhere.' % (
@@ -166,8 +167,7 @@
         resolved_related_tasks = [
             related_task for related_task in bugtask.related_tasks
             if (_is_resolved_upstream_task(related_task) or
-                _is_resolved_bugwatch_task(related_task))
-            ]
+                _is_resolved_bugwatch_task(related_task))]
 
         self.assert_(len(resolved_related_tasks) > 0)
         self.assert_(
@@ -203,8 +203,7 @@
         open_related_tasks = [
             related_task for related_task in bugtask.related_tasks
             if (_is_open_upstream_task(related_task) or
-                _is_open_bugwatch_task(related_task))
-            ]
+                _is_open_bugwatch_task(related_task))]
 
         self.assert_(
             len(open_related_tasks) > 0,

=== modified file 'lib/lp/registry/adapters.py'
--- lib/lp/registry/adapters.py	2010-08-20 20:31:18 +0000
+++ lib/lp/registry/adapters.py	2010-08-31 21:27:50 +0000
@@ -7,16 +7,25 @@
 
 __all__ = [
     'distroseries_to_launchpadusage',
+    'distroseries_to_serviceusage',
     'PollSubset',
     'productseries_to_product',
     ]
 
 
-from zope.component import getUtility
+from zope.component import (
+    adapter,
+    getUtility,
+    )
 from zope.component.interfaces import ComponentLookupError
-from zope.interface import implements
+from zope.interface import (
+    implementer,
+    implements,
+    )
 
 from canonical.launchpad.webapp.interfaces import ILaunchpadPrincipal
+from lp.app.interfaces.launchpad import IServiceUsage
+from lp.registry.interfaces.distroseries import IDistroSeries
 from lp.registry.interfaces.poll import (
     IPollSet,
     IPollSubset,
@@ -25,6 +34,13 @@
     )
 
 
+@implementer(IServiceUsage)
+@adapter(IDistroSeries)
+def distroseries_to_serviceusage(distroseries):
+    """Adapts `IDistroSeries` object to `IServiceUsage`."""
+    return distroseries.distribution
+
+
 def distroseries_to_launchpadusage(distroseries):
     """Adapts `IDistroSeries` object to `ILaunchpadUsage`."""
     return distroseries.distribution

=== modified file 'lib/lp/registry/browser/sourcepackage.py'
--- lib/lp/registry/browser/sourcepackage.py	2010-08-23 22:29:52 +0000
+++ lib/lp/registry/browser/sourcepackage.py	2010-08-31 21:27:50 +0000
@@ -73,6 +73,7 @@
 from canonical.launchpad.webapp.publisher import LaunchpadView
 from canonical.lazr.utils import smartquote
 from canonical.widgets import LaunchpadRadioWidget
+from lp.app.enums import ServiceUsage
 from lp.answers.browser.questiontarget import (
     QuestionTargetAnswersMenu,
     QuestionTargetFacetMixin,
@@ -283,8 +284,7 @@
         self.product = getUtility(IProductSet)[product_name]
         series_list = [
             series for series in self.product.series
-            if series.status != SeriesStatus.OBSOLETE
-            ]
+            if series.status != SeriesStatus.OBSOLETE]
 
         # If the product is not being changed, then the current
         # productseries can be the default choice. Otherwise,
@@ -306,8 +306,7 @@
             series_list.remove(dev_focus)
         vocab_terms = [
             SimpleTerm(series, series.name, series.name)
-            for series in series_list
-            ]
+            for series in series_list]
         dev_focus_term = SimpleTerm(
             dev_focus, dev_focus.name, "%s (Recommended)" % dev_focus.name)
         vocab_terms.insert(0, dev_focus_term)
@@ -339,6 +338,7 @@
     next_url = None
 
     main_action_label = u'Change'
+    
     def main_action(self, data):
         productseries = data['productseries']
         # Because it is part of a multistep view, the next_url can't
@@ -575,7 +575,7 @@
         if self.context.productseries is None:
             return False
         product = self.context.productseries.product
-        if product.official_malone:
+        if product.bug_tracking_usage == ServiceUsage.LAUNCHPAD:
             return True
         bugtracker = product.bugtracker
         if bugtracker is None:

=== modified file 'lib/lp/registry/browser/tests/distribution-views.txt'
--- lib/lp/registry/browser/tests/distribution-views.txt	2010-06-16 08:22:00 +0000
+++ lib/lp/registry/browser/tests/distribution-views.txt	2010-08-31 21:27:50 +0000
@@ -106,8 +106,8 @@
 
 The view accepts most of the distribution fields.
 
-    >>> distribution.official_malone
-    False
+    >>> distribution.bug_tracking_usage
+    <DBItem ServiceUsage.UNKNOWN, (10) Unknown>
 
     >>> view.field_names
     ['displayname', 'title', 'summary', 'description',
@@ -131,7 +131,7 @@
     guidelines
 
     >>> distribution.official_malone
-    True
+    <DBItem ServiceUsage.LAUNCHPAD, (20) Launchpad>
 
 Only admins and owners can access the view.
 

=== modified file 'lib/lp/registry/browser/tests/sourcepackage-views.txt'
--- lib/lp/registry/browser/tests/sourcepackage-views.txt	2010-08-04 05:27:22 +0000
+++ lib/lp/registry/browser/tests/sourcepackage-views.txt	2010-08-31 21:27:50 +0000
@@ -293,8 +293,8 @@
     >>> view = create_initialized_view(
     ...     package, name='+upstream-connections')
 
-    >>> print product.official_malone
-    False
+    >>> print product.bug_tracking_usage.name
+    UNKNOWN
     >>> print product.bugtracker
     None
     >>> print view.has_bugtracker

=== modified file 'lib/lp/registry/configure.zcml'
--- lib/lp/registry/configure.zcml	2010-08-23 03:25:20 +0000
+++ lib/lp/registry/configure.zcml	2010-08-31 21:27:50 +0000
@@ -171,6 +171,8 @@
         factory="lp.registry.adapters.distroseries_to_launchpadusage"
         permission="zope.Public"/>
     <adapter
+        factory="lp.registry.adapters.distroseries_to_serviceusage" />
+    <adapter
         provides="canonical.launchpad.webapp.interfaces.IBreadcrumb"
         for="lp.registry.interfaces.distroseries.IDistroSeries"
         factory="lp.registry.browser.distroseries.DistroSeriesBreadcrumb"
@@ -1379,6 +1381,11 @@
         factory="lp.registry.adapters.productseries_to_product"
         permission="zope.Public"/>
     <adapter
+        provides="lp.app.interfaces.launchpad.IServiceUsage"
+        for="lp.registry.interfaces.productseries.IProductSeries"
+        factory="lp.registry.adapters.productseries_to_product"
+        permission="zope.Public"/>
+    <adapter
         provides="lp.app.interfaces.launchpad.ILaunchpadUsage"
         for="lp.registry.interfaces.productseries.IProductSeries"
         factory="lp.registry.adapters.productseries_to_product"

=== modified file 'lib/lp/registry/doc/product.txt'
--- lib/lp/registry/doc/product.txt	2010-08-22 19:46:19 +0000
+++ lib/lp/registry/doc/product.txt	2010-08-31 21:27:50 +0000
@@ -1,4 +1,5 @@
-= Product =
+Product
+=======
 
 Launchpad keeps track of the "upstream" world as well as the "distro" world.
 The anchorpiece of the "upstream" world is the Product, which is a piece of
@@ -162,7 +163,8 @@
     Obsolete Junk
 
 
-== Translatable Products ==
+Translatable Products
+---------------------
 
 IProductSet will also tell us which products can be translated:
 
@@ -217,11 +219,16 @@
 The packaging table allows us to list source and distro source packages
 related to a certain upstream:
 
-   >>> alsa = productset.getByName('alsa-utils')
-   >>> [(sp.name, sp.distroseries.name) for sp in alsa.sourcepackages]
-   [(u'alsa-utils', u'sid'), (u'alsa-utils', u'warty')]
-   >>> [(sp.name, sp.distribution.name) for sp in alsa.distrosourcepackages]
-   [(u'alsa-utils', u'debian'), (u'alsa-utils', u'ubuntu')]
+    >>> alsa = productset.getByName('alsa-utils')
+    >>> [(sp.name, sp.distroseries.name) for sp in alsa.sourcepackages]
+    [(u'alsa-utils', u'sid'), (u'alsa-utils', u'warty')]
+    >>> [(sp.name, sp.distribution.name) for sp in alsa.distrosourcepackages]
+    [(u'alsa-utils', u'debian'), (u'alsa-utils', u'ubuntu')]
+
+For convenience, you can get just the distro source packages for Ubuntu.
+
+    >>> [(sp.name, sp.distribution.name) for sp in alsa.ubuntu_packages]
+    [(u'alsa-utils', u'ubuntu')]
 
 The date_next_suggest_packaging attribute records the date when Launchpad can
 resume suggesting Ubuntu packages that the project provides. A value of None
@@ -258,8 +265,8 @@
 one.
 
     >>> firefox = getUtility(IProductSet).getByName('firefox')
-    >>> firefox.official_malone
-    True
+    >>> firefox.bug_tracking_usage
+    <DBItem ServiceUsage.LAUNCHPAD, (20) Launchpad>
     >>> print firefox.bug_tracking_usage.name
     LAUNCHPAD
     >>> firefox.getExternalBugTracker() is None
@@ -307,7 +314,8 @@
     True
 
 
-== Answer Tracking ==
+Answer Tracking
+---------------
 
 Firefox uses the Answer Tracker as the official application to provide
 answers to questions.
@@ -321,7 +329,8 @@
     False
 
 
-== Product Creation ==
+Product Creation
+----------------
 
 We can create new products with the createProduct() method:
 
@@ -374,20 +383,21 @@
     True
 
 
-== Specification Listings ==
+Specification Listings
+----------------------
 
 We should be able to set whether or not a Product uses specifications
 officially.  It defaults to False.
 
- >>> firefox = productset.getByName('firefox')
- >>> firefox.official_blueprints
- False
+    >>> firefox = productset.getByName('firefox')
+    >>> firefox.official_blueprints
+    False
 
 We can change it to True.
 
- >>> firefox.official_blueprints = True
- >>> firefox.official_blueprints
- True
+    >>> firefox.official_blueprints = True
+    >>> firefox.official_blueprints
+    True
 
 We should be able to get lists of specifications in different states
 related to a product.
@@ -395,39 +405,40 @@
 Basically, we can filter by completeness, and by whether or not the spec is
 informational.
 
- >>> firefox = productset.getByName('firefox')
- >>> from canonical.launchpad.interfaces import SpecificationFilter
+    >>> firefox = productset.getByName('firefox')
+    >>> from canonical.launchpad.interfaces import SpecificationFilter
 
 First, there should be only one informational spec for firefox:
 
- >>> filter = [SpecificationFilter.INFORMATIONAL]
- >>> for spec in firefox.specifications(filter=filter):
- ...    print spec.name
- extension-manager-upgrades
+    >>> filter = [SpecificationFilter.INFORMATIONAL]
+    >>> for spec in firefox.specifications(filter=filter):
+    ...    print spec.name
+    extension-manager-upgrades
 
 
 There are no completed specs for firefox:
 
- >>> filter = [SpecificationFilter.COMPLETE]
- >>> for spec in firefox.specifications(filter=filter):
- ...    print spec.name
+    >>> filter = [SpecificationFilter.COMPLETE]
+    >>> for spec in firefox.specifications(filter=filter):
+    ...    print spec.name
 
 
 And there are five incomplete specs:
 
- >>> filter = [SpecificationFilter.INCOMPLETE]
- >>> firefox.specifications(filter=filter).count()
- 5
+    >>> filter = [SpecificationFilter.INCOMPLETE]
+    >>> firefox.specifications(filter=filter).count()
+    5
 
 We can filter for specifications that contain specific text:
 
- >>> for spec in firefox.specifications(filter=['new']):
- ...     print spec.name
- canvas
- e4x
-
-
-== Milestones ==
+    >>> for spec in firefox.specifications(filter=['new']):
+    ...     print spec.name
+    canvas
+    e4x
+
+
+Milestones
+----------
 
 We can use IProduct.milestones to get all milestones associated with any
 ProductSeries of a product.
@@ -470,7 +481,8 @@
     [u'1.0.0', u'0.9.2', u'0.9.1', u'0.9', u'1.0', u'1.0-rc1']
 
 
-== Release ==
+Release
+-------
 
 All the releases for a Product can be retrieved through the releases property.
 
@@ -489,7 +501,8 @@
     0.9.1
 
 
-== Products With Branches ==
+Products With Branches
+----------------------
 
 Products are considered to officially support Launchpad as a location
 for their branches after a branch is set for the development focus
@@ -568,7 +581,8 @@
     landscape
 
 
-== Primary translatable ==
+Primary translatable
+--------------------
 
 Primary translatable series in a product should follow series where
 development is focused on.  To be able to do changes to facilitate
@@ -642,7 +656,8 @@
     1.0
 
 
-= Series list =
+Series list
+-----------
 
 The series for a product are returned as a sorted list, with the
 exception that the current development focus is first.
@@ -697,7 +712,9 @@
     ...     print series.name
     trunk
 
-= Changing ownership =
+
+Changing ownership
+==================
 
 If the owner of a project changes, all series and productreleases
 owned by the old owner are transfered to the new owner.

=== modified file 'lib/lp/registry/interfaces/product.py'
--- lib/lp/registry/interfaces/product.py	2010-08-22 19:26:46 +0000
+++ lib/lp/registry/interfaces/product.py	2010-08-31 21:27:50 +0000
@@ -637,6 +637,9 @@
     distrosourcepackages = Attribute(_("List of distribution packages for "
         "this product"))
 
+    ubuntu_packages = Attribute(
+        _("List of distribution packages for this product in Ubuntu"))
+
     series = exported(
         doNotSnapshot(
             CollectionField(value_type=Object(schema=IProductSeries))))

=== modified file 'lib/lp/registry/model/product.py'
--- lib/lp/registry/model/product.py	2010-08-23 18:03:26 +0000
+++ lib/lp/registry/model/product.py	2010-08-31 21:27:50 +0000
@@ -796,8 +796,6 @@
         dsps = []
         for packaging in ret:
             distro = packaging.distroseries.distribution
-            if distro in distros:
-                continue
             distros.add(distro)
             dsps.append(DistributionSourcePackage(
                 sourcepackagename=packaging.sourcepackagename,
@@ -806,6 +804,15 @@
             (x.sourcepackagename.name, x.distribution.name))
 
     @property
+    def ubuntu_packages(self):
+        """The Ubuntu `IDistributionSourcePackage`s linked to the `IProduct`.
+        """
+        ubuntu = getUtility(ILaunchpadCelebrities).ubuntu
+        return [
+            package for package in self.distrosourcepackages
+            if package.distribution == ubuntu]
+
+    @property
     def bugtargetdisplayname(self):
         """See IBugTarget."""
         return self.displayname

=== modified file 'lib/lp/registry/templates/distribution-index.pt'
--- lib/lp/registry/templates/distribution-index.pt	2010-08-06 16:01:38 +0000
+++ lib/lp/registry/templates/distribution-index.pt	2010-08-31 21:27:50 +0000
@@ -55,7 +55,7 @@
             tal:condition="context/official_answers" />
 
           <div tal:replace="structure context/@@+portlet-latestbugs"
-            tal:condition="context/official_malone" />
+            tal:condition="context/bug_tracking_usage/enumvalue:LAUNCHPAD" />
 
           <div tal:replace="structure context/@@+portlet-top-contributors" />
         </div>

=== modified file 'lib/lp/registry/templates/distribution-search.pt'
--- lib/lp/registry/templates/distribution-search.pt	2009-08-05 19:34:07 +0000
+++ lib/lp/registry/templates/distribution-search.pt	2010-08-31 21:27:50 +0000
@@ -69,7 +69,7 @@
                     tal:define="distribution package/distribution">
                     <a
                       tal:define="link package/menu:bugs/filebug"
-                      tal:condition="distribution/official_malone"
+                      tal:condition="distribution/bug_tracking_usage/enumvalue:LAUNCHPAD"
                       tal:attributes="href link/url">
                       <img
                         tal:attributes="alt link/text"

=== modified file 'lib/lp/registry/templates/product-index.pt'
--- lib/lp/registry/templates/product-index.pt	2010-08-25 19:15:48 +0000
+++ lib/lp/registry/templates/product-index.pt	2010-08-31 21:27:50 +0000
@@ -218,7 +218,7 @@
             tal:condition="context/official_answers" />
 
           <div tal:content="structure context/@@+portlet-latestbugs"
-            tal:condition="context/official_malone" />
+            tal:condition="context/bug_tracking_usage/enumvalue:LAUNCHPAD" />
 
           <div tal:content="structure context/@@+portlet-top-contributors" />
 

=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py	2010-08-30 06:38:53 +0000
+++ lib/lp/testing/factory.py	2010-08-31 21:27:50 +0000
@@ -836,7 +836,7 @@
             project=project,
             registrant=registrant)
         if official_malone is not None:
-            product.official_malone = official_malone
+            removeSecurityProxy(product).official_malone = official_malone
         if official_rosetta is not None:
             removeSecurityProxy(product).official_rosetta = official_rosetta
         if bug_supervisor is not None: