From ff55f866ffb5d0200bae6fc7358197608369feec Mon Sep 17 00:00:00 2001 From: "robin.keunen" Date: Fri, 6 Mar 2020 18:44:24 +0100 Subject: [PATCH 01/12] [ADD] easy_my_coop_api --- easy_my_coop_api/README.rst | 69 +++ easy_my_coop_api/__init__.py | 2 + easy_my_coop_api/__manifest__.py | 20 + easy_my_coop_api/controllers/__init__.py | 1 + .../controllers/ping_controller.py | 23 + easy_my_coop_api/readme/CONTRIBUTORS.rst | 2 + easy_my_coop_api/readme/DESCRIPTION.rst | 1 + easy_my_coop_api/services/__init__.py | 1 + easy_my_coop_api/services/ping_service.py | 36 ++ .../static/description/index.html | 425 ++++++++++++++++++ easy_my_coop_api/tests/__init__.py | 2 + easy_my_coop_api/tests/common.py | 28 ++ easy_my_coop_api/tests/test_ping.py | 38 ++ easy_my_coop_api/tests/test_registry.py | 28 ++ 14 files changed, 676 insertions(+) create mode 100644 easy_my_coop_api/README.rst create mode 100644 easy_my_coop_api/__init__.py create mode 100644 easy_my_coop_api/__manifest__.py create mode 100644 easy_my_coop_api/controllers/__init__.py create mode 100644 easy_my_coop_api/controllers/ping_controller.py create mode 100644 easy_my_coop_api/readme/CONTRIBUTORS.rst create mode 100644 easy_my_coop_api/readme/DESCRIPTION.rst create mode 100644 easy_my_coop_api/services/__init__.py create mode 100644 easy_my_coop_api/services/ping_service.py create mode 100644 easy_my_coop_api/static/description/index.html create mode 100644 easy_my_coop_api/tests/__init__.py create mode 100644 easy_my_coop_api/tests/common.py create mode 100644 easy_my_coop_api/tests/test_ping.py create mode 100644 easy_my_coop_api/tests/test_registry.py diff --git a/easy_my_coop_api/README.rst b/easy_my_coop_api/README.rst new file mode 100644 index 0000000..4eb4cb6 --- /dev/null +++ b/easy_my_coop_api/README.rst @@ -0,0 +1,69 @@ +================ +Easy My Coop API +================ + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-coopiteasy%2Fvertical--cooperative-lightgray.png?logo=github + :target: https://github.com/coopiteasy/vertical-cooperative/tree/12.0/easy_my_coop_api + :alt: coopiteasy/vertical-cooperative + +|badge1| |badge2| |badge3| + +Open Easy My Coop to the world: RESTful API. + +**Table of contents** + +.. contents:: + :local: + +Known issues / Roadmap +====================== + +The API should generate and use an external id for records instead +of odoo's generated id. It would make importing and export data as +well as migrating across versions easier. + +One way would be to use uuid but the default BaseRESTController +routes would need to be rewritten: they only take integer as ids. +Another way is to define a sequence per model. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Coop IT Easy SCRLfs + +Contributors +~~~~~~~~~~~~ + +* Coop IT Easy SCRLfs +* Robin Keunen + +Maintainers +~~~~~~~~~~~ + +This module is part of the `coopiteasy/vertical-cooperative `_ project on GitHub. + +You are welcome to contribute. diff --git a/easy_my_coop_api/__init__.py b/easy_my_coop_api/__init__.py new file mode 100644 index 0000000..d6d6244 --- /dev/null +++ b/easy_my_coop_api/__init__.py @@ -0,0 +1,2 @@ +from . import controllers +from . import services diff --git a/easy_my_coop_api/__manifest__.py b/easy_my_coop_api/__manifest__.py new file mode 100644 index 0000000..54b4f4f --- /dev/null +++ b/easy_my_coop_api/__manifest__.py @@ -0,0 +1,20 @@ +# Copyright 2020 Coop IT Easy SCRL fs +# Robin Keunen +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +{ + "name": "Easy My Coop API", + "version": "12.0.0.0.1", + "depends": ["base_rest", "easy_my_coop"], + "author": "Coop IT Easy SCRLfs", + "category": "Cooperative management", + "website": "www.coopiteasy.be", + "license": "AGPL-3", + "summary": """ + Open Easy My Coop to the world: RESTful API. + """, + "data": [], + "demo": [], + "installable": True, + "application": False, +} diff --git a/easy_my_coop_api/controllers/__init__.py b/easy_my_coop_api/controllers/__init__.py new file mode 100644 index 0000000..e505936 --- /dev/null +++ b/easy_my_coop_api/controllers/__init__.py @@ -0,0 +1 @@ +from . import ping_controller diff --git a/easy_my_coop_api/controllers/ping_controller.py b/easy_my_coop_api/controllers/ping_controller.py new file mode 100644 index 0000000..90e8d06 --- /dev/null +++ b/easy_my_coop_api/controllers/ping_controller.py @@ -0,0 +1,23 @@ +# Copyright 2020 Coop IT Easy SCRL fs +# Robin Keunen +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + + +from odoo.addons.base_rest.controllers import main +from odoo.http import route + + +class PingController(main.RestController): + _root_path = "/api/" + _collection_name = "emc.services" + _default_auth = "public" + + @route( + _root_path + "/ping", + methods=["GET"], + csrf=False, + ) + def test(self, _service_name, _id=None, **params): + return self._process_method( + _service_name, "ping", _id=_id, params=params + ) diff --git a/easy_my_coop_api/readme/CONTRIBUTORS.rst b/easy_my_coop_api/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000..f8672e6 --- /dev/null +++ b/easy_my_coop_api/readme/CONTRIBUTORS.rst @@ -0,0 +1,2 @@ +* Coop IT Easy SCRLfs +* Robin Keunen diff --git a/easy_my_coop_api/readme/DESCRIPTION.rst b/easy_my_coop_api/readme/DESCRIPTION.rst new file mode 100644 index 0000000..8a4c837 --- /dev/null +++ b/easy_my_coop_api/readme/DESCRIPTION.rst @@ -0,0 +1 @@ +Open Easy My Coop to the world: RESTful API. diff --git a/easy_my_coop_api/services/__init__.py b/easy_my_coop_api/services/__init__.py new file mode 100644 index 0000000..8d8dcb6 --- /dev/null +++ b/easy_my_coop_api/services/__init__.py @@ -0,0 +1 @@ +from . import ping_service diff --git a/easy_my_coop_api/services/ping_service.py b/easy_my_coop_api/services/ping_service.py new file mode 100644 index 0000000..823033e --- /dev/null +++ b/easy_my_coop_api/services/ping_service.py @@ -0,0 +1,36 @@ +# Copyright 2020 Coop IT Easy SCRL fs +# Robin Keunen +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + + +from odoo.addons.component.core import Component +from odoo import _ + + +class PingService(Component): + _inherit = "base.rest.service" + _name = "emc.services" + # _name = "ping.services" + _usage = "ping" # service_name + _collection = "emc.services" + _description = """ + Ping services (test the api) + """ + + def ping(self): + return {"message": _("Called ping on ping API")} + + def search(self): + return {"message": _("Called search on ping API")} + + def _validator_ping(self): + return {} + + def _validator_return_ping(self): + return {"message": {"type": "string"}} + + def _validator_search(self): + return {} + + def _validator_return_search(self): + return {"message": {"type": "string"}} diff --git a/easy_my_coop_api/static/description/index.html b/easy_my_coop_api/static/description/index.html new file mode 100644 index 0000000..bc922dd --- /dev/null +++ b/easy_my_coop_api/static/description/index.html @@ -0,0 +1,425 @@ + + + + + + +Easy My Coop API + + + +
+

Easy My Coop API

+ + +

Beta License: AGPL-3 coopiteasy/vertical-cooperative

+

Open Easy My Coop to the world: RESTful API.

+

Table of contents

+ +
+

Known issues / Roadmap

+

The API should generate and use an external id for records instead +of odoo’s generated id. It would make importing and export data as +well as migrating across versions easier.

+

One way would be to use uuid but the default BaseRESTController +routes would need to be rewritten: they only take integer as ids. +Another way is to define a sequence per model.

+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Coop IT Easy SCRLfs
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is part of the coopiteasy/vertical-cooperative project on GitHub.

+

You are welcome to contribute.

+
+
+
+ + diff --git a/easy_my_coop_api/tests/__init__.py b/easy_my_coop_api/tests/__init__.py new file mode 100644 index 0000000..99cc5b3 --- /dev/null +++ b/easy_my_coop_api/tests/__init__.py @@ -0,0 +1,2 @@ +from . import test_ping +from . import test_registry diff --git a/easy_my_coop_api/tests/common.py b/easy_my_coop_api/tests/common.py new file mode 100644 index 0000000..d755ead --- /dev/null +++ b/easy_my_coop_api/tests/common.py @@ -0,0 +1,28 @@ +# Copyright 2020 Coop IT Easy SCRL fs +# Robin Keunen +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + + +import requests +import odoo + +from odoo.addons.base_rest.tests.common import BaseRestCase + +HOST = "127.0.0.1" +PORT = odoo.tools.config["http_port"] + + +class BaseEMCRestCase(BaseRestCase): + def setUp(self): + super().setUp() + self.session = requests.Session() + + def http_get(self, url): + if url.startswith("/"): + url = "http://%s:%s%s" % (HOST, PORT, url) + return self.session.get(url) + + def http_post(self, url, data): + if url.startswith("/"): + url = "http://%s:%s%s" % (HOST, PORT, url) + return self.session.post(url, data=data) diff --git a/easy_my_coop_api/tests/test_ping.py b/easy_my_coop_api/tests/test_ping.py new file mode 100644 index 0000000..2389d22 --- /dev/null +++ b/easy_my_coop_api/tests/test_ping.py @@ -0,0 +1,38 @@ +# Copyright 2020 Coop IT Easy SCRL fs +# Robin Keunen +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + + +import json + +from odoo.addons.base_rest.controllers.main import _PseudoCollection +from odoo.addons.component.core import WorkContext + +from .common import BaseEMCRestCase + + +class TestPing(BaseEMCRestCase): + def test_ping_service(self): + collection = _PseudoCollection("emc.services", self.env) + emc_services_env = WorkContext( + model_name="rest.service.registration", collection=collection + ) + + service = emc_services_env.component(usage="ping") + result = service.ping() + + self.assertTrue("message" in result) + + def test_ping_route(self): + response = self.http_get("/api/ping/ping") + self.assertEquals(response.status_code, 200) + + content = json.loads(response.content) + self.assertTrue("message" in content) + + def test_search_route(self): + response = self.http_get("/api/ping") + self.assertEquals(response.status_code, 200) + + content = json.loads(response.content) + self.assertTrue("message" in content) diff --git a/easy_my_coop_api/tests/test_registry.py b/easy_my_coop_api/tests/test_registry.py new file mode 100644 index 0000000..c31df1a --- /dev/null +++ b/easy_my_coop_api/tests/test_registry.py @@ -0,0 +1,28 @@ +# Copyright 2020 Coop IT Easy SCRL fs +# Robin Keunen +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + + +import odoo + +from odoo.http import controllers_per_module +from odoo.addons.base_rest.tests.common import BaseRestCase + +from ..controllers.ping_controller import PingController + +HOST = "127.0.0.1" +PORT = odoo.tools.config["http_port"] + + +class TestControllerRegistry(BaseRestCase): + def test_controller_registry(self): + controllers = controllers_per_module["easy_my_coop_api"] + self.assertEqual(len(controllers), 1) + + self.assertIn( + ( + "odoo.addons.easy_my_coop_api.controllers.ping_controller.PingController", + PingController, + ), + controllers, + ) From 83aca966f314068820a91803953aa1da7e55c566 Mon Sep 17 00:00:00 2001 From: "robin.keunen" Date: Fri, 20 Mar 2020 18:10:50 +0100 Subject: [PATCH 02/12] [ADD] emc_api: get subscription request by id + get all requests --- easy_my_coop_api/services/__init__.py | 1 + easy_my_coop_api/services/schemas.py | 46 ++++++++++++ .../services/subscription_request_service.py | 75 +++++++++++++++++++ easy_my_coop_api/tests/__init__.py | 1 + easy_my_coop_api/tests/common.py | 28 +++++++ .../tests/test_subscription_requests.py | 64 ++++++++++++++++ 6 files changed, 215 insertions(+) create mode 100644 easy_my_coop_api/services/schemas.py create mode 100644 easy_my_coop_api/services/subscription_request_service.py create mode 100644 easy_my_coop_api/tests/test_subscription_requests.py diff --git a/easy_my_coop_api/services/__init__.py b/easy_my_coop_api/services/__init__.py index 8d8dcb6..d657c40 100644 --- a/easy_my_coop_api/services/__init__.py +++ b/easy_my_coop_api/services/__init__.py @@ -1 +1,2 @@ from . import ping_service +from . import subscription_request_service diff --git a/easy_my_coop_api/services/schemas.py b/easy_my_coop_api/services/schemas.py new file mode 100644 index 0000000..7b0f979 --- /dev/null +++ b/easy_my_coop_api/services/schemas.py @@ -0,0 +1,46 @@ +# Copyright 2020 Coop IT Easy SCRL fs +# Robin Keunen +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import _ +from odoo.fields import Date + + +def date_validator(field, value, error): + try: + Date.from_string(value) + except ValueError as e: + return error( + field, _("{} does not match format '%Y-%m-%d'".format(value)) + ) + + +S_SUBSCRIPTION_REQUEST = { + "id": {"type": "integer"}, + "name": {"type": "string"}, + "email": {"type": "string"}, + "date": {"type": "string"}, + "ordered_parts": {"type": "integer"}, + "share_product": { + "type": "dict", + "schema": {"id": {"type": "integer"}, "name": {"type": "string"}}, + }, + "address": { + "type": "dict", + "schema": { + "street": {"type": "string"}, + "zip_code": {"type": "string"}, + "city": {"type": "string"}, + "country": {"type": "string"}, + }, + }, + "lang": {"type": "string"}, +} + +S_SUBSCRIPTION_REQUEST_LIST = { + "count": {"type": "integer"}, + "rows": { + "type": "list", + "schema": {"type": "dict", "schema": S_SUBSCRIPTION_REQUEST}, + }, +} diff --git a/easy_my_coop_api/services/subscription_request_service.py b/easy_my_coop_api/services/subscription_request_service.py new file mode 100644 index 0000000..18e6df0 --- /dev/null +++ b/easy_my_coop_api/services/subscription_request_service.py @@ -0,0 +1,75 @@ +# Copyright 2019 Coop IT Easy SCRL fs +# Robin Keunen +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo.addons.component.core import Component +from odoo.fields import Date +from . import schemas + + +class SubscriptionRequestService(Component): + _inherit = "base.rest.service" + _name = "subscription.request.services" + _usage = "subscription_request" # service_name + _collection = "emc.services" + _description = """ + Subscription requests + """ + + def _to_dict(self, sr): + sr.ensure_one() + return { + "id": sr.id, + "name": sr.name, + "email": sr.email, + "date": Date.to_string(sr.date), + "ordered_parts": sr.ordered_parts, + "share_product": { + "id": sr.share_product_id.id, + "name": sr.share_product_id.name, + }, + "address": { + "street": sr.address, + "zip_code": sr.zip_code, + "city": sr.city, + "country": sr.country_id.code, + }, + "lang": sr.lang, + } + + def get(self, _id): + # fixme remove sudo + sr = self.env["subscription.request"].sudo().search([("id", "=", _id)]) + if sr: + return self._to_dict(sr) + else: + raise wrapJsonException( + NotFound(_("No subscription request for id %s") % _id) + ) + + return {"response": "Get called with message " + message} + + def search(self, params=None): + # fixme remove sudo + if params is None: + requests = self.env["subscription.request"].sudo().search([]) + else: + requests = self.env["subscription.request"].sudo().search([]) + + response = { + "count": len(requests), + "rows": [self._to_dict(sr) for sr in requests], + } + return response + + def _validator_get(self): + return {"_id": {"type": "integer"}} + + def _validator_return_get(self): + return schemas.S_SUBSCRIPTION_REQUEST + + def _validator_search(self): + return {} + + def _validator_return_search(self): + return schemas.S_SUBSCRIPTION_REQUEST_LIST diff --git a/easy_my_coop_api/tests/__init__.py b/easy_my_coop_api/tests/__init__.py index 99cc5b3..f831997 100644 --- a/easy_my_coop_api/tests/__init__.py +++ b/easy_my_coop_api/tests/__init__.py @@ -1,2 +1,3 @@ from . import test_ping from . import test_registry +from . import test_subscription_requests diff --git a/easy_my_coop_api/tests/common.py b/easy_my_coop_api/tests/common.py index d755ead..ebbbb74 100644 --- a/easy_my_coop_api/tests/common.py +++ b/easy_my_coop_api/tests/common.py @@ -4,6 +4,7 @@ import requests +import json import odoo from odoo.addons.base_rest.tests.common import BaseRestCase @@ -16,12 +17,39 @@ class BaseEMCRestCase(BaseRestCase): def setUp(self): super().setUp() self.session = requests.Session() + self.demo_request_1 = self.browse_ref( + "easy_my_coop.subscription_request_1_demo" + ) + self.demo_request_1_dict = { + "id": self.demo_request_1.id, + "name": "Manuel Dublues", + "email": "manuel@demo.net", + "date": "2020-02-23", + "ordered_parts": 3, + "share_product": { + "id": self.demo_request_1.share_product_id.id, + "name": "Part B - Worker", + }, + "address": { + "street": "schaerbeekstraat", + "zip_code": "1111", + "city": "Brussels", + "country": "BE", + }, + "lang": "en_US", + } def http_get(self, url): if url.startswith("/"): url = "http://%s:%s%s" % (HOST, PORT, url) return self.session.get(url) + def http_get_content(self, route): + response = self.http_get(route) + self.assertEquals(response.status_code, 200) + + return json.loads(response.content) + def http_post(self, url, data): if url.startswith("/"): url = "http://%s:%s%s" % (HOST, PORT, url) diff --git a/easy_my_coop_api/tests/test_subscription_requests.py b/easy_my_coop_api/tests/test_subscription_requests.py new file mode 100644 index 0000000..9fbd0e2 --- /dev/null +++ b/easy_my_coop_api/tests/test_subscription_requests.py @@ -0,0 +1,64 @@ +# Copyright 2020 Coop IT Easy SCRL fs +# Robin Keunen +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + + +import json + +from datetime import date, timedelta +from odoo.fields import Date +from odoo.addons.base_rest.controllers.main import _PseudoCollection +from odoo.addons.component.core import WorkContext + +from .common import BaseEMCRestCase + + +class TestSRController(BaseEMCRestCase): + def test_service(self): + # kept as example + # useful if you need to change data in database and check db type + collection = _PseudoCollection("emc.services", self.env) + emc_services_env = WorkContext( + model_name="rest.service.registration", collection=collection + ) + + service = emc_services_env.component(usage="subscription_request") + + result = service.get(self.demo_request_1.id) + self.assertEquals(self.demo_request_1_dict, result) + + def test_get_route(self): + id_ = self.demo_request_1.id + route = "/api/subscription_request/%s" % id_ + content = self.http_get_content(route) + self.assertEquals(self.demo_request_1_dict, content) + + @odoo.tools.mute_logger("odoo.addons.base_rest.http") + def test_route_get_returns_not_found(self): + route = "/api/subscription_request/%s" % "99999" + response = self.http_get(route) + self.assertEquals(response.status_code, 404) + + def test_route_get_string_returns_method_not_allowed(self): + route = "/api/subscription_request/%s" % "abc" + response = self.http_get(route) + self.assertEquals(response.status_code, 405) + + def test_route_search_all(self): + route = "/api/subscription_request" + response = self.http_get(route) + self.assertEquals(response.status_code, 200) + + content = json.loads(response.content) + + self.assertIn( + self.demo_request_1_dict, content["rows"] + ) + + # def test_search_route_from_date(self): + # from_ = Date.to_string(date.today() - timedelta(days=12)) + # response = self.http_get("/api/subscription_request?from=%s" % from_) + # self.assertEquals(response.status_code, 200) + # + # content = json.loads(response.content) + # self.assertTrue("message" in content) From 79d7cf5d72a439a7503cb4e6ad281a05e882990d Mon Sep 17 00:00:00 2001 From: "robin.keunen" Date: Thu, 2 Apr 2020 13:04:40 +0200 Subject: [PATCH 03/12] [ADD] emc_api: get subscription requests by date --- .../services/subscription_request_service.py | 36 +++++++--- .../tests/test_subscription_requests.py | 70 ++++++++++++++----- 2 files changed, 80 insertions(+), 26 deletions(-) diff --git a/easy_my_coop_api/services/subscription_request_service.py b/easy_my_coop_api/services/subscription_request_service.py index 18e6df0..2faa3f4 100644 --- a/easy_my_coop_api/services/subscription_request_service.py +++ b/easy_my_coop_api/services/subscription_request_service.py @@ -2,10 +2,16 @@ # Robin Keunen # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +import logging from odoo.addons.component.core import Component +from odoo.addons.base_rest.http import wrapJsonException +from werkzeug.exceptions import NotFound from odoo.fields import Date +from odoo import _ from . import schemas +_logger = logging.getLogger(__name__) + class SubscriptionRequestService(Component): _inherit = "base.rest.service" @@ -47,14 +53,19 @@ class SubscriptionRequestService(Component): NotFound(_("No subscription request for id %s") % _id) ) - return {"response": "Get called with message " + message} - - def search(self, params=None): + def search(self, date_from=None, date_to=None): # fixme remove sudo - if params is None: - requests = self.env["subscription.request"].sudo().search([]) - else: - requests = self.env["subscription.request"].sudo().search([]) + _logger.info("search from %s to %s" % (date_from, date_to)) + + domain = [] + if date_from: + date_from = Date.from_string(date_from) + domain.append(("date", ">=", date_from)) + if date_to: + date_to = Date.from_string(date_to) + domain.append(("date", "<=", date_to)) + + requests = self.env["subscription.request"].sudo().search(domain) response = { "count": len(requests), @@ -69,7 +80,16 @@ class SubscriptionRequestService(Component): return schemas.S_SUBSCRIPTION_REQUEST def _validator_search(self): - return {} + return { + "date_from": { + "type": "string", + "check_with": schemas.date_validator, + }, + "date_to": { + "type": "string", + "check_with": schemas.date_validator, + }, + } def _validator_return_search(self): return schemas.S_SUBSCRIPTION_REQUEST_LIST diff --git a/easy_my_coop_api/tests/test_subscription_requests.py b/easy_my_coop_api/tests/test_subscription_requests.py index 9fbd0e2..c2cf699 100644 --- a/easy_my_coop_api/tests/test_subscription_requests.py +++ b/easy_my_coop_api/tests/test_subscription_requests.py @@ -6,6 +6,7 @@ import json from datetime import date, timedelta +import odoo from odoo.fields import Date from odoo.addons.base_rest.controllers.main import _PseudoCollection from odoo.addons.component.core import WorkContext @@ -14,20 +15,33 @@ from .common import BaseEMCRestCase class TestSRController(BaseEMCRestCase): - def test_service(self): - # kept as example - # useful if you need to change data in database and check db type + def setUp(self): + super().setUp() collection = _PseudoCollection("emc.services", self.env) emc_services_env = WorkContext( model_name="rest.service.registration", collection=collection ) - service = emc_services_env.component(usage="subscription_request") + self.service = emc_services_env.component(usage="subscription_request") + + def test_service(self): + # kept as example + # useful if you need to change data in database and check db type - result = service.get(self.demo_request_1.id) + result = self.service.get(self.demo_request_1.id) self.assertEquals(self.demo_request_1_dict, result) - def test_get_route(self): + all_sr = self.service.search() + self.assertTrue(all_sr) + + sr_date = self.demo_request_1.date + date_from = Date.to_string(sr_date - timedelta(days=1)) + date_to = Date.to_string(sr_date + timedelta(days=1)) + + date_sr = self.service.search(date_from=date_from, date_to=date_to) + self.assertTrue(date_sr) + + def test_route_get(self): id_ = self.demo_request_1.id route = "/api/subscription_request/%s" % id_ content = self.http_get_content(route) @@ -46,19 +60,39 @@ class TestSRController(BaseEMCRestCase): def test_route_search_all(self): route = "/api/subscription_request" - response = self.http_get(route) - self.assertEquals(response.status_code, 200) + content = self.http_get_content(route) + self.assertIn(self.demo_request_1_dict, content["rows"]) - content = json.loads(response.content) + def test_route_search_by_date(self): + sr_date = self.demo_request_1.date + date_from = Date.to_string(sr_date - timedelta(days=1)) + date_to = Date.to_string(sr_date + timedelta(days=1)) - self.assertIn( - self.demo_request_1_dict, content["rows"] + route = "/api/subscription_request?date_from=%s" % date_from + content = self.http_get_content(route) + self.assertIn(self.demo_request_1_dict, content["rows"]) + + route = "/api/subscription_request?date_to=%s" % date_to + content = self.http_get_content(route) + self.assertIn(self.demo_request_1_dict, content["rows"]) + + route = "/api/subscription_request?date_from=%s&date_to=%s" % ( + date_from, + date_to, ) + content = self.http_get_content(route) + self.assertIn(self.demo_request_1_dict, content["rows"]) - # def test_search_route_from_date(self): - # from_ = Date.to_string(date.today() - timedelta(days=12)) - # response = self.http_get("/api/subscription_request?from=%s" % from_) - # self.assertEquals(response.status_code, 200) - # - # content = json.loads(response.content) - # self.assertTrue("message" in content) + route = "/api/subscription_request?date_from=%s" % "2300-01-01" + content = self.http_get_content(route) + self.assertEquals(content["count"], 0) + + route = "/api/subscription_request?date_to=%s" % "1900-01-01" + content = self.http_get_content(route) + self.assertEquals(content["count"], 0) + + @odoo.tools.mute_logger("odoo.addons.base_rest.http") + def test_route_search_acd_date_returns_bad_request(self): + route = "/api/subscription_request?date_from=%s" % "20200101" + response = self.http_get(route) + self.assertEquals(response.status_code, 400) From de087a3af121a08c462271bfe0edf8699470d766 Mon Sep 17 00:00:00 2001 From: "robin.keunen" Date: Fri, 3 Apr 2020 14:37:28 +0200 Subject: [PATCH 04/12] [ADD] emc_api: user authentification --- easy_my_coop_api/controllers/__init__.py | 2 +- .../{ping_controller.py => controllers.py} | 9 ++++---- easy_my_coop_api/services/ping_service.py | 9 ++++---- easy_my_coop_api/tests/common.py | 21 +++++++++++++++++++ easy_my_coop_api/tests/test_ping.py | 7 ++++--- easy_my_coop_api/tests/test_registry.py | 8 +++---- .../tests/test_subscription_requests.py | 6 ++++++ 7 files changed, 45 insertions(+), 17 deletions(-) rename easy_my_coop_api/controllers/{ping_controller.py => controllers.py} (69%) diff --git a/easy_my_coop_api/controllers/__init__.py b/easy_my_coop_api/controllers/__init__.py index e505936..e046e49 100644 --- a/easy_my_coop_api/controllers/__init__.py +++ b/easy_my_coop_api/controllers/__init__.py @@ -1 +1 @@ -from . import ping_controller +from . import controllers diff --git a/easy_my_coop_api/controllers/ping_controller.py b/easy_my_coop_api/controllers/controllers.py similarity index 69% rename from easy_my_coop_api/controllers/ping_controller.py rename to easy_my_coop_api/controllers/controllers.py index 90e8d06..58b7dba 100644 --- a/easy_my_coop_api/controllers/ping_controller.py +++ b/easy_my_coop_api/controllers/controllers.py @@ -7,17 +7,18 @@ from odoo.addons.base_rest.controllers import main from odoo.http import route -class PingController(main.RestController): +class UserController(main.RestController): _root_path = "/api/" _collection_name = "emc.services" - _default_auth = "public" + _default_auth = "user" @route( - _root_path + "/ping", + _root_path + "/test", methods=["GET"], + auth="public", csrf=False, ) def test(self, _service_name, _id=None, **params): return self._process_method( - _service_name, "ping", _id=_id, params=params + _service_name, "test", _id=_id, params=params ) diff --git a/easy_my_coop_api/services/ping_service.py b/easy_my_coop_api/services/ping_service.py index 823033e..738722b 100644 --- a/easy_my_coop_api/services/ping_service.py +++ b/easy_my_coop_api/services/ping_service.py @@ -10,23 +10,22 @@ from odoo import _ class PingService(Component): _inherit = "base.rest.service" _name = "emc.services" - # _name = "ping.services" _usage = "ping" # service_name _collection = "emc.services" _description = """ - Ping services (test the api) + Ping services (test the api) """ - def ping(self): + def test(self): return {"message": _("Called ping on ping API")} def search(self): return {"message": _("Called search on ping API")} - def _validator_ping(self): + def _validator_test(self): return {} - def _validator_return_ping(self): + def _validator_return_test(self): return {"message": {"type": "string"}} def _validator_search(self): diff --git a/easy_my_coop_api/tests/common.py b/easy_my_coop_api/tests/common.py index ebbbb74..188e018 100644 --- a/easy_my_coop_api/tests/common.py +++ b/easy_my_coop_api/tests/common.py @@ -6,6 +6,7 @@ import requests import json import odoo +from lxml import html from odoo.addons.base_rest.tests.common import BaseRestCase @@ -54,3 +55,23 @@ class BaseEMCRestCase(BaseRestCase): if url.startswith("/"): url = "http://%s:%s%s" % (HOST, PORT, url) return self.session.post(url, data=data) + + @staticmethod + def html_doc(response): + """Get an HTML LXML document.""" + return html.fromstring(response.content) + + def login(self, login, password): + url = "/web/login" + response = self.http_get(url) + self.assertEquals(response.status_code, 200) + + doc = self.html_doc(response) + token = doc.xpath("//input[@name='csrf_token']")[0].get("value") + + response = self.http_post( + url=url, + data={"login": login, "password": password, "csrf_token": token}, + ) + self.assertEquals(response.status_code, 200) + return response diff --git a/easy_my_coop_api/tests/test_ping.py b/easy_my_coop_api/tests/test_ping.py index 2389d22..0638e8c 100644 --- a/easy_my_coop_api/tests/test_ping.py +++ b/easy_my_coop_api/tests/test_ping.py @@ -12,25 +12,26 @@ from .common import BaseEMCRestCase class TestPing(BaseEMCRestCase): - def test_ping_service(self): + def test_public_service(self): collection = _PseudoCollection("emc.services", self.env) emc_services_env = WorkContext( model_name="rest.service.registration", collection=collection ) service = emc_services_env.component(usage="ping") - result = service.ping() + result = service.test() self.assertTrue("message" in result) def test_ping_route(self): - response = self.http_get("/api/ping/ping") + response = self.http_get("/api/ping/test") self.assertEquals(response.status_code, 200) content = json.loads(response.content) self.assertTrue("message" in content) def test_search_route(self): + self.login("manager-emc", "demo") response = self.http_get("/api/ping") self.assertEquals(response.status_code, 200) diff --git a/easy_my_coop_api/tests/test_registry.py b/easy_my_coop_api/tests/test_registry.py index c31df1a..eb2e0b7 100644 --- a/easy_my_coop_api/tests/test_registry.py +++ b/easy_my_coop_api/tests/test_registry.py @@ -8,7 +8,7 @@ import odoo from odoo.http import controllers_per_module from odoo.addons.base_rest.tests.common import BaseRestCase -from ..controllers.ping_controller import PingController +from ..controllers.controllers import UserController HOST = "127.0.0.1" PORT = odoo.tools.config["http_port"] @@ -18,11 +18,11 @@ class TestControllerRegistry(BaseRestCase): def test_controller_registry(self): controllers = controllers_per_module["easy_my_coop_api"] self.assertEqual(len(controllers), 1) - self.assertIn( ( - "odoo.addons.easy_my_coop_api.controllers.ping_controller.PingController", - PingController, + "odoo.addons.easy_my_coop_api" + ".controllers.controllers.UserController", + UserController, ), controllers, ) diff --git a/easy_my_coop_api/tests/test_subscription_requests.py b/easy_my_coop_api/tests/test_subscription_requests.py index c2cf699..9e92565 100644 --- a/easy_my_coop_api/tests/test_subscription_requests.py +++ b/easy_my_coop_api/tests/test_subscription_requests.py @@ -42,6 +42,7 @@ class TestSRController(BaseEMCRestCase): self.assertTrue(date_sr) def test_route_get(self): + self.login("manager-emc", "demo") id_ = self.demo_request_1.id route = "/api/subscription_request/%s" % id_ content = self.http_get_content(route) @@ -49,21 +50,25 @@ class TestSRController(BaseEMCRestCase): @odoo.tools.mute_logger("odoo.addons.base_rest.http") def test_route_get_returns_not_found(self): + self.login("manager-emc", "demo") route = "/api/subscription_request/%s" % "99999" response = self.http_get(route) self.assertEquals(response.status_code, 404) def test_route_get_string_returns_method_not_allowed(self): + self.login("manager-emc", "demo") route = "/api/subscription_request/%s" % "abc" response = self.http_get(route) self.assertEquals(response.status_code, 405) def test_route_search_all(self): + self.login("manager-emc", "demo") route = "/api/subscription_request" content = self.http_get_content(route) self.assertIn(self.demo_request_1_dict, content["rows"]) def test_route_search_by_date(self): + self.login("manager-emc", "demo") sr_date = self.demo_request_1.date date_from = Date.to_string(sr_date - timedelta(days=1)) date_to = Date.to_string(sr_date + timedelta(days=1)) @@ -93,6 +98,7 @@ class TestSRController(BaseEMCRestCase): @odoo.tools.mute_logger("odoo.addons.base_rest.http") def test_route_search_acd_date_returns_bad_request(self): + self.login("manager-emc", "demo") route = "/api/subscription_request?date_from=%s" % "20200101" response = self.http_get(route) self.assertEquals(response.status_code, 400) From 10bffeb8c9e56c3d2c9c027a2e64637367956269 Mon Sep 17 00:00:00 2001 From: "robin.keunen" Date: Fri, 3 Apr 2020 18:44:08 +0200 Subject: [PATCH 05/12] [ADD] emc_api: api_key authentification --- easy_my_coop_api/__manifest__.py | 5 +++- easy_my_coop_api/controllers/controllers.py | 2 +- easy_my_coop_api/tests/common.py | 23 +++++++++++++++---- easy_my_coop_api/tests/test_ping.py | 10 ++++---- .../tests/test_subscription_requests.py | 10 ++------ 5 files changed, 32 insertions(+), 18 deletions(-) diff --git a/easy_my_coop_api/__manifest__.py b/easy_my_coop_api/__manifest__.py index 54b4f4f..3c051aa 100644 --- a/easy_my_coop_api/__manifest__.py +++ b/easy_my_coop_api/__manifest__.py @@ -5,7 +5,10 @@ { "name": "Easy My Coop API", "version": "12.0.0.0.1", - "depends": ["base_rest", "easy_my_coop"], + "depends": [ + "base_rest", + "easy_my_coop", + ], # auth_api_key + running_env = dev "author": "Coop IT Easy SCRLfs", "category": "Cooperative management", "website": "www.coopiteasy.be", diff --git a/easy_my_coop_api/controllers/controllers.py b/easy_my_coop_api/controllers/controllers.py index 58b7dba..3c97de4 100644 --- a/easy_my_coop_api/controllers/controllers.py +++ b/easy_my_coop_api/controllers/controllers.py @@ -10,7 +10,7 @@ from odoo.http import route class UserController(main.RestController): _root_path = "/api/" _collection_name = "emc.services" - _default_auth = "user" + _default_auth = "api_key" @route( _root_path + "/test", diff --git a/easy_my_coop_api/tests/common.py b/easy_my_coop_api/tests/common.py index 188e018..c07f066 100644 --- a/easy_my_coop_api/tests/common.py +++ b/easy_my_coop_api/tests/common.py @@ -15,6 +15,15 @@ PORT = odoo.tools.config["http_port"] class BaseEMCRestCase(BaseRestCase): + @classmethod + def setUpClass(cls, *args, **kwargs): + super().setUpClass(*args, **kwargs) + cls.AuthApiKey = cls.env["auth.api.key"] + emc_manager = cls.env.ref("easy_my_coop.res_users_manager_emc_demo") + cls.api_key_test = cls.AuthApiKey.create( + {"name": "test-key", "key": "api-key", "user_id": emc_manager.id} + ) + def setUp(self): super().setUp() self.session = requests.Session() @@ -40,13 +49,19 @@ class BaseEMCRestCase(BaseRestCase): "lang": "en_US", } - def http_get(self, url): + def http_get(self, url, headers=None): + key_dict = {"API-KEY": "api-key"} + if headers: + headers.update(key_dict) + else: + headers = key_dict + if url.startswith("/"): url = "http://%s:%s%s" % (HOST, PORT, url) - return self.session.get(url) + return self.session.get(url, headers=headers) - def http_get_content(self, route): - response = self.http_get(route) + def http_get_content(self, route, headers=None): + response = self.http_get(route, headers=headers) self.assertEquals(response.status_code, 200) return json.loads(response.content) diff --git a/easy_my_coop_api/tests/test_ping.py b/easy_my_coop_api/tests/test_ping.py index 0638e8c..36c9433 100644 --- a/easy_my_coop_api/tests/test_ping.py +++ b/easy_my_coop_api/tests/test_ping.py @@ -4,11 +4,12 @@ import json +import requests from odoo.addons.base_rest.controllers.main import _PseudoCollection from odoo.addons.component.core import WorkContext -from .common import BaseEMCRestCase +from .common import BaseEMCRestCase, HOST, PORT class TestPing(BaseEMCRestCase): @@ -24,14 +25,15 @@ class TestPing(BaseEMCRestCase): self.assertTrue("message" in result) def test_ping_route(self): - response = self.http_get("/api/ping/test") + # public route + path = "/api/ping/test" + url = "http://%s:%s%s" % (HOST, PORT, path) + response = requests.get(url) self.assertEquals(response.status_code, 200) - content = json.loads(response.content) self.assertTrue("message" in content) def test_search_route(self): - self.login("manager-emc", "demo") response = self.http_get("/api/ping") self.assertEquals(response.status_code, 200) diff --git a/easy_my_coop_api/tests/test_subscription_requests.py b/easy_my_coop_api/tests/test_subscription_requests.py index 9e92565..a161b98 100644 --- a/easy_my_coop_api/tests/test_subscription_requests.py +++ b/easy_my_coop_api/tests/test_subscription_requests.py @@ -3,8 +3,6 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). -import json - from datetime import date, timedelta import odoo from odoo.fields import Date @@ -42,7 +40,6 @@ class TestSRController(BaseEMCRestCase): self.assertTrue(date_sr) def test_route_get(self): - self.login("manager-emc", "demo") id_ = self.demo_request_1.id route = "/api/subscription_request/%s" % id_ content = self.http_get_content(route) @@ -50,25 +47,21 @@ class TestSRController(BaseEMCRestCase): @odoo.tools.mute_logger("odoo.addons.base_rest.http") def test_route_get_returns_not_found(self): - self.login("manager-emc", "demo") route = "/api/subscription_request/%s" % "99999" response = self.http_get(route) self.assertEquals(response.status_code, 404) def test_route_get_string_returns_method_not_allowed(self): - self.login("manager-emc", "demo") route = "/api/subscription_request/%s" % "abc" response = self.http_get(route) self.assertEquals(response.status_code, 405) def test_route_search_all(self): - self.login("manager-emc", "demo") route = "/api/subscription_request" content = self.http_get_content(route) self.assertIn(self.demo_request_1_dict, content["rows"]) def test_route_search_by_date(self): - self.login("manager-emc", "demo") sr_date = self.demo_request_1.date date_from = Date.to_string(sr_date - timedelta(days=1)) date_to = Date.to_string(sr_date + timedelta(days=1)) @@ -98,7 +91,8 @@ class TestSRController(BaseEMCRestCase): @odoo.tools.mute_logger("odoo.addons.base_rest.http") def test_route_search_acd_date_returns_bad_request(self): - self.login("manager-emc", "demo") route = "/api/subscription_request?date_from=%s" % "20200101" response = self.http_get(route) self.assertEquals(response.status_code, 400) + + # def test_route_create(self): From 37b243327342a1dccf3d482a84b45c80355483e8 Mon Sep 17 00:00:00 2001 From: "robin.keunen" Date: Thu, 9 Apr 2020 16:06:44 +0200 Subject: [PATCH 06/12] [ADD] emc_api: create subscription request route --- easy_my_coop_api/__manifest__.py | 3 +- easy_my_coop_api/services/schemas.py | 51 ++++++++++++------- .../services/subscription_request_service.py | 40 ++++++++++++++- easy_my_coop_api/tests/common.py | 33 ++++++++---- .../tests/test_subscription_requests.py | 37 ++++++++++++-- oca_dependencies.txt | 4 +- 6 files changed, 133 insertions(+), 35 deletions(-) diff --git a/easy_my_coop_api/__manifest__.py b/easy_my_coop_api/__manifest__.py index 3c051aa..f42c066 100644 --- a/easy_my_coop_api/__manifest__.py +++ b/easy_my_coop_api/__manifest__.py @@ -8,7 +8,8 @@ "depends": [ "base_rest", "easy_my_coop", - ], # auth_api_key + running_env = dev + "auth_api_key", # todo conf running_env = dev + ], "author": "Coop IT Easy SCRLfs", "category": "Cooperative management", "website": "www.coopiteasy.be", diff --git a/easy_my_coop_api/services/schemas.py b/easy_my_coop_api/services/schemas.py index 7b0f979..aed7533 100644 --- a/easy_my_coop_api/services/schemas.py +++ b/easy_my_coop_api/services/schemas.py @@ -15,32 +15,49 @@ def date_validator(field, value, error): ) -S_SUBSCRIPTION_REQUEST = { - "id": {"type": "integer"}, - "name": {"type": "string"}, - "email": {"type": "string"}, - "date": {"type": "string"}, - "ordered_parts": {"type": "integer"}, - "share_product": { - "type": "dict", - "schema": {"id": {"type": "integer"}, "name": {"type": "string"}}, - }, +# todo consistency: S_SR_GET, S_SR_RETURN_GET, S_SR_POST ... + + +S_SUBSCRIPTION_REQUEST_BASE = { + "name": {"type": "string", "required": True, "empty": False}, + "email": {"type": "string", "required": True, "empty": False}, + "ordered_parts": {"type": "integer", "required": True}, "address": { "type": "dict", "schema": { - "street": {"type": "string"}, - "zip_code": {"type": "string"}, - "city": {"type": "string"}, - "country": {"type": "string"}, + "street": {"type": "string", "required": True, "empty": False}, + "zip_code": {"type": "string", "required": True, "empty": False}, + "city": {"type": "string", "required": True, "empty": False}, + "country": {"type": "string", "required": True, "empty": False}, + }, + }, + "lang": {"type": "string", "required": True, "empty": False}, +} + +S_SUBSCRIPTION_REQUEST_GET = { + **S_SUBSCRIPTION_REQUEST_BASE, + **{ + "id": {"type": "integer", "required": True}, + "date": {"type": "string", "required": True, "empty": False}, + "share_product": { + "type": "dict", + "schema": { + "id": {"type": "integer", "required": True}, + "name": {"type": "string", "required": True, "empty": False}, + }, }, }, - "lang": {"type": "string"}, +} + +S_SUBSCRIPTION_REQUEST_CREATE = { + **S_SUBSCRIPTION_REQUEST_BASE, + **{"share_product": {"type": "integer", "required": True}}, } S_SUBSCRIPTION_REQUEST_LIST = { - "count": {"type": "integer"}, + "count": {"type": "integer", "required": True}, "rows": { "type": "list", - "schema": {"type": "dict", "schema": S_SUBSCRIPTION_REQUEST}, + "schema": {"type": "dict", "schema": S_SUBSCRIPTION_REQUEST_GET}, }, } diff --git a/easy_my_coop_api/services/subscription_request_service.py b/easy_my_coop_api/services/subscription_request_service.py index 2faa3f4..49d9ca7 100644 --- a/easy_my_coop_api/services/subscription_request_service.py +++ b/easy_my_coop_api/services/subscription_request_service.py @@ -5,7 +5,7 @@ import logging from odoo.addons.component.core import Component from odoo.addons.base_rest.http import wrapJsonException -from werkzeug.exceptions import NotFound +from werkzeug.exceptions import NotFound, BadRequest from odoo.fields import Date from odoo import _ from . import schemas @@ -43,6 +43,31 @@ class SubscriptionRequestService(Component): "lang": sr.lang, } + def _get_country(self, code): + country = self.env["res.country"].search([("code", "=", code)]) + if country: + return country + else: + raise wrapJsonException( + BadRequest(_("No country for isocode %s") % code) + ) + + def _prepare_create(self, params): + address = params["address"] + country = self._get_country(address["country"]) + + return { + "name": params["name"], + "email": params["email"], + "ordered_parts": params["ordered_parts"], + "share_product_id": params["share_product"], + "address": address["street"], + "zip_code": address["zip_code"], + "city": address["city"], + "country_id": country.id, + "lang": params["lang"], + } + def get(self, _id): # fixme remove sudo sr = self.env["subscription.request"].sudo().search([("id", "=", _id)]) @@ -73,11 +98,16 @@ class SubscriptionRequestService(Component): } return response + def create(self, **params): + params = self._prepare_create(params) + sr = self.env["subscription.request"].create(params) + return self._to_dict(sr) + def _validator_get(self): return {"_id": {"type": "integer"}} def _validator_return_get(self): - return schemas.S_SUBSCRIPTION_REQUEST + return schemas.S_SUBSCRIPTION_REQUEST_GET def _validator_search(self): return { @@ -93,3 +123,9 @@ class SubscriptionRequestService(Component): def _validator_return_search(self): return schemas.S_SUBSCRIPTION_REQUEST_LIST + + def _validator_create(self): + return schemas.S_SUBSCRIPTION_REQUEST_CREATE + + def _validator_return_create(self): + return schemas.S_SUBSCRIPTION_REQUEST_GET diff --git a/easy_my_coop_api/tests/common.py b/easy_my_coop_api/tests/common.py index c07f066..9f65a16 100644 --- a/easy_my_coop_api/tests/common.py +++ b/easy_my_coop_api/tests/common.py @@ -8,12 +8,22 @@ import json import odoo from lxml import html +from odoo.fields import Date from odoo.addons.base_rest.tests.common import BaseRestCase HOST = "127.0.0.1" PORT = odoo.tools.config["http_port"] +def _add_api_key(headers): + key_dict = {"API-KEY": "api-key"} + if headers: + headers.update(key_dict) + else: + headers = key_dict + return headers + + class BaseEMCRestCase(BaseRestCase): @classmethod def setUpClass(cls, *args, **kwargs): @@ -30,15 +40,18 @@ class BaseEMCRestCase(BaseRestCase): self.demo_request_1 = self.browse_ref( "easy_my_coop.subscription_request_1_demo" ) + self.demo_share_product = self.demo_request_1.share_product_id + + date = Date.to_string(self.demo_request_1.date) self.demo_request_1_dict = { "id": self.demo_request_1.id, "name": "Manuel Dublues", "email": "manuel@demo.net", - "date": "2020-02-23", + "date": date, "ordered_parts": 3, "share_product": { - "id": self.demo_request_1.share_product_id.id, - "name": "Part B - Worker", + "id": self.demo_share_product.id, + "name": self.demo_share_product.name, }, "address": { "street": "schaerbeekstraat", @@ -50,14 +63,10 @@ class BaseEMCRestCase(BaseRestCase): } def http_get(self, url, headers=None): - key_dict = {"API-KEY": "api-key"} - if headers: - headers.update(key_dict) - else: - headers = key_dict - + headers = _add_api_key(headers) if url.startswith("/"): url = "http://%s:%s%s" % (HOST, PORT, url) + return self.session.get(url, headers=headers) def http_get_content(self, route, headers=None): @@ -66,10 +75,12 @@ class BaseEMCRestCase(BaseRestCase): return json.loads(response.content) - def http_post(self, url, data): + def http_post(self, url, data, headers=None): + headers = _add_api_key(headers) if url.startswith("/"): url = "http://%s:%s%s" % (HOST, PORT, url) - return self.session.post(url, data=data) + + return self.session.post(url, json=data, headers=headers) @staticmethod def html_doc(response): diff --git a/easy_my_coop_api/tests/test_subscription_requests.py b/easy_my_coop_api/tests/test_subscription_requests.py index a161b98..eef6b96 100644 --- a/easy_my_coop_api/tests/test_subscription_requests.py +++ b/easy_my_coop_api/tests/test_subscription_requests.py @@ -2,8 +2,8 @@ # Robin Keunen # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). - -from datetime import date, timedelta +import json +from datetime import timedelta import odoo from odoo.fields import Date from odoo.addons.base_rest.controllers.main import _PseudoCollection @@ -95,4 +95,35 @@ class TestSRController(BaseEMCRestCase): response = self.http_get(route) self.assertEquals(response.status_code, 400) - # def test_route_create(self): + def test_route_create(self): + url = "/api/subscription_request" + data = { + "name": "Lisa des Danses", + "email": "lisa@desdanses.be", + "ordered_parts": 3, + "share_product": self.demo_share_product.id, + "address": { + "street": "schaerbeekstraat", + "zip_code": "1111", + "city": "Brussels", + "country": "BE", + }, + "lang": "en_US", + } + + response = self.http_post(url, data=data) + self.assertEquals(response.status_code, 200) + content = json.loads(response.content) + + content.pop("id") # can't know id in advance + expected = { + **data, + **{ + "date": Date.to_string(Date.today()), + "share_product": { + "id": self.demo_share_product.id, + "name": self.demo_share_product.name, + }, + }, + } + self.assertEquals(expected, content) diff --git a/oca_dependencies.txt b/oca_dependencies.txt index 4a5c828..b9e8e82 100644 --- a/oca_dependencies.txt +++ b/oca_dependencies.txt @@ -1,5 +1,7 @@ # List the OCA project dependencies, one per line # Add a repository url and branch if you need a forked version -partner-contact addons https://github.com/coopiteasy/addons +partner-contact +rest-framework +server-auth From cd6d3f2fb31b4b594ee5645e76800ecd1a41e926 Mon Sep 17 00:00:00 2001 From: "robin.keunen" Date: Thu, 9 Apr 2020 17:00:53 +0200 Subject: [PATCH 07/12] [ADD] emc_api: update subscription request route --- easy_my_coop_api/services/schemas.py | 20 ++++ .../services/subscription_request_service.py | 103 ++++++++++++------ easy_my_coop_api/tests/common.py | 1 + .../tests/test_subscription_requests.py | 13 +++ 4 files changed, 105 insertions(+), 32 deletions(-) diff --git a/easy_my_coop_api/services/schemas.py b/easy_my_coop_api/services/schemas.py index aed7533..ba091ec 100644 --- a/easy_my_coop_api/services/schemas.py +++ b/easy_my_coop_api/services/schemas.py @@ -16,6 +16,7 @@ def date_validator(field, value, error): # todo consistency: S_SR_GET, S_SR_RETURN_GET, S_SR_POST ... +# and denormalize dict rather than updating them S_SUBSCRIPTION_REQUEST_BASE = { @@ -39,6 +40,7 @@ S_SUBSCRIPTION_REQUEST_GET = { **{ "id": {"type": "integer", "required": True}, "date": {"type": "string", "required": True, "empty": False}, + "state": {"type": "string", "required": True, "empty": False}, "share_product": { "type": "dict", "schema": { @@ -54,6 +56,24 @@ S_SUBSCRIPTION_REQUEST_CREATE = { **{"share_product": {"type": "integer", "required": True}}, } +S_SUBSCRIPTION_REQUEST_UPDATE = { + "name": {"type": "string"}, + "email": {"type": "string"}, + "ordered_parts": {"type": "integer"}, + "state": {"type": "string"}, + "address": { + "type": "dict", + "schema": { + "street": {"type": "string"}, + "zip_code": {"type": "string"}, + "city": {"type": "string"}, + "country": {"type": "string"}, + }, + }, + "lang": {"type": "string"}, + "share_product": {"type": "integer"}, +} + S_SUBSCRIPTION_REQUEST_LIST = { "count": {"type": "integer", "required": True}, "rows": { diff --git a/easy_my_coop_api/services/subscription_request_service.py b/easy_my_coop_api/services/subscription_request_service.py index 49d9ca7..cc7133c 100644 --- a/easy_my_coop_api/services/subscription_request_service.py +++ b/easy_my_coop_api/services/subscription_request_service.py @@ -22,12 +22,56 @@ class SubscriptionRequestService(Component): Subscription requests """ + def get(self, _id): + sr = self.env["subscription.request"].browse(_id) + if sr: + return self._to_dict(sr) + else: + raise wrapJsonException( + NotFound(_("No subscription request for id %s") % _id) + ) + + def search(self, date_from=None, date_to=None): + _logger.info("search from %s to %s" % (date_from, date_to)) + + domain = [] + if date_from: + date_from = Date.from_string(date_from) + domain.append(("date", ">=", date_from)) + if date_to: + date_to = Date.from_string(date_to) + domain.append(("date", "<=", date_to)) + + requests = self.env["subscription.request"].search(domain) + + response = { + "count": len(requests), + "rows": [self._to_dict(sr) for sr in requests], + } + return response + + def create(self, **params): + params = self._prepare_create(params) + sr = self.env["subscription.request"].create(params) + return self._to_dict(sr) + + def update(self, _id, **params): + params = self._prepare_update(params) + sr = self.env["subscription.request"].browse(_id) + if not sr: + raise wrapJsonException( + NotFound(_("No subscription request for id %s") % _id) + ) + sr.write(params) + return self._to_dict(sr) + def _to_dict(self, sr): sr.ensure_one() return { "id": sr.id, "name": sr.name, "email": sr.email, + "state": sr.state, "date": Date.to_string(sr.date), "ordered_parts": sr.ordered_parts, "share_product": { @@ -68,40 +112,29 @@ class SubscriptionRequestService(Component): "lang": params["lang"], } - def get(self, _id): - # fixme remove sudo - sr = self.env["subscription.request"].sudo().search([("id", "=", _id)]) - if sr: - return self._to_dict(sr) + def _prepare_update(self, params): + if "address" in params: + address = params["address"] + if "country" in address: + country = self._get_country(address["country"]).id + address["country"] = country else: - raise wrapJsonException( - NotFound(_("No subscription request for id %s") % _id) - ) - - def search(self, date_from=None, date_to=None): - # fixme remove sudo - _logger.info("search from %s to %s" % (date_from, date_to)) - - domain = [] - if date_from: - date_from = Date.from_string(date_from) - domain.append(("date", ">=", date_from)) - if date_to: - date_to = Date.from_string(date_to) - domain.append(("date", "<=", date_to)) - - requests = self.env["subscription.request"].sudo().search(domain) - - response = { - "count": len(requests), - "rows": [self._to_dict(sr) for sr in requests], + address = {} + + params = { + "name": params.get("name"), + "email": params.get("email"), + "state": params.get("state"), + "ordered_parts": params.get("ordered_parts"), + "share_product_id": params.get("share_product"), + "address": address.get("street"), + "zip_code": address.get("zip_code"), + "city": address.get("city"), + "country_id": address.get("country"), + "lang": params.get("lang"), } - return response - - def create(self, **params): - params = self._prepare_create(params) - sr = self.env["subscription.request"].create(params) - return self._to_dict(sr) + params = {k: v for k, v in params.items() if v is not None} + return params def _validator_get(self): return {"_id": {"type": "integer"}} @@ -129,3 +162,9 @@ class SubscriptionRequestService(Component): def _validator_return_create(self): return schemas.S_SUBSCRIPTION_REQUEST_GET + + def _validator_update(self): + return schemas.S_SUBSCRIPTION_REQUEST_UPDATE + + def _validator_return_update(self): + return schemas.S_SUBSCRIPTION_REQUEST_GET diff --git a/easy_my_coop_api/tests/common.py b/easy_my_coop_api/tests/common.py index 9f65a16..39e7a6f 100644 --- a/easy_my_coop_api/tests/common.py +++ b/easy_my_coop_api/tests/common.py @@ -48,6 +48,7 @@ class BaseEMCRestCase(BaseRestCase): "name": "Manuel Dublues", "email": "manuel@demo.net", "date": date, + "state": "draft", "ordered_parts": 3, "share_product": { "id": self.demo_share_product.id, diff --git a/easy_my_coop_api/tests/test_subscription_requests.py b/easy_my_coop_api/tests/test_subscription_requests.py index eef6b96..df97d3e 100644 --- a/easy_my_coop_api/tests/test_subscription_requests.py +++ b/easy_my_coop_api/tests/test_subscription_requests.py @@ -120,6 +120,7 @@ class TestSRController(BaseEMCRestCase): **data, **{ "date": Date.to_string(Date.today()), + "state": "draft", "share_product": { "id": self.demo_share_product.id, "name": self.demo_share_product.name, @@ -127,3 +128,15 @@ class TestSRController(BaseEMCRestCase): }, } self.assertEquals(expected, content) + + def test_route_update(self): + url = "/api/subscription_request/%s" % self.demo_request_1.id + data = {"state": "done"} + + response = self.http_post(url, data=data) + self.assertEquals(response.status_code, 200) + content = json.loads(response.content) + + expected = self.demo_request_1_dict + expected["state"] = "done" + self.assertEquals(expected, content) From db3eb937711b3736f822b968e3ce5bff54aaea80 Mon Sep 17 00:00:00 2001 From: "robin.keunen" Date: Wed, 15 Apr 2020 13:02:39 +0200 Subject: [PATCH 08/12] [REF] emc_api: method order,denormalize schemas --- easy_my_coop_api/controllers/controllers.py | 4 +- easy_my_coop_api/readme/ROADMAP.rst | 7 ++ easy_my_coop_api/services/ping_service.py | 6 +- easy_my_coop_api/services/schemas.py | 64 +++++++++++-------- .../services/subscription_request_service.py | 24 +++---- 5 files changed, 58 insertions(+), 47 deletions(-) create mode 100644 easy_my_coop_api/readme/ROADMAP.rst diff --git a/easy_my_coop_api/controllers/controllers.py b/easy_my_coop_api/controllers/controllers.py index 3c97de4..6567255 100644 --- a/easy_my_coop_api/controllers/controllers.py +++ b/easy_my_coop_api/controllers/controllers.py @@ -18,7 +18,7 @@ class UserController(main.RestController): auth="public", csrf=False, ) - def test(self, _service_name, _id=None, **params): + def test(self, _service_name): return self._process_method( - _service_name, "test", _id=_id, params=params + _service_name, "test", _id=None, params=None, ) diff --git a/easy_my_coop_api/readme/ROADMAP.rst b/easy_my_coop_api/readme/ROADMAP.rst new file mode 100644 index 0000000..5698982 --- /dev/null +++ b/easy_my_coop_api/readme/ROADMAP.rst @@ -0,0 +1,7 @@ +The API should generate and use an external id for records instead +of odoo's generated id. It would make importing and export data as +well as migrating across versions easier. + +One way would be to use uuid but the default BaseRESTController +routes would need to be rewritten: they only take integer as ids. +Another way is to define a sequence per model. diff --git a/easy_my_coop_api/services/ping_service.py b/easy_my_coop_api/services/ping_service.py index 738722b..ce63f1c 100644 --- a/easy_my_coop_api/services/ping_service.py +++ b/easy_my_coop_api/services/ping_service.py @@ -16,12 +16,12 @@ class PingService(Component): Ping services (test the api) """ - def test(self): - return {"message": _("Called ping on ping API")} - def search(self): return {"message": _("Called search on ping API")} + def test(self): + return {"message": _("Called ping on ping API")} + def _validator_test(self): return {} diff --git a/easy_my_coop_api/services/schemas.py b/easy_my_coop_api/services/schemas.py index ba091ec..f20dab7 100644 --- a/easy_my_coop_api/services/schemas.py +++ b/easy_my_coop_api/services/schemas.py @@ -15,14 +15,22 @@ def date_validator(field, value, error): ) -# todo consistency: S_SR_GET, S_SR_RETURN_GET, S_SR_POST ... -# and denormalize dict rather than updating them +S_SUBSCRIPTION_REQUEST_GET = {"_id": {"type": "integer"}} - -S_SUBSCRIPTION_REQUEST_BASE = { - "name": {"type": "string", "required": True, "empty": False}, +S_SUBSCRIPTION_REQUEST_RETURN_GET = { + "id": {"type": "integer", "required": True}, "email": {"type": "string", "required": True, "empty": False}, + "name": {"type": "string", "required": True, "empty": False}, + "date": {"type": "string", "required": True, "empty": False}, + "state": {"type": "string", "required": True, "empty": False}, "ordered_parts": {"type": "integer", "required": True}, + "share_product": { + "type": "dict", + "schema": { + "id": {"type": "integer", "required": True}, + "name": {"type": "string", "required": True, "empty": False}, + }, + }, "address": { "type": "dict", "schema": { @@ -35,25 +43,37 @@ S_SUBSCRIPTION_REQUEST_BASE = { "lang": {"type": "string", "required": True, "empty": False}, } -S_SUBSCRIPTION_REQUEST_GET = { - **S_SUBSCRIPTION_REQUEST_BASE, - **{ - "id": {"type": "integer", "required": True}, - "date": {"type": "string", "required": True, "empty": False}, - "state": {"type": "string", "required": True, "empty": False}, - "share_product": { +S_SUBSCRIPTION_REQUEST_SEARCH = { + "date_from": {"type": "string", "check_with": date_validator}, + "date_to": {"type": "string", "check_with": date_validator}, +} + +S_SUBSCRIPTION_REQUEST_RETURN_SEARCH = { + "count": {"type": "integer", "required": True}, + "rows": { + "type": "list", + "schema": { "type": "dict", - "schema": { - "id": {"type": "integer", "required": True}, - "name": {"type": "string", "required": True, "empty": False}, - }, + "schema": S_SUBSCRIPTION_REQUEST_RETURN_GET, }, }, } S_SUBSCRIPTION_REQUEST_CREATE = { - **S_SUBSCRIPTION_REQUEST_BASE, - **{"share_product": {"type": "integer", "required": True}}, + "name": {"type": "string", "required": True, "empty": False}, + "email": {"type": "string", "required": True, "empty": False}, + "ordered_parts": {"type": "integer", "required": True}, + "share_product": {"type": "integer", "required": True}, + "address": { + "type": "dict", + "schema": { + "street": {"type": "string", "required": True, "empty": False}, + "zip_code": {"type": "string", "required": True, "empty": False}, + "city": {"type": "string", "required": True, "empty": False}, + "country": {"type": "string", "required": True, "empty": False}, + }, + }, + "lang": {"type": "string", "required": True, "empty": False}, } S_SUBSCRIPTION_REQUEST_UPDATE = { @@ -73,11 +93,3 @@ S_SUBSCRIPTION_REQUEST_UPDATE = { "lang": {"type": "string"}, "share_product": {"type": "integer"}, } - -S_SUBSCRIPTION_REQUEST_LIST = { - "count": {"type": "integer", "required": True}, - "rows": { - "type": "list", - "schema": {"type": "dict", "schema": S_SUBSCRIPTION_REQUEST_GET}, - }, -} diff --git a/easy_my_coop_api/services/subscription_request_service.py b/easy_my_coop_api/services/subscription_request_service.py index cc7133c..7017cbf 100644 --- a/easy_my_coop_api/services/subscription_request_service.py +++ b/easy_my_coop_api/services/subscription_request_service.py @@ -16,7 +16,8 @@ _logger = logging.getLogger(__name__) class SubscriptionRequestService(Component): _inherit = "base.rest.service" _name = "subscription.request.services" - _usage = "subscription_request" # service_name + # service_name todo subscription-request + _usage = "subscription_request" _collection = "emc.services" _description = """ Subscription requests @@ -137,34 +138,25 @@ class SubscriptionRequestService(Component): return params def _validator_get(self): - return {"_id": {"type": "integer"}} + return schemas.S_SUBSCRIPTION_REQUEST_GET def _validator_return_get(self): - return schemas.S_SUBSCRIPTION_REQUEST_GET + return schemas.S_SUBSCRIPTION_REQUEST_RETURN_GET def _validator_search(self): - return { - "date_from": { - "type": "string", - "check_with": schemas.date_validator, - }, - "date_to": { - "type": "string", - "check_with": schemas.date_validator, - }, - } + return schemas.S_SUBSCRIPTION_REQUEST_SEARCH def _validator_return_search(self): - return schemas.S_SUBSCRIPTION_REQUEST_LIST + return schemas.S_SUBSCRIPTION_REQUEST_RETURN_SEARCH def _validator_create(self): return schemas.S_SUBSCRIPTION_REQUEST_CREATE def _validator_return_create(self): - return schemas.S_SUBSCRIPTION_REQUEST_GET + return schemas.S_SUBSCRIPTION_REQUEST_RETURN_GET def _validator_update(self): return schemas.S_SUBSCRIPTION_REQUEST_UPDATE def _validator_return_update(self): - return schemas.S_SUBSCRIPTION_REQUEST_GET + return schemas.S_SUBSCRIPTION_REQUEST_RETURN_GET From 47bee476932d1215cfe4585efe03d4a7ae89a09d Mon Sep 17 00:00:00 2001 From: "robin.keunen" Date: Mon, 25 May 2020 13:05:33 +0200 Subject: [PATCH 09/12] [FIX] emc_api: decode response from bytes to utf-8 --- easy_my_coop_api/services/subscription_request_service.py | 5 +++-- easy_my_coop_api/tests/common.py | 4 ++-- easy_my_coop_api/tests/test_ping.py | 4 ++-- easy_my_coop_api/tests/test_subscription_requests.py | 4 ++-- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/easy_my_coop_api/services/subscription_request_service.py b/easy_my_coop_api/services/subscription_request_service.py index 7017cbf..5331643 100644 --- a/easy_my_coop_api/services/subscription_request_service.py +++ b/easy_my_coop_api/services/subscription_request_service.py @@ -51,7 +51,8 @@ class SubscriptionRequestService(Component): } return response - def create(self, **params): + def create(self, **params): # pylint: disable=method-required-super + params = self._prepare_create(params) sr = self.env["subscription.request"].create(params) return self._to_dict(sr) @@ -69,7 +70,7 @@ class SubscriptionRequestService(Component): def _to_dict(self, sr): sr.ensure_one() return { - "id": sr.id, + "id": sr.id, "name": sr.name, "email": sr.email, "state": sr.state, diff --git a/easy_my_coop_api/tests/common.py b/easy_my_coop_api/tests/common.py index 39e7a6f..7a7222d 100644 --- a/easy_my_coop_api/tests/common.py +++ b/easy_my_coop_api/tests/common.py @@ -73,8 +73,8 @@ class BaseEMCRestCase(BaseRestCase): def http_get_content(self, route, headers=None): response = self.http_get(route, headers=headers) self.assertEquals(response.status_code, 200) - - return json.loads(response.content) + content = response.content.decode("utf-8") + return json.loads(content) def http_post(self, url, data, headers=None): headers = _add_api_key(headers) diff --git a/easy_my_coop_api/tests/test_ping.py b/easy_my_coop_api/tests/test_ping.py index 36c9433..dcfbe2a 100644 --- a/easy_my_coop_api/tests/test_ping.py +++ b/easy_my_coop_api/tests/test_ping.py @@ -30,12 +30,12 @@ class TestPing(BaseEMCRestCase): url = "http://%s:%s%s" % (HOST, PORT, path) response = requests.get(url) self.assertEquals(response.status_code, 200) - content = json.loads(response.content) + content = json.loads(response.content.decode("utf-8")) self.assertTrue("message" in content) def test_search_route(self): response = self.http_get("/api/ping") self.assertEquals(response.status_code, 200) - content = json.loads(response.content) + content = json.loads(response.content.decode("utf-8")) self.assertTrue("message" in content) diff --git a/easy_my_coop_api/tests/test_subscription_requests.py b/easy_my_coop_api/tests/test_subscription_requests.py index df97d3e..71643b8 100644 --- a/easy_my_coop_api/tests/test_subscription_requests.py +++ b/easy_my_coop_api/tests/test_subscription_requests.py @@ -113,7 +113,7 @@ class TestSRController(BaseEMCRestCase): response = self.http_post(url, data=data) self.assertEquals(response.status_code, 200) - content = json.loads(response.content) + content = json.loads(response.content.decode("utf-8")) content.pop("id") # can't know id in advance expected = { @@ -135,7 +135,7 @@ class TestSRController(BaseEMCRestCase): response = self.http_post(url, data=data) self.assertEquals(response.status_code, 200) - content = json.loads(response.content) + content = json.loads(response.content.decode("utf-8")) expected = self.demo_request_1_dict expected["state"] = "done" From 5b205dc8dd5ffc82c6fd450cd45dacdb54ad3c0e Mon Sep 17 00:00:00 2001 From: "robin.keunen" Date: Tue, 26 May 2020 10:35:57 +0200 Subject: [PATCH 10/12] [REF] emc_api: pass lint --- .isort.cfg | 2 +- easy_my_coop_api/services/ping_service.py | 4 +++- .../services/subscription_request_service.py | 19 ++++++++++++------- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/.isort.cfg b/.isort.cfg index 06551a7..1517121 100644 --- a/.isort.cfg +++ b/.isort.cfg @@ -9,4 +9,4 @@ line_length=79 known_odoo=odoo known_odoo_addons=odoo.addons sections=FUTURE,STDLIB,THIRDPARTY,ODOO,ODOO_ADDONS,FIRSTPARTY,LOCALFOLDER -known_third_party=addons,cStringIO,openerp,requests,setuptools,werkzeug,xlsxwriter +known_third_party=addons,cStringIO,lxml,openerp,requests,setuptools,werkzeug,xlsxwriter diff --git a/easy_my_coop_api/services/ping_service.py b/easy_my_coop_api/services/ping_service.py index ce63f1c..49b37d1 100644 --- a/easy_my_coop_api/services/ping_service.py +++ b/easy_my_coop_api/services/ping_service.py @@ -1,11 +1,13 @@ # Copyright 2020 Coop IT Easy SCRL fs # Robin Keunen # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +# pylint: disable=consider-merging-classes-inherited -from odoo.addons.component.core import Component from odoo import _ +from odoo.addons.component.core import Component + class PingService(Component): _inherit = "base.rest.service" diff --git a/easy_my_coop_api/services/subscription_request_service.py b/easy_my_coop_api/services/subscription_request_service.py index 5331643..1a462c5 100644 --- a/easy_my_coop_api/services/subscription_request_service.py +++ b/easy_my_coop_api/services/subscription_request_service.py @@ -1,13 +1,18 @@ # Copyright 2019 Coop IT Easy SCRL fs # Robin Keunen # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +# pylint: disable=consider-merging-classes-inherited import logging -from odoo.addons.component.core import Component -from odoo.addons.base_rest.http import wrapJsonException -from werkzeug.exceptions import NotFound, BadRequest -from odoo.fields import Date + +from werkzeug.exceptions import BadRequest, NotFound + from odoo import _ +from odoo.fields import Date + +from odoo.addons.base_rest.http import wrapJsonException +from odoo.addons.component.core import Component + from . import schemas _logger = logging.getLogger(__name__) @@ -20,7 +25,7 @@ class SubscriptionRequestService(Component): _usage = "subscription_request" _collection = "emc.services" _description = """ - Subscription requests + Subscription requests """ def get(self, _id): @@ -33,7 +38,7 @@ class SubscriptionRequestService(Component): ) def search(self, date_from=None, date_to=None): - _logger.info("search from %s to %s" % (date_from, date_to)) + _logger.info("search from {} to {}".format(date_from, date_to)) domain = [] if date_from: @@ -70,7 +75,7 @@ class SubscriptionRequestService(Component): def _to_dict(self, sr): sr.ensure_one() return { - "id": sr.id, + "id": sr.id, "name": sr.name, "email": sr.email, "state": sr.state, From fb5869758d7a19bc634e58474dfd9e613528ac3b Mon Sep 17 00:00:00 2001 From: "robin.keunen" Date: Tue, 26 May 2020 16:12:51 +0200 Subject: [PATCH 11/12] [IMP] emc_api: generate api key --- easy_my_coop_api/README.rst | 16 ++++++++ easy_my_coop_api/__init__.py | 1 + easy_my_coop_api/models/__init__.py | 1 + easy_my_coop_api/models/auth_api_key.py | 34 +++++++++++++++ easy_my_coop_api/readme/USAGE.rst | 12 ++++++ .../static/description/index.html | 41 +++++++++++++------ 6 files changed, 93 insertions(+), 12 deletions(-) create mode 100644 easy_my_coop_api/models/__init__.py create mode 100644 easy_my_coop_api/models/auth_api_key.py create mode 100644 easy_my_coop_api/readme/USAGE.rst diff --git a/easy_my_coop_api/README.rst b/easy_my_coop_api/README.rst index 4eb4cb6..f6ee9ae 100644 --- a/easy_my_coop_api/README.rst +++ b/easy_my_coop_api/README.rst @@ -26,6 +26,22 @@ Open Easy My Coop to the world: RESTful API. .. contents:: :local: +Usage +===== + +To give access to the API to a structure, go to + +- Settings > Technical (debug mode) > Auth API Key +- click create and select a user, save. +- communicate the API-KEY to the structure. + +It is recommended to create a technical user for the structure belonging to the group "Easy My Coop User". +For example, for the structure Coop IT Easy, create partner with + +- name = coopiteasy-api-user +- Application Accesses = Cooperative Management / User +- Platform Structure = Coop IT Easy + Known issues / Roadmap ====================== diff --git a/easy_my_coop_api/__init__.py b/easy_my_coop_api/__init__.py index d6d6244..c312a84 100644 --- a/easy_my_coop_api/__init__.py +++ b/easy_my_coop_api/__init__.py @@ -1,2 +1,3 @@ from . import controllers +from . import models from . import services diff --git a/easy_my_coop_api/models/__init__.py b/easy_my_coop_api/models/__init__.py new file mode 100644 index 0000000..6dfe3f7 --- /dev/null +++ b/easy_my_coop_api/models/__init__.py @@ -0,0 +1 @@ +from . import auth_api_key diff --git a/easy_my_coop_api/models/auth_api_key.py b/easy_my_coop_api/models/auth_api_key.py new file mode 100644 index 0000000..0db7834 --- /dev/null +++ b/easy_my_coop_api/models/auth_api_key.py @@ -0,0 +1,34 @@ +# Copyright 2020 Coop IT Easy SCRL fs +# Robin Keunen +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +import uuid + +from odoo import api, fields, models + + +class AuthApiKey(models.Model): + _inherit = "auth.api.key" + + def _default_key(self): + return uuid.uuid4() + + # overloaded fields + # required is set to false to allow for a computed field, + # it will always be set. + name = fields.Char(required=False, compute="_compute_name", store=True) + key = fields.Char(default=_default_key) + + @api.multi + @api.depends("user_id") + def _compute_name(self): + for key in self: + if key.user_id: + now = fields.Datetime.now() + + key.name = "{login}-{now}".format( + now=fields.Datetime.to_string(now), + login=key.user_id.login, + ) + else: + key.name = "no-user" diff --git a/easy_my_coop_api/readme/USAGE.rst b/easy_my_coop_api/readme/USAGE.rst new file mode 100644 index 0000000..ee1feef --- /dev/null +++ b/easy_my_coop_api/readme/USAGE.rst @@ -0,0 +1,12 @@ +To give access to the API to a structure, go to + +- Settings > Technical (debug mode) > Auth API Key +- click create and select a user, save. +- communicate the API-KEY to the structure. + +It is recommended to create a technical user for the structure belonging to the group "Easy My Coop User". +For example, for the structure Coop IT Easy, create partner with + +- name = coopiteasy-api-user +- Application Accesses = Cooperative Management / User +- Platform Structure = Coop IT Easy diff --git a/easy_my_coop_api/static/description/index.html b/easy_my_coop_api/static/description/index.html index bc922dd..81a2b67 100644 --- a/easy_my_coop_api/static/description/index.html +++ b/easy_my_coop_api/static/description/index.html @@ -372,18 +372,35 @@ ul.auto-toc {

Table of contents

+
+

Usage

+

To give access to the API to a structure, go to

+
    +
  • Settings > Technical (debug mode) > Auth API Key
  • +
  • click create and select a user, save.
  • +
  • communicate the API-KEY to the structure.
  • +
+

It is recommended to create a technical user for the structure belonging to the group “Easy My Coop User”. +For example, for the structure Coop IT Easy, create partner with

+
    +
  • name = coopiteasy-api-user
  • +
  • Application Accesses = Cooperative Management / User
  • +
  • Platform Structure = Coop IT Easy
  • +
+
-

Known issues / Roadmap

+

Known issues / Roadmap

The API should generate and use an external id for records instead of odoo’s generated id. It would make importing and export data as well as migrating across versions easier.

@@ -392,7 +409,7 @@ routes would need to be rewritten: they only take integer as ids. Another way is to define a sequence per model.

-

Bug Tracker

+

Bug Tracker

Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us smashing it by providing a detailed and welcomed @@ -400,22 +417,22 @@ If you spotted it first, help us smashing it by providing a detailed and welcome

Do not contact contributors directly about support or help with technical issues.

-

Credits

+

Credits

-

Authors

+

Authors

  • Coop IT Easy SCRLfs
-

Contributors

+

Contributors

-

Maintainers

+

Maintainers

This module is part of the coopiteasy/vertical-cooperative project on GitHub.

You are welcome to contribute.

From 1c70a2d40fd3af49f8b92e0228848e3a3968221b Mon Sep 17 00:00:00 2001 From: "robin.keunen" Date: Tue, 26 May 2020 17:45:02 +0200 Subject: [PATCH 12/12] [REF] emc_api: rename service subscription-request --- easy_my_coop_api/controllers/controllers.py | 5 +-- easy_my_coop_api/models/auth_api_key.py | 3 +- .../services/subscription_request_service.py | 3 +- easy_my_coop_api/tests/common.py | 10 +++--- easy_my_coop_api/tests/test_ping.py | 5 +-- easy_my_coop_api/tests/test_registry.py | 2 +- .../tests/test_subscription_requests.py | 31 ++++++++++--------- 7 files changed, 31 insertions(+), 28 deletions(-) diff --git a/easy_my_coop_api/controllers/controllers.py b/easy_my_coop_api/controllers/controllers.py index 6567255..8badeb2 100644 --- a/easy_my_coop_api/controllers/controllers.py +++ b/easy_my_coop_api/controllers/controllers.py @@ -3,9 +3,10 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). -from odoo.addons.base_rest.controllers import main from odoo.http import route +from odoo.addons.base_rest.controllers import main + class UserController(main.RestController): _root_path = "/api/" @@ -20,5 +21,5 @@ class UserController(main.RestController): ) def test(self, _service_name): return self._process_method( - _service_name, "test", _id=None, params=None, + _service_name, "test", _id=None, params=None ) diff --git a/easy_my_coop_api/models/auth_api_key.py b/easy_my_coop_api/models/auth_api_key.py index 0db7834..75446ad 100644 --- a/easy_my_coop_api/models/auth_api_key.py +++ b/easy_my_coop_api/models/auth_api_key.py @@ -27,8 +27,7 @@ class AuthApiKey(models.Model): now = fields.Datetime.now() key.name = "{login}-{now}".format( - now=fields.Datetime.to_string(now), - login=key.user_id.login, + now=fields.Datetime.to_string(now), login=key.user_id.login ) else: key.name = "no-user" diff --git a/easy_my_coop_api/services/subscription_request_service.py b/easy_my_coop_api/services/subscription_request_service.py index 1a462c5..a64de5d 100644 --- a/easy_my_coop_api/services/subscription_request_service.py +++ b/easy_my_coop_api/services/subscription_request_service.py @@ -21,8 +21,7 @@ _logger = logging.getLogger(__name__) class SubscriptionRequestService(Component): _inherit = "base.rest.service" _name = "subscription.request.services" - # service_name todo subscription-request - _usage = "subscription_request" + _usage = "subscription-request" _collection = "emc.services" _description = """ Subscription requests diff --git a/easy_my_coop_api/tests/common.py b/easy_my_coop_api/tests/common.py index 7a7222d..1e15a7f 100644 --- a/easy_my_coop_api/tests/common.py +++ b/easy_my_coop_api/tests/common.py @@ -3,12 +3,14 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). -import requests import json -import odoo + +import requests from lxml import html +import odoo from odoo.fields import Date + from odoo.addons.base_rest.tests.common import BaseRestCase HOST = "127.0.0.1" @@ -66,7 +68,7 @@ class BaseEMCRestCase(BaseRestCase): def http_get(self, url, headers=None): headers = _add_api_key(headers) if url.startswith("/"): - url = "http://%s:%s%s" % (HOST, PORT, url) + url = "http://{}:{}{}".format(HOST, PORT, url) return self.session.get(url, headers=headers) @@ -79,7 +81,7 @@ class BaseEMCRestCase(BaseRestCase): def http_post(self, url, data, headers=None): headers = _add_api_key(headers) if url.startswith("/"): - url = "http://%s:%s%s" % (HOST, PORT, url) + url = "http://{}:{}{}".format(HOST, PORT, url) return self.session.post(url, json=data, headers=headers) diff --git a/easy_my_coop_api/tests/test_ping.py b/easy_my_coop_api/tests/test_ping.py index dcfbe2a..ffb757b 100644 --- a/easy_my_coop_api/tests/test_ping.py +++ b/easy_my_coop_api/tests/test_ping.py @@ -4,12 +4,13 @@ import json + import requests from odoo.addons.base_rest.controllers.main import _PseudoCollection from odoo.addons.component.core import WorkContext -from .common import BaseEMCRestCase, HOST, PORT +from .common import HOST, PORT, BaseEMCRestCase class TestPing(BaseEMCRestCase): @@ -27,7 +28,7 @@ class TestPing(BaseEMCRestCase): def test_ping_route(self): # public route path = "/api/ping/test" - url = "http://%s:%s%s" % (HOST, PORT, path) + url = "http://{}:{}{}".format(HOST, PORT, path) response = requests.get(url) self.assertEquals(response.status_code, 200) content = json.loads(response.content.decode("utf-8")) diff --git a/easy_my_coop_api/tests/test_registry.py b/easy_my_coop_api/tests/test_registry.py index eb2e0b7..b420eff 100644 --- a/easy_my_coop_api/tests/test_registry.py +++ b/easy_my_coop_api/tests/test_registry.py @@ -4,8 +4,8 @@ import odoo - from odoo.http import controllers_per_module + from odoo.addons.base_rest.tests.common import BaseRestCase from ..controllers.controllers import UserController diff --git a/easy_my_coop_api/tests/test_subscription_requests.py b/easy_my_coop_api/tests/test_subscription_requests.py index 71643b8..405205b 100644 --- a/easy_my_coop_api/tests/test_subscription_requests.py +++ b/easy_my_coop_api/tests/test_subscription_requests.py @@ -4,8 +4,10 @@ import json from datetime import timedelta + import odoo from odoo.fields import Date + from odoo.addons.base_rest.controllers.main import _PseudoCollection from odoo.addons.component.core import WorkContext @@ -20,7 +22,7 @@ class TestSRController(BaseEMCRestCase): model_name="rest.service.registration", collection=collection ) - self.service = emc_services_env.component(usage="subscription_request") + self.service = emc_services_env.component(usage="subscription-request") def test_service(self): # kept as example @@ -41,23 +43,23 @@ class TestSRController(BaseEMCRestCase): def test_route_get(self): id_ = self.demo_request_1.id - route = "/api/subscription_request/%s" % id_ + route = "/api/subscription-request/%s" % id_ content = self.http_get_content(route) self.assertEquals(self.demo_request_1_dict, content) @odoo.tools.mute_logger("odoo.addons.base_rest.http") def test_route_get_returns_not_found(self): - route = "/api/subscription_request/%s" % "99999" + route = "/api/subscription-request/%s" % "99999" response = self.http_get(route) self.assertEquals(response.status_code, 404) def test_route_get_string_returns_method_not_allowed(self): - route = "/api/subscription_request/%s" % "abc" + route = "/api/subscription-request/%s" % "abc" response = self.http_get(route) self.assertEquals(response.status_code, 405) def test_route_search_all(self): - route = "/api/subscription_request" + route = "/api/subscription-request" content = self.http_get_content(route) self.assertIn(self.demo_request_1_dict, content["rows"]) @@ -66,37 +68,36 @@ class TestSRController(BaseEMCRestCase): date_from = Date.to_string(sr_date - timedelta(days=1)) date_to = Date.to_string(sr_date + timedelta(days=1)) - route = "/api/subscription_request?date_from=%s" % date_from + route = "/api/subscription-request?date_from=%s" % date_from content = self.http_get_content(route) self.assertIn(self.demo_request_1_dict, content["rows"]) - route = "/api/subscription_request?date_to=%s" % date_to + route = "/api/subscription-request?date_to=%s" % date_to content = self.http_get_content(route) self.assertIn(self.demo_request_1_dict, content["rows"]) - route = "/api/subscription_request?date_from=%s&date_to=%s" % ( - date_from, - date_to, + route = "/api/subscription-request?date_from={}&date_to={}".format( + date_from, date_to ) content = self.http_get_content(route) self.assertIn(self.demo_request_1_dict, content["rows"]) - route = "/api/subscription_request?date_from=%s" % "2300-01-01" + route = "/api/subscription-request?date_from=%s" % "2300-01-01" content = self.http_get_content(route) self.assertEquals(content["count"], 0) - route = "/api/subscription_request?date_to=%s" % "1900-01-01" + route = "/api/subscription-request?date_to=%s" % "1900-01-01" content = self.http_get_content(route) self.assertEquals(content["count"], 0) @odoo.tools.mute_logger("odoo.addons.base_rest.http") def test_route_search_acd_date_returns_bad_request(self): - route = "/api/subscription_request?date_from=%s" % "20200101" + route = "/api/subscription-request?date_from=%s" % "20200101" response = self.http_get(route) self.assertEquals(response.status_code, 400) def test_route_create(self): - url = "/api/subscription_request" + url = "/api/subscription-request" data = { "name": "Lisa des Danses", "email": "lisa@desdanses.be", @@ -130,7 +131,7 @@ class TestSRController(BaseEMCRestCase): self.assertEquals(expected, content) def test_route_update(self): - url = "/api/subscription_request/%s" % self.demo_request_1.id + url = "/api/subscription-request/%s" % self.demo_request_1.id data = {"state": "done"} response = self.http_post(url, data=data)