diff --git a/project/api/organization/resources.py b/project/api/organization/resources.py index ed356ab..8b49202 100644 --- a/project/api/organization/resources.py +++ b/project/api/organization/resources.py @@ -18,6 +18,9 @@ from project.api.event.schemas import ( from project.api.organizer.schemas import ( OrganizerListRequestSchema, OrganizerListResponseSchema, + OrganizerIdSchema, + OrganizerPostRequestSchema, + OrganizerPostRequestLoadSchema, ) from project.api.event_reference.schemas import ( EventReferenceListRequestSchema, @@ -115,6 +118,26 @@ class OrganizationOrganizerListResource(BaseResource): pagination = get_organizer_query(admin_unit.id, name).paginate() return pagination + @doc( + summary="Add new organizer", + tags=["Organizations", "Organizers"], + security=[{"oauth2": ["organizer:write"]}], + ) + @use_kwargs(OrganizerPostRequestSchema, location="json") + @marshal_with(OrganizerIdSchema, 201) + @require_oauth("organizer:write") + def post(self, id, **kwargs): + login_api_user_or_401(current_token.user) + admin_unit = get_admin_unit_for_manage_or_404(id) + access_or_401(admin_unit, "organizer:create") + + organizer = OrganizerPostRequestLoadSchema().load(kwargs, session=db.session) + organizer.admin_unit_id = admin_unit.id + db.session.add(organizer) + db.session.commit() + + return organizer, 201 + class OrganizationPlaceListResource(BaseResource): @doc(summary="List places of organization", tags=["Organizations", "Places"]) diff --git a/project/api/organizer/resources.py b/project/api/organizer/resources.py index c917f91..ccc0b59 100644 --- a/project/api/organizer/resources.py +++ b/project/api/organizer/resources.py @@ -1,8 +1,19 @@ from project.api import add_api_resource -from flask_apispec import marshal_with, doc +from flask import make_response +from flask_apispec import marshal_with, doc, use_kwargs from project.api.resources import BaseResource -from project.api.organizer.schemas import OrganizerSchema +from project.api.organizer.schemas import ( + OrganizerSchema, + OrganizerPostRequestSchema, + OrganizerPostRequestLoadSchema, + OrganizerPatchRequestSchema, + OrganizerPatchRequestLoadSchema, +) from project.models import EventOrganizer +from project.oauth2 import require_oauth +from authlib.integrations.flask_oauth2 import current_token +from project import db +from project.access import access_or_401, login_api_user_or_401 class OrganizerResource(BaseResource): @@ -11,6 +22,63 @@ class OrganizerResource(BaseResource): def get(self, id): return EventOrganizer.query.get_or_404(id) + @doc( + summary="Update organizer", + tags=["Organizers"], + security=[{"oauth2": ["organizer:write"]}], + ) + @use_kwargs(OrganizerPostRequestSchema, location="json") + @marshal_with(None, 204) + @require_oauth("organizer:write") + def put(self, id, **kwargs): + login_api_user_or_401(current_token.user) + organizer = EventOrganizer.query.get_or_404(id) + access_or_401(organizer.adminunit, "organizer:update") + + organizer = OrganizerPostRequestLoadSchema().load( + kwargs, session=db.session, instance=organizer + ) + db.session.commit() + + return make_response("", 204) + + @doc( + summary="Patch organizer", + tags=["Organizers"], + security=[{"oauth2": ["organizer:write"]}], + ) + @use_kwargs(OrganizerPatchRequestSchema, location="json") + @marshal_with(None, 204) + @require_oauth("organizer:write") + def patch(self, id, **kwargs): + login_api_user_or_401(current_token.user) + organizer = EventOrganizer.query.get_or_404(id) + access_or_401(organizer.adminunit, "organizer:update") + + organizer = OrganizerPatchRequestLoadSchema().load( + kwargs, session=db.session, instance=organizer + ) + db.session.commit() + + return make_response("", 204) + + @doc( + summary="Delete organizer", + tags=["Organizers"], + security=[{"oauth2": ["organizer:write"]}], + ) + @marshal_with(None, 204) + @require_oauth("organizer:write") + def delete(self, id): + login_api_user_or_401(current_token.user) + organizer = EventOrganizer.query.get_or_404(id) + access_or_401(organizer.adminunit, "organizer:delete") + + db.session.delete(organizer) + db.session.commit() + + return make_response("", 204) + add_api_resource( OrganizerResource, diff --git a/project/api/organizer/schemas.py b/project/api/organizer/schemas.py index f732aae..0058c95 100644 --- a/project/api/organizer/schemas.py +++ b/project/api/organizer/schemas.py @@ -1,7 +1,13 @@ -from marshmallow import fields +from marshmallow import fields, validate from project.api import marshmallow from project.models import EventOrganizer -from project.api.location.schemas import LocationSchema +from project.api.location.schemas import ( + LocationSchema, + LocationPostRequestSchema, + LocationPostRequestLoadSchema, + LocationPatchRequestSchema, + LocationPatchRequestLoadSchema, +) from project.api.image.schemas import ImageSchema from project.api.organization.schemas import OrganizationRefSchema from project.api.schemas import PaginationRequestSchema, PaginationResponseSchema @@ -50,3 +56,50 @@ class OrganizerListResponseSchema(PaginationResponseSchema): items = fields.List( fields.Nested(OrganizerRefSchema), metadata={"description": "Organizers"} ) + + +class OrganizerPostRequestSchema(marshmallow.SQLAlchemySchema): + class Meta: + model = EventOrganizer + + name = fields.Str(required=True, validate=validate.Length(min=3, max=255)) + url = fields.Str(validate=[validate.URL(), validate.Length(max=255)], missing=None) + email = fields.Str( + validate=[validate.Email(), validate.Length(max=255)], missing=None + ) + phone = fields.Str(validate=validate.Length(max=255), missing=None) + fax = fields.Str(validate=validate.Length(max=255), missing=None) + + location = fields.Nested(LocationPostRequestSchema, missing=None) + + +class OrganizerPostRequestLoadSchema(OrganizerPostRequestSchema): + class Meta: + model = EventOrganizer + load_instance = True + + location = fields.Nested(LocationPostRequestLoadSchema, missing=None) + + +class OrganizerPatchRequestSchema(marshmallow.SQLAlchemySchema): + class Meta: + model = EventOrganizer + + name = fields.Str(validate=validate.Length(min=3, max=255), allow_none=True) + url = fields.Str( + validate=[validate.URL(), validate.Length(max=255)], allow_none=True + ) + email = fields.Str( + validate=[validate.Email(), validate.Length(max=255)], allow_none=True + ) + phone = fields.Str(validate=validate.Length(max=255), allow_none=True) + fax = fields.Str(validate=validate.Length(max=255), allow_none=True) + location = fields.Nested(LocationPatchRequestSchema, allow_none=True) + + +class OrganizerPatchRequestLoadSchema(OrganizerPatchRequestSchema): + class Meta: + model = EventOrganizer + load_instance = True + + location = fields.Nested(LocationPatchRequestLoadSchema, allow_none=True) diff --git a/tests/api/test_organization.py b/tests/api/test_organization.py index cc072a3..4a4635f 100644 --- a/tests/api/test_organization.py +++ b/tests/api/test_organization.py @@ -38,6 +38,27 @@ def test_organizers(client, seeder, utils): utils.get_ok(url) +def test_organizers_post(client, seeder, utils, app): + user_id, admin_unit_id = seeder.setup_api_access() + + url = utils.get_url( + "api_v1_organization_organizer_list", id=admin_unit_id, name="crew" + ) + response = utils.post_json(url, {"name": "Neuer Organisator"}) + utils.assert_response_created(response) + assert "id" in response.json + + with app.app_context(): + from project.models import EventOrganizer + + organizer = ( + EventOrganizer.query.filter(EventOrganizer.admin_unit_id == admin_unit_id) + .filter(EventOrganizer.name == "Neuer Organisator") + .first() + ) + assert organizer is not None + + def test_places(client, seeder, utils): user_id, admin_unit_id = seeder.setup_base() seeder.upsert_default_event_place(admin_unit_id) diff --git a/tests/api/test_organizer.py b/tests/api/test_organizer.py index 34acd1c..2f0743f 100644 --- a/tests/api/test_organizer.py +++ b/tests/api/test_organizer.py @@ -4,3 +4,49 @@ def test_read(client, seeder, utils): url = utils.get_url("api_v1_organizer", id=organizer_id) utils.get_ok(url) + + +def test_put(client, seeder, utils, app): + user_id, admin_unit_id = seeder.setup_api_access() + organizer_id = seeder.upsert_default_event_organizer(admin_unit_id) + + url = utils.get_url("api_v1_organizer", id=organizer_id) + response = utils.put_json(url, {"name": "Neuer Name"}) + utils.assert_response_no_content(response) + + with app.app_context(): + from project.models import EventOrganizer + + organizer = EventOrganizer.query.get(organizer_id) + assert organizer.name == "Neuer Name" + + +def test_patch(client, seeder, utils, app): + user_id, admin_unit_id = seeder.setup_api_access() + organizer_id = seeder.upsert_default_event_organizer(admin_unit_id) + + url = utils.get_url("api_v1_organizer", id=organizer_id) + response = utils.patch_json(url, {"phone": "55555"}) + utils.assert_response_no_content(response) + + with app.app_context(): + from project.models import EventOrganizer + + organizer = EventOrganizer.query.get(organizer_id) + assert organizer.name == "Meine Crew" + assert organizer.phone == "55555" + + +def test_delete(client, seeder, utils, app): + user_id, admin_unit_id = seeder.setup_api_access() + organizer_id = seeder.upsert_default_event_organizer(admin_unit_id) + + url = utils.get_url("api_v1_organizer", id=organizer_id) + response = utils.delete(url) + utils.assert_response_no_content(response) + + with app.app_context(): + from project.models import EventOrganizer + + organizer = EventOrganizer.query.get(organizer_id) + assert organizer is None