← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~cprov/launchpad/create-spec-webservice into lp:launchpad

 

Celso Providelo has proposed merging lp:~cprov/launchpad/create-spec-webservice into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  Bug #1329424 in Launchpad itself: "cannot create specification via API"
  https://bugs.launchpad.net/launchpad/+bug/1329424

For more details, see:
https://code.launchpad.net/~cprov/launchpad/create-spec-webservice/+merge/223332

Expose ISpecificationSet as and empty collection by default and add a named operation for creating new specifications.
-- 
https://code.launchpad.net/~cprov/launchpad/create-spec-webservice/+merge/223332
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cprov/launchpad/create-spec-webservice into lp:launchpad.
=== modified file 'lib/lp/blueprints/interfaces/specification.py'
--- lib/lp/blueprints/interfaces/specification.py	2013-06-28 00:49:47 +0000
+++ lib/lp/blueprints/interfaces/specification.py	2014-06-17 04:31:31 +0000
@@ -18,8 +18,11 @@
 
 from lazr.restful.declarations import (
     call_with,
+    collection_default_content,
     error_status,
+    export_as_webservice_collection,
     export_as_webservice_entry,
+    export_factory_operation,
     export_operation_as,
     export_write_operation,
     exported,
@@ -712,6 +715,29 @@
 
 class ISpecificationSet(IHasSpecifications):
     """A container for specifications."""
+    export_as_webservice_collection(ISpecification)
+
+    @collection_default_content()
+    def empty_list():
+        """Return an empty set - only exists to keep lazr.restful happy."""
+
+    @call_with(owner=REQUEST_USER)
+    @operation_parameters(
+        target=Reference(
+            schema=ISpecificationTarget, required=True,
+            title=(u"The product or distribution context of this "
+                   u"specification.")))
+    @export_factory_operation(
+        ISpecification, ['name', 'title', 'specurl', 'summary',
+                         'definition_status', 'assignee', 'drafter',
+                         'whiteboard'])
+    @operation_for_version('devel')
+    def createSpecification(name, title, specurl, summary, definition_status,
+                            owner, target, approver=None, assignee=None,
+                            drafter=None, whiteboard=None,
+                            information_type=None,
+                            priority=SpecificationPriority.UNDEFINED):
+        """Create a new Specification."""
 
     displayname = Attribute('Displayname')
 
@@ -739,10 +765,10 @@
         """Return the specification with the given name for the given pillar.
         """
 
-    def new(name, title, specurl, summary, definition_status,
-        owner, approver=None, product=None, distribution=None, assignee=None,
-        drafter=None, whiteboard=None,
-        priority=SpecificationPriority.UNDEFINED):
+    def new(name, title, specurl, summary, definition_status, owner,
+            approver=None, product=None, distribution=None, assignee=None,
+            drafter=None, whiteboard=None, information_type=None,
+            priority=SpecificationPriority.UNDEFINED):
         """Create a new specification."""
 
     def getDependencyDict(specifications):

=== modified file 'lib/lp/blueprints/interfaces/webservice.py'
--- lib/lp/blueprints/interfaces/webservice.py	2013-06-27 15:11:38 +0000
+++ lib/lp/blueprints/interfaces/webservice.py	2014-06-17 04:31:31 +0000
@@ -22,6 +22,7 @@
 from lp.blueprints.interfaces.specification import (
     GoalProposeError,
     ISpecification,
+    ISpecificationSet,
     )
 from lp.blueprints.interfaces.specificationbranch import ISpecificationBranch
 from lp.blueprints.interfaces.specificationsubscription import (

=== modified file 'lib/lp/blueprints/model/specification.py'
--- lib/lp/blueprints/model/specification.py	2013-12-13 12:51:41 +0000
+++ lib/lp/blueprints/model/specification.py	2014-06-17 04:31:31 +0000
@@ -1111,3 +1111,23 @@
     def get(self, spec_id):
         """See lp.blueprints.interfaces.specification.ISpecificationSet."""
         return Specification.get(spec_id)
+
+    def empty_list(self):
+        """See `ISpecificationSet`."""
+        return []
+
+    def createSpecification(self, name, title, specurl, summary,
+                            definition_status, owner, target, approver=None,
+                            assignee=None, drafter=None, whiteboard=None,
+                            information_type=None,
+                            priority=SpecificationPriority.UNDEFINED):
+        """See `ISpecificationSet`."""
+        spec = self.new(
+            name=name, title=title, specurl=specurl, summary=summary,
+            definition_status=definition_status, owner=owner,
+            approver=approver,assignee=assignee, drafter=drafter,
+            whiteboard=whiteboard, priority=priority,
+            information_type=information_type
+        )
+        spec.setTarget(target)
+        return spec

=== modified file 'lib/lp/blueprints/tests/test_webservice.py'
--- lib/lp/blueprints/tests/test_webservice.py	2013-06-27 09:40:16 +0000
+++ lib/lp/blueprints/tests/test_webservice.py	2014-06-17 04:31:31 +0000
@@ -11,7 +11,9 @@
 
 from lp.blueprints.enums import SpecificationDefinitionStatus
 from lp.services.webapp.interaction import ANONYMOUS
+from lp.services.webapp.interfaces import OAuthPermission
 from lp.testing import (
+    api_url,
     launchpadlib_for,
     person_logged_in,
     TestCaseWithFactory,
@@ -48,6 +50,55 @@
         return launchpadlib.load(pillar_name)
 
 
+class SpecificationWebserviceTests(SpecificationWebserviceTestCase):
+    """Test accessing specification top-level webservice."""
+    layer = AppServerLayer
+
+    def test_collection(self):
+        # `ISpecificationSet` is exposed as a webservice via /specs
+        # and is represented by an empty collection.
+        user = self.factory.makePerson()
+        webservice = webservice_for_person(user)
+        response = webservice.get('/specs')
+        self.assertEqual(200, response.status)
+        self.assertEqual(
+            ['entries', 'resource_type_link', 'start', 'total_size'],
+            sorted(response.jsonBody().keys()))
+        self.assertEqual(0, response.jsonBody()['total_size'])
+
+    def test_creation_for_products(self):
+        # `ISpecificationSet.createSpecification` is exposed and
+        # allows specification creation for products.
+        user = self.factory.makePerson()
+        product = self.factory.makeProduct()
+        product_url = api_url(product)
+        webservice = webservice_for_person(
+            user, permission=OAuthPermission.WRITE_PUBLIC)
+        response = webservice.named_post(
+            '/specs', 'createSpecification',
+            name='test-prod', title='Product', specurl='http://test.com',
+            definition_status='Approved', summary='A summary',
+            target=product_url,
+            api_version='devel')
+        self.assertEqual(201, response.status)
+
+    def test_creation_for_distribution(self):
+        # `ISpecificationSet.createSpecification` also allows
+        # specification creation for distributions.
+        user = self.factory.makePerson()
+        distribution = self.factory.makeDistribution()
+        distribution_url = api_url(distribution)
+        webservice = webservice_for_person(
+            user, permission=OAuthPermission.WRITE_PUBLIC)
+        response = webservice.named_post(
+            '/specs', 'createSpecification',
+            name='test-distro', title='Distro', specurl='http://test.com',
+            definition_status='Approved', summary='A summary',
+            target=distribution_url,
+            api_version='devel')
+        self.assertEqual(201, response.status)
+
+
 class SpecificationAttributeWebserviceTests(SpecificationWebserviceTestCase):
     """Test accessing specification attributes over the webservice."""
     layer = AppServerLayer


Follow ups