Merge lp:~rharding/launchpad/store_edit_product_info_type into lp:launchpad

Proposed by Richard Harding
Status: Merged
Approved by: Richard Harding
Approved revision: no longer in the source branch.
Merged at revision: 16120
Proposed branch: lp:~rharding/launchpad/store_edit_product_info_type
Merge into: lp:launchpad
Prerequisite: lp:~rharding/launchpad/edit_product_info_type
Diff against target: 324 lines (+162/-33)
6 files modified
lib/lp/registry/browser/tests/test_product.py (+68/-0)
lib/lp/registry/javascript/product_views.js (+4/-2)
lib/lp/registry/model/product.py (+41/-9)
lib/lp/registry/stories/webservice/xx-project-registry.txt (+1/-1)
lib/lp/registry/tests/test_product.py (+46/-19)
lib/lp/scripts/garbo.py (+2/-2)
To merge this branch: bzr merge lp:~rharding/launchpad/store_edit_product_info_type
Reviewer Review Type Date Requested Status
Aaron Bentley (community) Approve
Review via email: mp+128288@code.launchpad.net

Commit message

Implement updating based on changing the information type on Product edit.

Description of the change

= Summary =

This is the second branch in allowing edits to the Product. It updates the
setter for the information type to update policies as required.

== Pre Implementation ==

Talked with Aaron about updating the policies correctly.

== Implementation Notes ==

The first update is to change the validation to make sure we allow the
_setLicense to create a commercial subscription if we've not had one
previously. This allows for a Public project to go to a Private information
type as an edit.

The second update makes sure that after we change the information type that
any policies are updated as required.

== Q/A ==

Create a new project, as a public information type, and then after you create
it go to the "Edit details" and change it to a Private type. It should adjust
accordingly and show as Private.

Create a new project, as a private information type, and then change it to
Public and it should.

== Tests ==

test_product.py

== Lint ==

Cleaned up a couple of lint errors from this and previous branch.

To post a comment you must log in.
Revision history for this message
Aaron Bentley (abentley) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/registry/browser/tests/test_product.py'
--- lib/lp/registry/browser/tests/test_product.py 2012-10-08 10:07:11 +0000
+++ lib/lp/registry/browser/tests/test_product.py 2012-10-10 12:32:23 +0000
@@ -432,6 +432,74 @@
432 cache.objects['team_membership_policy_data'])432 cache.objects['team_membership_policy_data'])
433433
434434
435class TestProductEditView(TestCaseWithFactory):
436 """Tests for the ProductEditView"""
437
438 layer = DatabaseFunctionalLayer
439
440 def setUp(self):
441 super(TestProductEditView, self).setUp()
442
443 def _make_product_edit_form(self, proprietary=False):
444 """Return form data for product edit.
445
446 :param product: Factory product to base the form data base of.
447 """
448 if proprietary:
449 licenses = License.OTHER_PROPRIETARY.title
450 license_info = 'Commercial Subscription'
451 information_type = 'PROPRIETARY'
452 else:
453 licenses = ['MIT']
454 license_info = ''
455 information_type = 'PUBLIC'
456
457 return {
458 'field.actions.change': 'Change',
459 'field.name': self.product.name,
460 'field.displayname': self.product.displayname,
461 'field.title': self.product.title,
462 'field.summary': self.product.summary,
463 'field.information_type': information_type,
464 'field.licenses': licenses,
465 'field.license_info': license_info,
466 }
467
468 def test_change_information_type_proprietary(self):
469 product = self.factory.makeProduct(name='fnord')
470 with FeatureFixture({u'disclosure.private_projects.enabled': u'on'}):
471 login_person(product.owner)
472 form = self._make_product_edit_form(proprietary=True)
473 view = create_initialized_view(product, '+edit', form=form)
474 self.assertEqual(0, len(view.errors))
475
476 product_set = getUtility(IProductSet)
477 updated_product = product_set.getByName('fnord')
478 self.assertEqual(
479 InformationType.PROPRIETARY, updated_product.information_type)
480 # A complimentary commercial subscription is auto generated for
481 # the product when the information type is changed.
482 self.assertIsNotNone(updated_product.commercial_subscription)
483
484 def test_change_information_type_public(self):
485 owner = self.factory.makePerson(name='pting')
486 product = self.factory.makeProduct(
487 name='fnord',
488 information_type=InformationType.PROPRIETARY,
489 owner=owner,
490 )
491 with FeatureFixture({u'disclosure.private_projects.enabled': u'on'}):
492 login_person(owner)
493 form = self._make_product_edit_form()
494 view = create_initialized_view(product, '+edit', form=form)
495 self.assertEqual(0, len(view.errors))
496
497 product_set = getUtility(IProductSet)
498 updated_product = product_set.getByName('fnord')
499 self.assertEqual(
500 InformationType.PUBLIC, updated_product.information_type)
501
502
435class ProductSetReviewLicensesViewTestCase(TestCaseWithFactory):503class ProductSetReviewLicensesViewTestCase(TestCaseWithFactory):
436 """Tests the ProductSetReviewLicensesView."""504 """Tests the ProductSetReviewLicensesView."""
437505
438506
=== modified file 'lib/lp/registry/javascript/product_views.js'
--- lib/lp/registry/javascript/product_views.js 2012-10-10 12:32:23 +0000
+++ lib/lp/registry/javascript/product_views.js 2012-10-10 12:32:23 +0000
@@ -345,8 +345,10 @@
345 */345 */
346 information_type_change: function (value) {346 information_type_change: function (value) {
347 toggle_license_field(value);347 toggle_license_field(value);
348 var driver_cont = Y.one('input[name="field.driver"]').ancestor('td');348 var driver_cont =
349 var bug_super_cont = Y.one('input[name="field.bug_supervisor"]').ancestor('td');349 Y.one('input[name="field.driver"]').ancestor('td');
350 var bug_super_cont =
351 Y.one('input[name="field.bug_supervisor"]').ancestor('td');
350352
351 if (info_type.get_cache_data_from_key(value, 'value',353 if (info_type.get_cache_data_from_key(value, 'value',
352 'is_private')) {354 'is_private')) {
353355
=== modified file 'lib/lp/registry/model/product.py'
--- lib/lp/registry/model/product.py 2012-10-08 10:07:11 +0000
+++ lib/lp/registry/model/product.py 2012-10-10 12:32:23 +0000
@@ -433,17 +433,43 @@
433 # Proprietary check works only after creation, because during433 # Proprietary check works only after creation, because during
434 # creation, has_commercial_subscription cannot give the right value434 # creation, has_commercial_subscription cannot give the right value
435 # and triggers an inappropriate DB flush.435 # and triggers an inappropriate DB flush.
436 if (not self._SO_creating and value in PROPRIETARY_INFORMATION_TYPES436
437 and not self.has_current_commercial_subscription):437 # If you're changing the license, and setting a PROPRIETARY
438 raise CommercialSubscribersOnly(438 # information type, yet you don't have a subscription you get one when
439 'A valid commercial subscription is required for private'439 # the license is set.
440 ' Projects.')440
441 # If you have a commercial subscription, but it's not current, you
442 # cannot set the information type to a PROPRIETARY type.
443 if not self._SO_creating and value in PROPRIETARY_INFORMATION_TYPES:
444 # Create the complimentary commercial subscription for the product.
445 self._ensure_complimentary_subscription()
446
447 if not self.has_current_commercial_subscription:
448 raise CommercialSubscribersOnly(
449 'A valid commercial subscription is required for private'
450 ' Projects.')
451
441 return value452 return value
442453
443 information_type = EnumCol(454 _information_type = EnumCol(
444 enum=InformationType, default=InformationType.PUBLIC,455 enum=InformationType, default=InformationType.PUBLIC,
456 dbName="information_type",
445 storm_validator=_valid_product_information_type)457 storm_validator=_valid_product_information_type)
446458
459 def _get_information_type(self):
460 return self._information_type or InformationType.PUBLIC
461
462 def _set_information_type(self, value):
463 self._information_type = value
464 # Make sure that policies are updated to grant permission to the
465 # maintainer as required for the Product.
466 # However, only on edits. If this is a new Product it's handled
467 # already.
468 if not self._SO_creating:
469 self._ensurePolicies([value])
470
471 information_type = property(_get_information_type, _set_information_type)
472
447 security_contact = None473 security_contact = None
448474
449 @property475 @property
@@ -952,6 +978,15 @@
952 get_property_cache(self)._cached_licenses = tuple(sorted(licenses))978 get_property_cache(self)._cached_licenses = tuple(sorted(licenses))
953 if (License.OTHER_PROPRIETARY in licenses979 if (License.OTHER_PROPRIETARY in licenses
954 and self.commercial_subscription is None):980 and self.commercial_subscription is None):
981 self._ensure_complimentary_subscription()
982
983 notify(LicensesModifiedEvent(self))
984
985 licenses = property(_getLicenses, _setLicenses)
986
987 def _ensure_complimentary_subscription(self):
988 """Create a complementary commercial subscription for the product"""
989 if not self.commercial_subscription:
955 lp_janitor = getUtility(ILaunchpadCelebrities).janitor990 lp_janitor = getUtility(ILaunchpadCelebrities).janitor
956 now = datetime.datetime.now(pytz.UTC)991 now = datetime.datetime.now(pytz.UTC)
957 date_expires = now + datetime.timedelta(days=30)992 date_expires = now + datetime.timedelta(days=30)
@@ -964,9 +999,6 @@
964 registrant=lp_janitor, purchaser=lp_janitor,999 registrant=lp_janitor, purchaser=lp_janitor,
965 sales_system_id=sales_system_id, whiteboard=whiteboard)1000 sales_system_id=sales_system_id, whiteboard=whiteboard)
966 get_property_cache(self).commercial_subscription = subscription1001 get_property_cache(self).commercial_subscription = subscription
967 notify(LicensesModifiedEvent(self))
968
969 licenses = property(_getLicenses, _setLicenses)
9701002
971 def _getOwner(self):1003 def _getOwner(self):
972 """Get the owner."""1004 """Get the owner."""
9731005
=== modified file 'lib/lp/registry/stories/webservice/xx-project-registry.txt'
--- lib/lp/registry/stories/webservice/xx-project-registry.txt 2012-10-08 10:07:11 +0000
+++ lib/lp/registry/stories/webservice/xx-project-registry.txt 2012-10-10 12:32:23 +0000
@@ -168,7 +168,7 @@
168 freshmeat_project: None168 freshmeat_project: None
169 homepage_url: None169 homepage_url: None
170 icon_link: u'http://.../firefox/icon'170 icon_link: u'http://.../firefox/icon'
171 information_type: None171 information_type: u'Public'
172 is_permitted: True172 is_permitted: True
173 license_approved: False173 license_approved: False
174 license_info: None174 license_info: None
175175
=== modified file 'lib/lp/registry/tests/test_product.py'
--- lib/lp/registry/tests/test_product.py 2012-10-10 02:45:36 +0000
+++ lib/lp/registry/tests/test_product.py 2012-10-10 12:32:23 +0000
@@ -4,8 +4,10 @@
4__metaclass__ = type4__metaclass__ = type
55
6from cStringIO import StringIO6from cStringIO import StringIO
7import datetime7from datetime import (
88 datetime,
9 timedelta,
10 )
9import pytz11import pytz
10from storm.locals import Store12from storm.locals import Store
11from testtools.matchers import MatchesAll13from testtools.matchers import MatchesAll
@@ -500,21 +502,46 @@
500 with person_logged_in(product.owner):502 with person_logged_in(product.owner):
501 product.information_type = info_type503 product.information_type = info_type
502504
503 def test_product_information_set_proprietary_requires_commercial(self):505 def test_set_proprietary_gets_commerical_subscription(self):
504 # Cannot set Product.information_type to proprietary values if no506 # Changing a Product to Proprietary will auto generate a complimentary
505 # commercial subscription.507 # subscription just as choosing a proprietary license at creation time.
506 product = self.factory.makeProduct()508 owner = self.factory.makePerson(name='pting')
507 self.useContext(person_logged_in(product.owner))509 product = self.factory.makeProduct(owner=owner)
510 self.useContext(person_logged_in(owner))
511 self.assertIsNone(product.commercial_subscription)
512
513 product.information_type = InformationType.PROPRIETARY
514 self.assertEqual(InformationType.PROPRIETARY, product.information_type)
515 self.assertIsNotNone(product.commercial_subscription)
516
517 def test_set_proprietary_fails_expired_commerical_subscription(self):
518 # Cannot set information type to proprietary with an expired
519 # complimentary subscription.
520 owner = self.factory.makePerson(name='pting')
521 product = self.factory.makeProduct(
522 information_type=InformationType.PROPRIETARY,
523 owner=owner,
524 )
525 self.useContext(person_logged_in(owner))
526
527 # The Product now has a complimentary commercial subscription.
528 new_expires_date = datetime.now(pytz.timezone('UTC')) - timedelta(1)
529 naked_subscription = removeSecurityProxy(
530 product.commercial_subscription)
531 naked_subscription.date_expires = new_expires_date
532
533 # We can make the product PUBLIC
534 product.information_type = InformationType.PUBLIC
535 self.assertEqual(InformationType.PUBLIC, product.information_type)
536
537 # However we can't change it back to a Proprietary because our
538 # commercial subscription has expired.
508 for info_type in PROPRIETARY_INFORMATION_TYPES:539 for info_type in PROPRIETARY_INFORMATION_TYPES:
509 with ExpectedException(540 with ExpectedException(
510 CommercialSubscribersOnly,541 CommercialSubscribersOnly,
511 'A valid commercial subscription is required for private'542 'A valid commercial subscription is required for private'
512 ' Projects.'):543 ' Projects.'):
513 product.information_type = info_type544 product.information_type = info_type
514 product.redeemSubscriptionVoucher(
515 'hello', product.owner, product.owner, 1)
516 for info_type in PROPRIETARY_INFORMATION_TYPES:
517 product.information_type = info_type
518545
519 def test_product_information_init_proprietary_requires_commercial(self):546 def test_product_information_init_proprietary_requires_commercial(self):
520 # Cannot create a product with proprietary types without specifying547 # Cannot create a product with proprietary types without specifying
@@ -980,9 +1007,9 @@
980 cs = product.commercial_subscription1007 cs = product.commercial_subscription
981 self.assertIsNotNone(cs)1008 self.assertIsNotNone(cs)
982 self.assertIn('complimentary-30-day', cs.sales_system_id)1009 self.assertIn('complimentary-30-day', cs.sales_system_id)
983 now = datetime.datetime.now(pytz.UTC)1010 now = datetime.now(pytz.UTC)
984 self.assertTrue(now >= cs.date_starts)1011 self.assertTrue(now >= cs.date_starts)
985 future_30_days = now + datetime.timedelta(days=30)1012 future_30_days = now + timedelta(days=30)
986 self.assertTrue(future_30_days >= cs.date_expires)1013 self.assertTrue(future_30_days >= cs.date_expires)
987 self.assertIn(1014 self.assertIn(
988 "Complimentary 30 day subscription. -- Launchpad",1015 "Complimentary 30 day subscription. -- Launchpad",
@@ -1003,9 +1030,9 @@
1003 cs = product.commercial_subscription1030 cs = product.commercial_subscription
1004 self.assertIsNotNone(cs)1031 self.assertIsNotNone(cs)
1005 self.assertIn('complimentary-30-day', cs.sales_system_id)1032 self.assertIn('complimentary-30-day', cs.sales_system_id)
1006 now = datetime.datetime.now(pytz.UTC)1033 now = datetime.now(pytz.UTC)
1007 self.assertTrue(now >= cs.date_starts)1034 self.assertTrue(now >= cs.date_starts)
1008 future_30_days = now + datetime.timedelta(days=30)1035 future_30_days = now + timedelta(days=30)
1009 self.assertTrue(future_30_days >= cs.date_expires)1036 self.assertTrue(future_30_days >= cs.date_expires)
1010 self.assertIn(1037 self.assertIn(
1011 "Complimentary 30 day subscription. -- Launchpad",1038 "Complimentary 30 day subscription. -- Launchpad",
@@ -1283,8 +1310,8 @@
1283 product = question.product1310 product = question.product
1284 transaction.commit()1311 transaction.commit()
1285 ws_product = self.wsObject(product, product.owner)1312 ws_product = self.wsObject(product, product.owner)
1286 now = datetime.datetime.now(tz=pytz.utc)1313 now = datetime.now(tz=pytz.utc)
1287 day = datetime.timedelta(days=1)1314 day = timedelta(days=1)
1288 self.failUnlessEqual(1315 self.failUnlessEqual(
1289 [oopsid],1316 [oopsid],
1290 ws_product.findReferencedOOPS(start_date=now - day, end_date=now))1317 ws_product.findReferencedOOPS(start_date=now - day, end_date=now))
@@ -1301,8 +1328,8 @@
1301 product = self.factory.makeProduct()1328 product = self.factory.makeProduct()
1302 transaction.commit()1329 transaction.commit()
1303 ws_product = self.wsObject(product, product.owner)1330 ws_product = self.wsObject(product, product.owner)
1304 now = datetime.datetime.now(tz=pytz.utc)1331 now = datetime.now(tz=pytz.utc)
1305 day = datetime.timedelta(days=1)1332 day = timedelta(days=1)
1306 self.failUnlessEqual(1333 self.failUnlessEqual(
1307 [],1334 [],
1308 ws_product.findReferencedOOPS(start_date=now - day, end_date=now))1335 ws_product.findReferencedOOPS(start_date=now - day, end_date=now))
13091336
=== modified file 'lib/lp/scripts/garbo.py'
--- lib/lp/scripts/garbo.py 2012-10-08 10:07:11 +0000
+++ lib/lp/scripts/garbo.py 2012-10-10 12:32:23 +0000
@@ -928,10 +928,10 @@
928 def __call__(self, chunk_size):928 def __call__(self, chunk_size):
929 """See `TunableLoop`."""929 """See `TunableLoop`."""
930 subselect = Select(930 subselect = Select(
931 Product.id, Product.information_type == None,931 Product.id, Product._information_type == None,
932 limit=chunk_size)932 limit=chunk_size)
933 result = self.store.execute(933 result = self.store.execute(
934 Update({Product.information_type: 1},934 Update({Product._information_type: 1},
935 Product.id.is_in(subselect)))935 Product.id.is_in(subselect)))
936 transaction.commit()936 transaction.commit()
937 self.rows_updated = result.rowcount937 self.rows_updated = result.rowcount