diff --git a/CHANGES.rst b/CHANGES.rst index a6bc249..d2724df 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,7 +4,10 @@ Changelog 1.2.6 (unreleased) ------------------ -- Nothing changed yet. +- Added more information in the `/@booking/` service (e.g. booking_folder, booking_address, booking_office), + already present in the `/@bookings?fullobjects=1` service. https://github.com/RedTurtle/design.plone.ioprenoto/pull/41 + These changes will be moved in the future from here to redturtle.reservations 2.3.x + [mamico] 1.2.5 (2024-04-22) diff --git a/setup.py b/setup.py index a816b99..7a80b87 100644 --- a/setup.py +++ b/setup.py @@ -57,7 +57,7 @@ "z3c.jbot", "plone.api>=1.8.4", "plone.app.dexterity", - "redturtle.prenotazioni>=2.2.0", + "redturtle.prenotazioni>=2.2.0.dev0", "design.plone.policy", "plone.restapi>= 9.6.0", ], diff --git a/src/design/plone/ioprenoto/restapi/services/booking/__init__.py b/src/design/plone/ioprenoto/restapi/services/booking/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/design/plone/ioprenoto/restapi/services/booking/add.py b/src/design/plone/ioprenoto/restapi/services/booking/add.py new file mode 100644 index 0000000..5820527 --- /dev/null +++ b/src/design/plone/ioprenoto/restapi/services/booking/add.py @@ -0,0 +1,34 @@ +# TODO: il codice qui è temporaneo, va spostato in redturtle.prenotazioni +# di conseguenza l'implementazione si semplifica + +from plone import api +from redturtle.prenotazioni.interfaces import ISerializeToPrenotazioneSearchableItem +from redturtle.prenotazioni.restapi.services.booking.add import ( + AddBooking as BaseAddBooking, +) +from zope.component import getMultiAdapter + + +class AddBooking(BaseAddBooking): + def reply(self): + result = super().reply() + catalog = api.portal.get_tool("portal_catalog") + uid = result["UID"] + booking = catalog.unrestrictedSearchResults(UID=uid)[0]._unrestrictedGetObject() + + response = getMultiAdapter( + (booking, self.request), ISerializeToPrenotazioneSearchableItem + )(fullobjects=True) + + # BBB: + response["@type"] = booking.portal_type + response["id"] = booking.getId() # response["@id"].split("/")[-1] + response["UID"] = response["booking_id"] + response["gate"] = response["booking_gate"] + response["booking_folder_uid"] = ( + response["booking_folder"]["uid"] if "booking_folder" in response else None + ) + if "notify_on_confirm" not in response: + prenotazioni_folder = booking.getPrenotazioniFolder() + response["notify_on_confirm"] = prenotazioni_folder.notify_on_confirm + return response diff --git a/src/design/plone/ioprenoto/restapi/services/booking/configure.zcml b/src/design/plone/ioprenoto/restapi/services/booking/configure.zcml new file mode 100644 index 0000000..e0d0b8e --- /dev/null +++ b/src/design/plone/ioprenoto/restapi/services/booking/configure.zcml @@ -0,0 +1,24 @@ + + + + + + + diff --git a/src/design/plone/ioprenoto/restapi/services/booking/get.py b/src/design/plone/ioprenoto/restapi/services/booking/get.py new file mode 100644 index 0000000..1f95e30 --- /dev/null +++ b/src/design/plone/ioprenoto/restapi/services/booking/get.py @@ -0,0 +1,64 @@ +# TODO: il codice qui è temporaneo, va spostato in redturtle.prenotazioni + +from AccessControl import Unauthorized +from plone import api +from redturtle.prenotazioni.interfaces import ISerializeToPrenotazioneSearchableItem +from redturtle.prenotazioni.restapi.services.booking.get import BookingInfo as Base +from zope.component import getMultiAdapter + + +class BookingInfo(Base): + def reply(self): + if not self.booking_uid: + return self.reply_no_content(status=404) + catalog = api.portal.get_tool("portal_catalog") + query = {"portal_type": "Prenotazione", "UID": self.booking_uid} + booking = catalog.unrestrictedSearchResults(query) + + if not booking: + return self.reply_no_content(status=404) + + booking = booking[0]._unrestrictedGetObject() + if not booking.canAccessBooking(): + raise Unauthorized + + # (Pdb) pp getMultiAdapter((booking, self.request), ISerializeToPrenotazioneSearchableItem)(fullobjects=True).keys() + # dict_keys(['@id', 'title', 'description', 'booking_id', 'booking_code', 'booking_url', 'booking_date', + # 'booking_expiration_date', 'booking_type', 'booking_gate', 'booking_status', 'booking_status_label', + # 'booking_status_date', 'booking_status_notes', 'email', 'fiscalcode', + # 'phone', 'staff_notes', 'company', 'vacation', 'modification_date', 'creation_date', 'booking_folder', 'booking_address', + # 'booking_office', 'requirements', 'cosa_serve']) + + # (Pdb) pp getMultiAdapter((booking, self.request), ISerializeToJson)().keys() + # dict_keys(['@id', 'UID', '@type', 'title', 'description', 'gate', 'id', 'phone', 'email', 'fiscalcode', 'company', + # 'staff_notes', 'booking_date', 'booking_expiration_date', 'booking_status', 'booking_status_label', + # 'booking_type', 'booking_folder_uid', 'vacation', 'booking_code', 'notify_on_confirm', 'cosa_serve', 'requirements', + # 'modification_date', 'creation_date']) + + # nel cambio si perdono gli attributi: + # UID (diventa booking_id), + # @type (sempre 'Prenotazione'), + # gate (diventa booking_gate), + # id (ultimo pezzo dell'URL), + # staff_notes (usato?) + # booking_folder_uid (['booking_folder']['uid']) + # notify_on_confirm (usato?) + + # response = getMultiAdapter((booking, self.request), ISerializeToJson)() + response = getMultiAdapter( + (booking, self.request), ISerializeToPrenotazioneSearchableItem + )(fullobjects=True) + + # BBB: + response["@type"] = booking.portal_type + response["id"] = booking.getId() # response["@id"].split("/")[-1] + response["UID"] = response["booking_id"] + response["gate"] = response["booking_gate"] + response["booking_folder_uid"] = ( + response["booking_folder"]["uid"] if "booking_folder" in response else None + ) + if "notify_on_confirm" not in response: + prenotazioni_folder = booking.getPrenotazioniFolder() + response["notify_on_confirm"] = prenotazioni_folder.notify_on_confirm + + return response diff --git a/src/design/plone/ioprenoto/restapi/services/configure.zcml b/src/design/plone/ioprenoto/restapi/services/configure.zcml index d341a5c..9862ef1 100644 --- a/src/design/plone/ioprenoto/restapi/services/configure.zcml +++ b/src/design/plone/ioprenoto/restapi/services/configure.zcml @@ -5,6 +5,7 @@ + diff --git a/src/design/plone/ioprenoto/tests/test_booking_info.py b/src/design/plone/ioprenoto/tests/test_booking_info.py new file mode 100644 index 0000000..83714d5 --- /dev/null +++ b/src/design/plone/ioprenoto/tests/test_booking_info.py @@ -0,0 +1,242 @@ +from copy import deepcopy +from datetime import date +from datetime import datetime +from datetime import timedelta +from design.plone.ioprenoto.testing import DESIGN_PLONE_IOPRENOTO_API_FUNCTIONAL_TESTING +from plone import api +from plone.app.testing import setRoles +from plone.app.testing import SITE_OWNER_NAME +from plone.app.testing import SITE_OWNER_PASSWORD +from plone.app.testing import TEST_USER_ID +from plone.app.textfield.value import RichTextValue +from plone.restapi.testing import RelativeSession +from transaction import commit +from z3c.relationfield.relation import RelationValue +from zope.component import queryUtility +from zope.intid.interfaces import IIntIds + +import unittest + + +class TestBookingInfo(unittest.TestCase): + layer = DESIGN_PLONE_IOPRENOTO_API_FUNCTIONAL_TESTING + maxDiff = None + + def setUp(self): + self.app = self.layer["app"] + self.portal = self.layer["portal"] + self.request = self.layer["request"] + self.portal_url = self.portal.absolute_url() + self.today = datetime.now() + + self.api_session = RelativeSession(self.portal_url) + self.api_session.headers.update({"Accept": "application/json"}) + self.api_session.auth = (SITE_OWNER_NAME, SITE_OWNER_PASSWORD) + + setRoles(self.portal, TEST_USER_ID, ["Manager"]) + + self.venue = api.content.create( + container=self.portal, + title="Example venue", + type="Venue", + city="Ferrara", + country="380", + street="Foo Street 22", + ) + self.unita_organizzativa = api.content.create( + container=self.portal, + type="UnitaOrganizzativa", + title="UO", + sede=[RelationValue(to_id=queryUtility(IIntIds).getId(self.venue))], + ) + self.unita_organizzativa2 = api.content.create( + container=self.portal, + type="UnitaOrganizzativa", + title="UO 2", + ) + self.unita_organizzativa3 = api.content.create( + container=self.portal, + type="UnitaOrganizzativa", + title="UO 3", + ) + + self.prenotazioni_folder = api.content.create( + container=self.portal, + type="PrenotazioniFolder", + title="Prenotazioni Folder", + orario_di_apertura="foo", + descriptionAgenda=RichTextValue( + "

description agenda

", + "text/html", + "text/html", + ), + gates=["Gate A"], + uffici_correlati=[ + RelationValue( + to_id=queryUtility(IIntIds).getId(self.unita_organizzativa) + ) + ], + ) + week_table = deepcopy(self.prenotazioni_folder.week_table) + for day in week_table: + day["morning_start"] = "0700" + day["morning_end"] = "1300" + self.prenotazioni_folder.week_table = week_table + + api.content.transition( + obj=api.content.create( + type="PrenotazioneType", + title="Type A", + duration=30, + container=self.prenotazioni_folder, + gates=["all"], + ), + transition="publish", + ) + + # self.prenotazioni_folder2 = api.content.create( + # container=self.portal, + # type="PrenotazioniFolder", + # title="Prenotazioni Folder 2", + # uffici_correlati=[ + # RelationValue( + # to_id=queryUtility(IIntIds).getId(self.unita_organizzativa) + # ) + # ], + # ) + # api.content.transition( + # obj=api.content.create( + # type="PrenotazioneType", + # title="Type A", + # duration=10, + # container=self.prenotazioni_folder2, + # gates=["all"], + # ), + # transition="publish", + # ) + # api.content.transition( + # obj=api.content.create( + # type="PrenotazioneType", + # title="Type B", + # duration=30, + # container=self.prenotazioni_folder2, + # gates=["all"], + # ), + # transition="publish", + # ) + # self.prenotazioni_folder3 = api.content.create( + # container=self.portal, + # type="PrenotazioniFolder", + # title="Prenotazioni Folder 3", + # uffici_correlati=[ + # RelationValue( + # to_id=queryUtility(IIntIds).getId(self.unita_organizzativa2) + # ) + # ], + # ) + + # self.prenotazioni_folder4 = api.content.create( + # container=self.portal, + # type="PrenotazioniFolder", + # title="Prenotazioni Folder 4", + # ) + + # self.servizio = api.content.create( + # container=self.portal, + # type="Servizio", + # title="Servizio", + # canale_fisico=[ + # RelationValue( + # to_id=queryUtility(IIntIds).getId(self.unita_organizzativa) + # ) + # ], + # ) + + # booker = IBooker(self.prenotazioni_folder) + # self.prenotazione1 = booker.book( + # { + # "booking_date": self.today.replace(hour=8, minute=0), + # "booking_type": "Type A", + # "title": "foo", + # "other_fields": { + # "booking_office": self.unita_organizzativa.absolute_url(), + # "booking_address": self.venue.absolute_url(), + # }, + # } + # ) + commit() + + def test_get_bookings(self): + booking_date = "{}T10:00:00+00:00".format( + (date.today() + timedelta(3)).strftime("%Y-%m-%d") + ) + res = self.api_session.post( + self.prenotazioni_folder.absolute_url() + "/@booking", + json={ + "booking_date": booking_date, + "booking_type": "Type A", + "other_fields": { + "booking_office": self.unita_organizzativa.absolute_url(), + "booking_address": self.venue.absolute_url(), + }, + "fields": [ + {"name": "title", "value": "Mario Rossi"}, + {"name": "email", "value": "mario.rossi@example"}, + {"name": "description", "value": "foo"}, + ], + }, + ) + self.assertEqual(res.status_code, 200) + booking = res.json() + self.assertIn("@id", booking) + self.assertEqual(booking["id"], "mario-rossi") + self.assertEqual( + booking["booking_folder"]["@id"], + self.prenotazioni_folder.absolute_url(), + ) + self.assertEqual(booking["booking_address"]["@id"], self.venue.absolute_url()) + self.assertEqual( + booking["booking_office"]["@id"], + self.unita_organizzativa.absolute_url(), + ) + + res = self.api_session.get( + self.portal.absolute_url() + "/@bookings?fullobjects=1" + ) + self.assertEqual(res.status_code, 200) + self.assertEqual(len(res.json()["items"]), 1) + booking_info = res.json()["items"][0] + self.assertIn("@id", booking_info) + self.assertEqual( + booking_info["booking_folder"]["@id"], + self.prenotazioni_folder.absolute_url(), + ) + self.assertEqual( + booking_info["booking_address"]["@id"], self.venue.absolute_url() + ) + self.assertEqual( + booking_info["booking_office"]["@id"], + self.unita_organizzativa.absolute_url(), + ) + + # TODO: andrebbe messo lo UID anche su booking_id visto che è l'attributo conosciuto come identificativo + res = self.api_session.get( + self.portal.absolute_url() + f"/@booking/{booking['UID']}" + ) + booking_info = res.json() + # self.assertEqual(booking_info["@type"], "Prenotazione") + self.assertEqual( + booking_info["booking_folder"]["@id"], + self.prenotazioni_folder.absolute_url(), + ) + self.assertEqual( + booking_info["booking_address"]["@id"], self.venue.absolute_url() + ) + self.assertEqual( + booking_info["booking_office"]["@id"], + self.unita_organizzativa.absolute_url(), + ) + self.assertEqual( + booking_info["notify_on_confirm"], + False, + ) diff --git a/src/design/plone/ioprenoto/tests/test_prenotazione_add.py b/src/design/plone/ioprenoto/tests/test_prenotazione_add.py index 54a8fbb..82eab46 100644 --- a/src/design/plone/ioprenoto/tests/test_prenotazione_add.py +++ b/src/design/plone/ioprenoto/tests/test_prenotazione_add.py @@ -45,6 +45,7 @@ def setUp(self): self.api_session.headers.update({"Accept": "application/json"}) def test_description_required_validation(self): + """TODO: spiegare perchè description è obbligatorio""" self.api_session.auth = None booking_date = "{}T09:00:00+00:00".format( (date.today() + timedelta(1)).strftime("%Y-%m-%d") diff --git a/test_plone60.cfg b/test_plone60.cfg index d63012d..ec9942a 100644 --- a/test_plone60.cfg +++ b/test_plone60.cfg @@ -13,3 +13,5 @@ auto-checkout = createcoverage = 1.5 watchdog = 2.1.6 plone.restapi = >=9.6.0 +docutils=0.21.2 +