← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~jcsackett/launchpad/add-enums-to-models into lp:launchpad/devel

 

j.c.sackett has proposed merging lp:~jcsackett/launchpad/add-enums-to-models into lp:launchpad/devel with lp:~bac/launchpad/progress-enums as a prerequisite.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers): code


= Summary =

IServiceUsage provides enums to give better data on how a product or distribution uses launchpad, in place of the old bool fields (e.g. official_rosetta). However, when we roll out these enums, they will default to UNKNOWN until we can migrate data.

This branch creates properties using the enums (moved to _[ENUM] format) to use the old bool data until data is set for the enum, so we can start using the enums in code.

== Proposed fix ==

Create properties using getters and setters to intelligent find the data from the new and old attributes, and set them properly moving forward.

== Pre-implementation notes ==

Talked with Curtis.

== Implementation details ==

As above.

This branch incorporates Brad's work on the progress bar, as they need to land together. ~bac/launchpad/progress-enums
== Tests ==

bin/test -vvc -t UsageEnums

== Demo and Q/A ==

Go to a project on launchpad.dev and ensure the configuration and status
links are correct.

= Launchpad lint =

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/registry/configure.zcml
  lib/lp/registry/browser/product.py
  lib/lp/registry/browser/tests/pillar-views.txt
  lib/lp/registry/interfaces/product.py
  lib/lp/registry/model/distribution.py
  lib/lp/registry/model/product.py
  lib/lp/registry/tests/test_distribution.py
  lib/lp/registry/tests/test_product.py

./lib/lp/registry/interfaces/product.py
     901: E301 expected 1 blank line, found 2
./lib/lp/registry/model/distribution.py
    1181: E231 missing whitespace after ','
    1185: E231 missing whitespace after ','
    1197: E231 missing whitespace after ','

E301 from comment issue
E231 from creation of tuple (something = ('some tuple',))

-- 
https://code.launchpad.net/~jcsackett/launchpad/add-enums-to-models/+merge/33255
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~jcsackett/launchpad/add-enums-to-models into lp:launchpad/devel.
=== modified file 'lib/lp/registry/configure.zcml'
--- lib/lp/registry/configure.zcml	2010-08-20 19:22:45 +0000
+++ lib/lp/registry/configure.zcml	2010-08-20 19:22:47 +0000
@@ -1090,7 +1090,7 @@
             interface="lp.registry.interfaces.product.IProductEditRestricted"/>
         <require
             permission="launchpad.Edit"
-	    set_schema="lp.app.interfaces.launchpad.IServiceUsage"/>
+	        set_schema="lp.app.interfaces.launchpad.IServiceUsage"/>
         <require
             permission="launchpad.Edit"
             set_attributes="bug_reporting_guidelines
@@ -1109,6 +1109,7 @@
                             security_contact sourceforgeproject
                             summary title wikiurl"/>
 
+
         <!-- mark 2006-04-10 I put "name" in the admin group because
                         with Bazaar now in place, lots of people can have personal
                         data published at product name-based locations (personal
@@ -1376,7 +1377,6 @@
         for="lp.registry.interfaces.productseries.IProductSeries"
         factory="lp.registry.browser.productseries.ProductSeriesBreadcrumb"
         permission="zope.Public"/>
-
     <!-- ProductSeriesSet -->
 
     <class
@@ -1405,6 +1405,9 @@
             permission="launchpad.Edit"
             interface="lp.registry.interfaces.distribution.IDistributionEditRestricted"/>
         <require
+            permission="launchpad.Edit"
+	        set_schema="lp.app.interfaces.launchpad.IServiceUsage"/>
+        <require
             permission="launchpad.Moderate"
             interface="lp.registry.interfaces.distribution.IDistributionDriverRestricted"/>
         <require

=== modified file 'lib/lp/registry/model/distribution.py'
--- lib/lp/registry/model/distribution.py	2010-08-16 01:58:24 +0000
+++ lib/lp/registry/model/distribution.py	2010-08-20 19:22:47 +0000
@@ -30,7 +30,6 @@
     DecoratedResultSet)
 from canonical.launchpad.components.storm_operators import FTQ, Match, RANK
 from canonical.launchpad.interfaces.lpstorm import IStore
-from canonical.lazr.utils import safe_hasattr
 from lp.registry.model.announcement import MakesAnnouncements
 from lp.soyuz.model.archive import Archive
 from lp.soyuz.model.binarypackagename import BinaryPackageName
@@ -198,7 +197,6 @@
         else:
             alsoProvides(self, IDerivativeDistribution)
 
-
     @property
     def uploaders(self):
         """See `IDistribution`."""
@@ -235,21 +233,85 @@
         return True in (self.official_malone, self.official_rosetta,
                         self.official_blueprints, self.official_answers)
 
-    answers_usage = EnumCol(
+    _answers_usage = EnumCol(
         dbName="answers_usage", notNull=True,
         schema=ServiceUsage,
         default=ServiceUsage.UNKNOWN)
-    blueprints_usage = EnumCol(
+
+    def _get_answers_usage(self):
+        if self._answers_usage != ServiceUsage.UNKNOWN:
+            # If someone has set something with the enum, use it.
+            return self._answers_usage
+        elif self.official_answers:
+            return ServiceUsage.LAUNCHPAD
+        return self._answers_usage
+
+    def _set_answers_usage(self, val):
+        self._answers_usage = val
+        if val == ServiceUsage.LAUNCHPAD:
+            self.official_answers = True
+        else:
+            self.official_answers = False
+
+    answers_usage = property(
+        _get_answers_usage,
+        _set_answers_usage,
+        doc="Indicates if the product uses the answers service.")
+
+    _blueprints_usage = EnumCol(
         dbName="blueprints_usage", notNull=True,
         schema=ServiceUsage,
         default=ServiceUsage.UNKNOWN)
-    translations_usage = EnumCol(
+
+    def _get_blueprints_usage(self):
+        if self._blueprints_usage != ServiceUsage.UNKNOWN:
+            # If someone has set something with the enum, use it.
+            return self._blueprints_usage
+        elif self.official_blueprints:
+            return ServiceUsage.LAUNCHPAD
+        return self._blueprints_usage
+
+    def _set_blueprints_usage(self, val):
+        self._blueprints_usage = val
+        if val == ServiceUsage.LAUNCHPAD:
+            self.official_blueprints = True
+        else:
+            self.official_blueprints = False
+
+    blueprints_usage = property(
+        _get_blueprints_usage,
+        _set_blueprints_usage,
+        doc="Indicates if the product uses the blueprints service.")
+
+    _translations_usage = EnumCol(
         dbName="translations_usage", notNull=True,
         schema=ServiceUsage,
         default=ServiceUsage.UNKNOWN)
+
+    def _get_translations_usage(self):
+        if self._translations_usage != ServiceUsage.UNKNOWN:
+            # If someone has set something with the enum, use it.
+            return self._translations_usage
+        elif self.official_rosetta:
+            return ServiceUsage.LAUNCHPAD
+        return self._translations_usage
+
+    def _set_translations_usage(self, val):
+        self._translations_usage = val
+        if val == ServiceUsage.LAUNCHPAD:
+            self.official_rosetta = True
+        else:
+            self.official_rosetta = False
+
+    translations_usage = property(
+        _get_translations_usage,
+        _set_translations_usage,
+        doc="Indicates if the product uses the translations service.")
+
     @property
     def codehosting_usage(self):
         return ServiceUsage.NOT_APPLICABLE
+
     @property
     def bug_tracking_usage(self):
         if not self.official_malone:
@@ -476,9 +538,9 @@
         if not self.full_functionality:
             return None
 
-        urls = {'http_base_url' : http_base_url,
-                'ftp_base_url' : ftp_base_url,
-                'rsync_base_url' : rsync_base_url}
+        urls = {'http_base_url': http_base_url,
+                'ftp_base_url': ftp_base_url,
+                'rsync_base_url': rsync_base_url}
         for name, value in urls.items():
             if value is not None:
                 urls[name] = IDistributionMirror[name].normalize(value)
@@ -691,7 +753,7 @@
 
         # filter based on completion. see the implementation of
         # Specification.is_complete() for more details
-        completeness =  Specification.completeness_clause
+        completeness = Specification.completeness_clause
 
         if SpecificationFilter.COMPLETE in filter:
             query += ' AND ( %s ) ' % completeness
@@ -1016,15 +1078,15 @@
         find_spec = (
             DistributionSourcePackageCache,
             SourcePackageName,
-            SQL('rank(fti, ftq(%s)) AS rank' % sqlvalues(text))
+            SQL('rank(fti, ftq(%s)) AS rank' % sqlvalues(text)),
             )
         origin = [
             DistributionSourcePackageCache,
             Join(
                 SourcePackageName,
                 DistributionSourcePackageCache.sourcepackagename ==
-                    SourcePackageName.id
-                )
+                    SourcePackageName.id,
+                ),
             ]
 
         publishing_condition = ''
@@ -1065,8 +1127,7 @@
                    quote(text), quote_like(text), has_packaging_condition,
                    publishing_condition)
         dsp_caches_with_ranks = store.using(*origin).find(
-            find_spec, condition
-            ).order_by('rank DESC')
+            find_spec, condition).order_by('rank DESC')
 
         return dsp_caches_with_ranks
 
@@ -1085,8 +1146,7 @@
             cache, source_package_name, rank = result
             return DistributionSourcePackage(
                 self,
-                source_package_name
-                )
+                source_package_name)
 
         # Return the decorated result set so the consumer of these
         # results will only see DSPs
@@ -1150,7 +1210,7 @@
         extra_clauses = (
             BinaryPackageRelease.binarypackagenameID ==
                 DistroSeriesPackageCache.binarypackagenameID,
-            Match(search_vector_column, query_function)
+            Match(search_vector_column, query_function),
             )
         where_spec = (self._binaryPackageSearchClause + extra_clauses)
 
@@ -1406,11 +1466,11 @@
         # XXX Julian 2007-08-16
         # These component names should be Soyuz-wide constants.
         componentMapToArchivePurpose = {
-            'main' : ArchivePurpose.PRIMARY,
-            'restricted' : ArchivePurpose.PRIMARY,
-            'universe' : ArchivePurpose.PRIMARY,
-            'multiverse' : ArchivePurpose.PRIMARY,
-            'partner' : ArchivePurpose.PARTNER,
+            'main': ArchivePurpose.PRIMARY,
+            'restricted': ArchivePurpose.PRIMARY,
+            'universe': ArchivePurpose.PRIMARY,
+            'multiverse': ArchivePurpose.PRIMARY,
+            'partner': ArchivePurpose.PARTNER,
             'contrib': ArchivePurpose.PRIMARY,
             'non-free': ArchivePurpose.PRIMARY,
             }

=== modified file 'lib/lp/registry/model/product.py'
--- lib/lp/registry/model/product.py	2010-08-20 19:22:45 +0000
+++ lib/lp/registry/model/product.py	2010-08-20 19:22:47 +0000
@@ -261,24 +261,26 @@
         # XXX Need to remove official_codehosting column from Product
         # table.
         return self.development_focus.branch is not None
+
     @property
     def official_anything(self):
         return True in (self.official_malone, self.official_rosetta,
                         self.official_blueprints, self.official_answers,
                         self.official_codehosting)
 
-    answers_usage = EnumCol(
+    _answers_usage = EnumCol(
         dbName="answers_usage", notNull=True,
         schema=ServiceUsage,
         default=ServiceUsage.UNKNOWN)
-    blueprints_usage = EnumCol(
+    _blueprints_usage = EnumCol(
         dbName="blueprints_usage", notNull=True,
         schema=ServiceUsage,
         default=ServiceUsage.UNKNOWN)
-    translations_usage = EnumCol(
+    _translations_usage = EnumCol(
         dbName="translations_usage", notNull=True,
         schema=ServiceUsage,
         default=ServiceUsage.UNKNOWN)
+
     @property
     def codehosting_usage(self):
         if self.development_focus.branch is None:
@@ -288,6 +290,7 @@
         elif self.development_focus.branch.branch_type == BranchType.MIRRORED:
             return ServiceUsage.EXTERNAL
         return ServiceUsage.NOT_APPLICABLE
+
     @property
     def bug_tracking_usage(self):
         if self.official_malone:
@@ -296,6 +299,7 @@
             return ServiceUsage.UNKNOWN
         else:
             return ServiceUsage.EXTERNAL
+
     @property
     def uses_launchpad(self):
         """Does this distribution actually use Launchpad?"""
@@ -511,6 +515,66 @@
         super(Product, self).__storm_invalidated__()
         self._cached_licenses = None
 
+    def _get_answers_usage(self):
+        if self._answers_usage != ServiceUsage.UNKNOWN:
+            # If someone has set something with the enum, use it.
+            return self._answers_usage
+        elif self.official_answers:
+            return ServiceUsage.LAUNCHPAD
+        return self._answers_usage
+
+    def _set_answers_usage(self, val):
+        self._answers_usage = val
+        if val == ServiceUsage.LAUNCHPAD:
+            self.official_answers = True
+        else:
+            self.official_answers = False
+
+    answers_usage = property(
+        _get_answers_usage,
+        _set_answers_usage,
+        doc="Indicates if the product uses the answers service.")
+
+    def _get_blueprints_usage(self):
+        if self._blueprints_usage != ServiceUsage.UNKNOWN:
+            # If someone has set something with the enum, use it.
+            return self._blueprints_usage
+        elif self.official_blueprints:
+            return ServiceUsage.LAUNCHPAD
+        return self._blueprints_usage
+
+    def _set_blueprints_usage(self, val):
+        self._blueprints_usage = val
+        if val == ServiceUsage.LAUNCHPAD:
+            self.official_blueprints = True
+        else:
+            self.official_blueprints = False
+
+    blueprints_usage = property(
+        _get_blueprints_usage,
+        _set_blueprints_usage,
+        doc="Indicates if the product uses the blueprints service.")
+
+    def _get_translations_usage(self):
+        if self._translations_usage != ServiceUsage.UNKNOWN:
+            # If someone has set something with the enum, use it.
+            return self._translations_usage
+        elif self.official_rosetta:
+            return ServiceUsage.LAUNCHPAD
+        return self._translations_usage
+
+    def _set_translations_usage(self, val):
+        self._translations_usage = val
+        if val == ServiceUsage.LAUNCHPAD:
+            self.official_rosetta = True
+        else:
+            self.official_rosetta = False
+
+    translations_usage = property(
+        _get_translations_usage,
+        _set_translations_usage,
+        doc="Indicates if the product uses the translations service.")
+
     def _getLicenses(self):
         """Get the licenses as a tuple."""
         if self._cached_licenses is None:

=== modified file 'lib/lp/registry/tests/test_distribution.py'
--- lib/lp/registry/tests/test_distribution.py	2010-08-10 19:14:56 +0000
+++ lib/lp/registry/tests/test_distribution.py	2010-08-20 19:22:47 +0000
@@ -10,17 +10,155 @@
 from lazr.lifecycle.snapshot import Snapshot
 from lp.registry.tests.test_distroseries import (
     TestDistroSeriesCurrentSourceReleases)
+from lp.app.enums import ServiceUsage
 from lp.registry.interfaces.distroseries import NoSuchDistroSeries
 from lp.registry.interfaces.series import SeriesStatus
 from lp.registry.interfaces.distribution import IDistribution
 
 from lp.soyuz.interfaces.distributionsourcepackagerelease import (
     IDistributionSourcePackageRelease)
-from lp.testing import TestCaseWithFactory
+from lp.testing import (
+    login_person,
+    TestCaseWithFactory,
+    )
 from canonical.testing.layers import (
     DatabaseFunctionalLayer, LaunchpadFunctionalLayer)
 
 
+class TestDistributionUsageEnums(TestCaseWithFactory):
+    """Tests the usage enums for the distribution."""
+
+    layer = DatabaseFunctionalLayer
+
+    def setUp(self):
+        super(TestDistributionUsageEnums, self).setUp()
+        self.distribution = self.factory.makeDistribution()
+
+    def test_answers_usage_no_data(self):
+        # By default, we don't know anything about a distribution
+        self.assertEqual(
+            ServiceUsage.UNKNOWN,
+            self.distribution.answers_usage)
+
+    def test_answers_usage_using_bool(self):
+        # If the old bool says they use Launchpad, return LAUNCHPAD
+        # if the ServiceUsage is unknown.
+        login_person(self.distribution.owner)
+        self.distribution.official_answers = True
+        self.assertEqual(
+            ServiceUsage.LAUNCHPAD,
+            self.distribution.answers_usage)
+
+    def test_answers_usage_with_enum_data(self):
+        # If the enum has something other than UNKNOWN as its status,
+        # use that.
+        login_person(self.distribution.owner)
+        self.distribution.answers_usage = ServiceUsage.EXTERNAL
+        self.assertEqual(
+            ServiceUsage.EXTERNAL,
+            self.distribution.answers_usage)
+
+    def test_answers_setter(self):
+        login_person(self.distribution.owner)
+        self.distribution.official_answers = True
+        self.distribution.answers_usage = ServiceUsage.EXTERNAL
+        self.assertEqual(
+            False,
+            self.distribution.official_answers)
+        self.distribution.answers_usage = ServiceUsage.LAUNCHPAD
+        self.assertEqual(
+            True,
+            self.distribution.official_answers)
+
+    def test_codehosting_usage(self):
+        # Only test get for codehosting; this has no setter because the
+        # state is derived from other data.
+        distribution = self.factory.makeDistribution()
+        self.assertEqual(
+            ServiceUsage.NOT_APPLICABLE,
+            distribution.codehosting_usage)
+
+    def test_translations_usage_no_data(self):
+        # By default, we don't know anything about a distribution
+        self.assertEqual(
+            ServiceUsage.UNKNOWN,
+            self.distribution.translations_usage)
+
+    def test_translations_usage_using_bool(self):
+        # If the old bool says they use Launchpad, return LAUNCHPAD
+        # if the ServiceUsage is unknown.
+        login_person(self.distribution.owner)
+        self.distribution.official_rosetta = True
+        self.assertEqual(
+            ServiceUsage.LAUNCHPAD,
+            self.distribution.translations_usage)
+
+    def test_translations_usage_with_enum_data(self):
+        # If the enum has something other than UNKNOWN as its status,
+        # use that.
+        login_person(self.distribution.owner)
+        self.distribution.translations_usage = ServiceUsage.EXTERNAL
+        self.assertEqual(
+            ServiceUsage.EXTERNAL,
+            self.distribution.translations_usage)
+
+    def test_translations_setter(self):
+        login_person(self.distribution.owner)
+        self.distribution.official_rosetta = True
+        self.distribution.translations_usage = ServiceUsage.EXTERNAL
+        self.assertEqual(
+            False,
+            self.distribution.official_rosetta)
+        self.distribution.translations_usage = ServiceUsage.LAUNCHPAD
+        self.assertEqual(
+            True,
+            self.distribution.official_rosetta)
+
+    def test_bug_tracking_usage(self):
+        # Only test get for bug_tracking; this has no setter because the
+        # state is derived from other data.
+        distribution = self.factory.makeDistribution()
+        self.assertEqual(
+            ServiceUsage.UNKNOWN,
+            distribution.bug_tracking_usage)
+
+    def test_blueprints_usage_no_data(self):
+        # By default, we don't know anything about a distribution
+        self.assertEqual(
+            ServiceUsage.UNKNOWN,
+            self.distribution.blueprints_usage)
+
+    def test_blueprints_usage_using_bool(self):
+        # If the old bool says they use Launchpad, return LAUNCHPAD
+        # if the ServiceUsage is unknown.
+        login_person(self.distribution.owner)
+        self.distribution.official_blueprints = True
+        self.assertEqual(
+            ServiceUsage.LAUNCHPAD,
+            self.distribution.blueprints_usage)
+
+    def test_blueprints_usage_with_enum_data(self):
+        # If the enum has something other than UNKNOWN as its status,
+        # use that.
+        login_person(self.distribution.owner)
+        self.distribution.blueprints_usage = ServiceUsage.EXTERNAL
+        self.assertEqual(
+            ServiceUsage.EXTERNAL,
+            self.distribution.blueprints_usage)
+
+    def test_blueprints_setter(self):
+        login_person(self.distribution.owner)
+        self.distribution.official_blueprints = True
+        self.distribution.blueprints_usage = ServiceUsage.EXTERNAL
+        self.assertEqual(
+            False,
+            self.distribution.official_blueprints)
+        self.distribution.blueprints_usage = ServiceUsage.LAUNCHPAD
+        self.assertEqual(
+            True,
+            self.distribution.official_blueprints)
+
+
 class TestDistribution(TestCaseWithFactory):
 
     layer = DatabaseFunctionalLayer

=== modified file 'lib/lp/registry/tests/test_product.py'
--- lib/lp/registry/tests/test_product.py	2010-08-10 19:14:56 +0000
+++ lib/lp/registry/tests/test_product.py	2010-08-20 19:22:47 +0000
@@ -14,18 +14,145 @@
 from canonical.launchpad.ftests import login
 from canonical.launchpad.testing.pages import (
     find_main_content, get_feedback_messages, setupBrowser)
-from canonical.testing import LaunchpadFunctionalLayer
+from canonical.testing import (
+    DatabaseFunctionalLayer,
+    LaunchpadFunctionalLayer,
+    )
 
 from canonical.launchpad.ftests import syncUpdate
 
 from lazr.lifecycle.snapshot import Snapshot
+from lp.app.enums import ServiceUsage
 from lp.registry.interfaces.person import IPersonSet
 from lp.registry.interfaces.product import IProduct, License
 from lp.registry.model.product import Product
 from lp.registry.model.productlicense import ProductLicense
 from lp.registry.model.commercialsubscription import (
     CommercialSubscription)
-from lp.testing import TestCaseWithFactory
+from lp.testing import (
+    login_person,
+    TestCaseWithFactory,
+    )
+
+
+class TestProductUsageEnums(TestCaseWithFactory):
+    """Tests the usage enums for the product."""
+
+    layer = DatabaseFunctionalLayer
+
+    def setUp(self):
+        super(TestProductUsageEnums, self).setUp()
+        self.product = self.factory.makeProduct()
+
+    def test_answers_usage_no_data(self):
+        # By default, we don't know anything about a product
+        self.assertEqual(ServiceUsage.UNKNOWN, self.product.answers_usage)
+
+    def test_answers_usage_using_bool(self):
+        # If the old bool says they use Launchpad, return LAUNCHPAD
+        # if the ServiceUsage is unknown.
+        login_person(self.product.owner)
+        self.product.official_answers = True
+        self.assertEqual(ServiceUsage.LAUNCHPAD, self.product.answers_usage)
+
+    def test_answers_usage_with_enum_data(self):
+        # If the enum has something other than UNKNOWN as its status,
+        # use that.
+        login_person(self.product.owner)
+        self.product.answers_usage = ServiceUsage.EXTERNAL
+        self.assertEqual(ServiceUsage.EXTERNAL, self.product.answers_usage)
+
+    def test_answers_setter(self):
+        login_person(self.product.owner)
+        self.product.official_answers = True
+        self.product.answers_usage = ServiceUsage.EXTERNAL
+        self.assertEqual(
+            False,
+            self.product.official_answers)
+        self.product.answers_usage = ServiceUsage.LAUNCHPAD
+        self.assertEqual(
+            True,
+            self.product.official_answers)
+
+    def test_codehosting_usage(self):
+        # Only test get for codehosting; this has no setter because the
+        # state is derived from other data.
+        product = self.factory.makeProduct()
+        self.assertEqual(ServiceUsage.UNKNOWN, product.codehosting_usage)
+
+    def test_translations_usage_no_data(self):
+        # By default, we don't know anything about a product
+        self.assertEqual(
+            ServiceUsage.UNKNOWN,
+            self.product.translations_usage)
+
+    def test_translations_usage_using_bool(self):
+        # If the old bool says they use Launchpad, return LAUNCHPAD
+        # if the ServiceUsage is unknown.
+        login_person(self.product.owner)
+        self.product.official_rosetta = True
+        self.assertEqual(
+            ServiceUsage.LAUNCHPAD,
+            self.product.translations_usage)
+
+    def test_translations_usage_with_enum_data(self):
+        # If the enum has something other than UNKNOWN as its status,
+        # use that.
+        login_person(self.product.owner)
+        self.product.translations_usage = ServiceUsage.EXTERNAL
+        self.assertEqual(
+            ServiceUsage.EXTERNAL,
+            self.product.translations_usage)
+
+    def test_translations_setter(self):
+        login_person(self.product.owner)
+        self.product.official_rosetta = True
+        self.product.translations_usage = ServiceUsage.EXTERNAL
+        self.assertEqual(
+            False,
+            self.product.official_rosetta)
+        self.product.translations_usage = ServiceUsage.LAUNCHPAD
+        self.assertEqual(
+            True,
+            self.product.official_rosetta)
+
+    def test_bug_tracking_usage(self):
+        # Only test get for bug_tracking; this has no setter because the
+        # state is derived from other data.
+        product = self.factory.makeProduct()
+        self.assertEqual(ServiceUsage.UNKNOWN, product.bug_tracking_usage)
+
+    def test_blueprints_usage_no_data(self):
+        # By default, we don't know anything about a product
+        self.assertEqual(ServiceUsage.UNKNOWN, self.product.blueprints_usage)
+
+    def test_blueprints_usage_using_bool(self):
+        # If the old bool says they use Launchpad, return LAUNCHPAD
+        # if the ServiceUsage is unknown.
+        login_person(self.product.owner)
+        self.product.official_blueprints = True
+        self.assertEqual(
+            ServiceUsage.LAUNCHPAD,
+            self.product.blueprints_usage)
+
+    def test_blueprints_usage_with_enum_data(self):
+        # If the enum has something other than UNKNOWN as its status,
+        # use that.
+        login_person(self.product.owner)
+        self.product.blueprints_usage = ServiceUsage.EXTERNAL
+        self.assertEqual(ServiceUsage.EXTERNAL, self.product.blueprints_usage)
+
+    def test_blueprints_setter(self):
+        login_person(self.product.owner)
+        self.product.official_blueprints = True
+        self.product.blueprints_usage = ServiceUsage.EXTERNAL
+        self.assertEqual(
+            False,
+            self.product.official_blueprints)
+        self.product.blueprints_usage = ServiceUsage.LAUNCHPAD
+        self.assertEqual(
+            True,
+            self.product.official_blueprints)
 
 
 class TestProduct(TestCaseWithFactory):


Follow ups