From ea175788904f5e2d1678ffd4fb410182128ca744 Mon Sep 17 00:00:00 2001 From: Daniel Grams Date: Thu, 13 Jul 2023 08:58:04 +0200 Subject: [PATCH] API: event-reference-requests #520 --- project/api/__init__.py | 3 + .../api/event_reference_request/__init__.py | 0 .../api/event_reference_request/resources.py | 127 +++++++++++++++ .../api/event_reference_request/schemas.py | 126 ++++++++++++++ project/api/organization/resources.py | 96 ++++++++++- .../models/admin_unit_verification_request.py | 6 + project/models/event_reference_request.py | 11 ++ project/services/reference.py | 51 ++++-- project/services/search_params.py | 7 + project/views/event.py | 10 +- project/views/reference.py | 5 +- project/views/reference_request.py | 98 ++++++----- project/views/reference_request_review.py | 6 - project/views/verification_request_review.py | 6 - tests/api/test_event_reference_request.py | 126 ++++++++++++++ tests/api/test_organization.py | 154 ++++++++++++++++++ tests/seeder.py | 9 + tests/views/test_reference_request.py | 6 +- 18 files changed, 771 insertions(+), 76 deletions(-) create mode 100644 project/api/event_reference_request/__init__.py create mode 100644 project/api/event_reference_request/resources.py create mode 100644 project/api/event_reference_request/schemas.py create mode 100644 tests/api/test_event_reference_request.py diff --git a/project/api/__init__.py b/project/api/__init__.py index 251d7a7..e87d3b9 100644 --- a/project/api/__init__.py +++ b/project/api/__init__.py @@ -133,6 +133,8 @@ scope_list = [ "eventlist:write", "customwidget:write", "eventreference:write", + "eventreferencerequest:read", + "eventreferencerequest:write", ] scopes = {k: get_localized_scope(k) for v, k in enumerate(scope_list)} @@ -220,6 +222,7 @@ import project.api.event_category.resources import project.api.event_date.resources import project.api.event_list.resources import project.api.event_reference.resources +import project.api.event_reference_request.resources import project.api.organization.resources import project.api.organization_invitation.resources import project.api.organization_relation.resources diff --git a/project/api/event_reference_request/__init__.py b/project/api/event_reference_request/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/project/api/event_reference_request/resources.py b/project/api/event_reference_request/resources.py new file mode 100644 index 0000000..9109429 --- /dev/null +++ b/project/api/event_reference_request/resources.py @@ -0,0 +1,127 @@ +from flask import abort, make_response +from flask_apispec import doc, marshal_with, use_kwargs +from marshmallow import ValidationError + +from project import db +from project.access import access_or_401, has_access, login_api_user_or_401 +from project.api import add_api_resource +from project.api.event_reference.schemas import EventReferenceIdSchema +from project.api.event_reference_request.schemas import ( + EventReferenceRequestRejectRequestSchema, + EventReferenceRequestSchema, + EventReferenceRequestVerifyRequestSchema, +) +from project.api.resources import BaseResource, require_api_access +from project.models import EventReferenceRequest +from project.models.event_reference_request import EventReferenceRequestReviewStatus +from project.services.reference import create_event_reference_for_request +from project.views.reference_request_review import ( + send_reference_request_review_status_mails, +) + + +class EventReferenceRequestResource(BaseResource): + @doc(summary="Get event reference request", tags=["Event Reference Requests"]) + @marshal_with(EventReferenceRequestSchema) + @require_api_access("eventreferencerequest:read") + def get(self, id): + login_api_user_or_401() + reference_request = EventReferenceRequest.query.get_or_404(id) + + if not has_access( + reference_request.event.admin_unit, "reference_request:read" + ) and not has_access(reference_request.admin_unit, "reference_request:verify"): + abort(401) + + return reference_request + + @doc( + summary="Delete reference request", + tags=["Event Reference Requests"], + ) + @marshal_with(None, 204) + @require_api_access("eventreferencerequest:write") + def delete(self, id): + login_api_user_or_401() + reference_request = EventReferenceRequest.query.get_or_404(id) + access_or_401(reference_request.event.admin_unit, "reference_request:delete") + + db.session.delete(reference_request) + db.session.commit() + + return make_response("", 204) + + +class EventReferenceRequestVerifyResource(BaseResource): + @doc( + summary="Verify event reference request. Returns reference id.", + tags=["Event Reference Requests"], + ) + @use_kwargs(EventReferenceRequestVerifyRequestSchema, location="json", apply=True) + @marshal_with(EventReferenceIdSchema, 201) + @require_api_access("eventreferencerequest:write") + def post(self, id, **kwargs): + login_api_user_or_401() + reference_request = EventReferenceRequest.query.get_or_404(id) + access_or_401(reference_request.admin_unit, "reference_request:verify") + + if ( + reference_request.review_status + == EventReferenceRequestReviewStatus.verified + ): # pragma: no cover + raise ValidationError("Request already verified") + + reference_request.review_status = EventReferenceRequestReviewStatus.verified + + reference = create_event_reference_for_request(reference_request) + reference.rating = kwargs.get("rating", reference.rating) + db.session.commit() + + send_reference_request_review_status_mails(reference_request) + + return reference, 201 + + +class EventReferenceRequestRejectResource(BaseResource): + @doc( + summary="Reject event reference request", + tags=["Event Reference Requests"], + ) + @use_kwargs(EventReferenceRequestRejectRequestSchema, location="json", apply=False) + @marshal_with(None, 204) + @require_api_access("eventreferencerequest:write") + def post(self, id): + login_api_user_or_401() + reference_request = EventReferenceRequest.query.get_or_404(id) + access_or_401(reference_request.admin_unit, "reference_request:verify") + + if ( + reference_request.review_status + == EventReferenceRequestReviewStatus.verified + ): # pragma: no cover + raise ValidationError("Request already verified") + + reference_request = self.update_instance( + EventReferenceRequestRejectRequestSchema, instance=reference_request + ) + reference_request.review_status = EventReferenceRequestReviewStatus.rejected + db.session.commit() + + return make_response("", 204) + + +add_api_resource( + EventReferenceRequestResource, + "/event-reference-requests/", + "api_v1_event_reference_request", +) +add_api_resource( + EventReferenceRequestVerifyResource, + "/event-reference-requests//verify", + "api_v1_event_reference_request_verify", +) +add_api_resource( + EventReferenceRequestRejectResource, + "/event-reference-requests//reject", + "api_v1_event_reference_request_reject", +) diff --git a/project/api/event_reference_request/schemas.py b/project/api/event_reference_request/schemas.py new file mode 100644 index 0000000..ada812e --- /dev/null +++ b/project/api/event_reference_request/schemas.py @@ -0,0 +1,126 @@ +from marshmallow import fields, validate +from marshmallow_enum import EnumField + +from project.api import marshmallow +from project.api.event.schemas import EventRefSchema, EventWriteIdSchema +from project.api.organization.schemas import ( + OrganizationRefSchema, + OrganizationWriteIdSchema, +) +from project.api.schemas import ( + IdSchemaMixin, + PaginationRequestSchema, + PaginationResponseSchema, + SQLAlchemyBaseSchema, + TrackableRequestSchemaMixin, + TrackableSchemaMixin, +) +from project.models import EventReferenceRequest +from project.models.event_reference_request import ( + EventReferenceRequestRejectionReason, + EventReferenceRequestReviewStatus, +) + + +class EventReferenceRequestModelSchema(SQLAlchemyBaseSchema): + class Meta: + model = EventReferenceRequest + load_instance = True + + +class EventReferenceRequestIdSchema(EventReferenceRequestModelSchema, IdSchemaMixin): + pass + + +class EventReferenceRequestBaseSchemaMixin(TrackableSchemaMixin): + review_status = EnumField( + EventReferenceRequestReviewStatus, + load_default=EventReferenceRequestReviewStatus.inbox, + ) + rejection_reason = EnumField( + EventReferenceRequestRejectionReason, + ) + + +class EventReferenceRequestRefSchema( + EventReferenceRequestIdSchema, TrackableSchemaMixin +): + event = fields.Nested(EventRefSchema) + + +class EventReferenceRequestSchema( + EventReferenceRequestIdSchema, EventReferenceRequestBaseSchemaMixin +): + event = fields.Nested(EventRefSchema) + organization = fields.Nested(OrganizationRefSchema, attribute="admin_unit") + + +class EventReferenceRequestDumpSchema(EventReferenceRequestIdSchema): + event_id = marshmallow.auto_field() + organization_id = fields.Int(attribute="admin_unit_id") + + +class EventReferenceRequestListRequestSchema( + PaginationRequestSchema, TrackableRequestSchemaMixin +): + sort = fields.Str( + metadata={"description": "Sort result items."}, + validate=validate.OneOf(["-created_at", "-updated_at", "-last_modified_at"]), + ) + + +class EventReferenceRequestListResponseSchema(PaginationResponseSchema): + items = fields.List( + fields.Nested(EventReferenceRequestRefSchema), + metadata={"description": "Event reference requests"}, + ) + + +class EventReferenceRequestWriteSchemaMixin(object): + event = fields.Nested( + EventWriteIdSchema, + required=True, + metadata={"description": "Event to reference"}, + ) + target_organization = fields.Nested( + OrganizationWriteIdSchema, + attribute="admin_unit", + required=True, + metadata={"description": "Target organization."}, + ) + + +class EventReferenceRequestPostRequestSchema( + EventReferenceRequestModelSchema, + EventReferenceRequestWriteSchemaMixin, +): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.make_post_schema() + + +class EventReferenceRequestVerifyRequestSchema(SQLAlchemyBaseSchema): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.make_post_schema() + + rating = fields.Int( + load_default=50, + dump_default=50, + validate=validate.OneOf([0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]), + metadata={ + "description": "How relevant the event is to your organization. 0 (Little relevant), 50 (Default), 100 (Highlight)." + }, + ) + + +class EventReferenceRequestRejectRequestSchema( + EventReferenceRequestModelSchema, +): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.make_post_schema() + + rejection_reason = EnumField( + EventReferenceRequestRejectionReason, + ) diff --git a/project/api/organization/resources.py b/project/api/organization/resources.py index f8d6897..120f7c0 100644 --- a/project/api/organization/resources.py +++ b/project/api/organization/resources.py @@ -6,6 +6,7 @@ from sqlalchemy import and_ from project import db from project.access import ( access_or_401, + can_request_event_reference, can_verify_admin_unit, get_admin_unit_for_manage_or_404, login_api_user, @@ -45,6 +46,12 @@ from project.api.event_reference.schemas import ( EventReferenceListRequestSchema, EventReferenceListResponseSchema, ) +from project.api.event_reference_request.schemas import ( + EventReferenceRequestIdSchema, + EventReferenceRequestListRequestSchema, + EventReferenceRequestListResponseSchema, + EventReferenceRequestPostRequestSchema, +) from project.api.organization.schemas import ( OrganizationListRequestSchema, OrganizationListResponseSchema, @@ -92,16 +99,23 @@ from project.services.importer.event_importer import EventImporter from project.services.reference import ( get_reference_incoming_query, get_reference_outgoing_query, + get_reference_requests_incoming_query, + get_reference_requests_outgoing_query, get_relation_outgoing_query, ) from project.services.search_params import ( AdminUnitSearchParams, EventPlaceSearchParams, + EventReferenceRequestSearchParams, EventReferenceSearchParams, EventSearchParams, OrganizerSearchParams, TrackableSearchParams, ) +from project.views.reference_request import ( + handle_request_according_to_relation, + send_reference_request_mails, +) from project.views.utils import get_current_admin_unit_for_api, send_mail_async @@ -375,10 +389,78 @@ class OrganizationOutgoingEventReferenceListResource(BaseResource): def get(self, id, **kwargs): admin_unit = AdminUnit.query.get_or_404(id) - pagination = get_reference_outgoing_query(admin_unit).paginate() + params = EventReferenceSearchParams() + params.load_from_request(**kwargs) + params.admin_unit_id = admin_unit.id + pagination = get_reference_outgoing_query(params).paginate() return pagination +class OrganizationIncomingEventReferenceRequestListResource(BaseResource): + @doc( + summary="List incoming event reference requests of organization", + tags=["Organizations", "Event Reference Requests"], + ) + @use_kwargs(EventReferenceRequestListRequestSchema, location=("query")) + @marshal_with(EventReferenceRequestListResponseSchema) + @require_api_access("eventreferencerequest:read") + def get(self, id, **kwargs): + login_api_user_or_401() + admin_unit = get_admin_unit_for_manage_or_404(id) + + params = EventReferenceRequestSearchParams() + params.load_from_request(**kwargs) + params.admin_unit_id = admin_unit.id + pagination = get_reference_requests_incoming_query(params).paginate() + return pagination + + +class OrganizationOutgoingEventReferenceRequestListResource(BaseResource): + @doc( + summary="List outgoing event reference requests of organization", + tags=["Organizations", "Event Reference Requests"], + ) + @use_kwargs(EventReferenceRequestListRequestSchema, location=("query")) + @marshal_with(EventReferenceRequestListResponseSchema) + @require_api_access("eventreferencerequest:read") + def get(self, id, **kwargs): + login_api_user_or_401() + admin_unit = get_admin_unit_for_manage_or_404(id) + + params = EventReferenceRequestSearchParams() + params.load_from_request(**kwargs) + params.admin_unit_id = admin_unit.id + pagination = get_reference_requests_outgoing_query(params).paginate() + return pagination + + @doc( + summary="Add reference request", + tags=["Organizations", "Event Reference Requests"], + ) + @use_kwargs(EventReferenceRequestPostRequestSchema, location="json", apply=False) + @marshal_with(EventReferenceRequestIdSchema, 201) + @require_api_access("eventreferencerequest:write") + def post(self, id): + login_api_user_or_401() + admin_unit = get_admin_unit_for_manage_or_404(id) + + reference_request = self.create_instance(EventReferenceRequestPostRequestSchema) + event = reference_request.event + + if event.admin_unit_id != admin_unit.id: + abort(404) + + if not can_request_event_reference(event): + abort(401) + + db.session.add(reference_request) + reference, _ = handle_request_according_to_relation(reference_request, event) + db.session.commit() + send_reference_request_mails(reference_request, reference) + + return reference_request, 201 + + class OrganizationOutgoingRelationListResource(BaseResource): @doc( summary="List outgoing relations of organization", @@ -626,6 +708,18 @@ add_api_resource( "/organizations//event-references/outgoing", "api_v1_organization_outgoing_event_reference_list", ) + +add_api_resource( + OrganizationIncomingEventReferenceRequestListResource, + "/organizations//event-reference-requests/incoming", + "api_v1_organization_incoming_event_reference_request_list", +) +add_api_resource( + OrganizationOutgoingEventReferenceRequestListResource, + "/organizations//event-reference-requests/outgoing", + "api_v1_organization_outgoing_event_reference_request_list", +) + add_api_resource( OrganizationOutgoingRelationListResource, "/organizations//relations/outgoing", diff --git a/project/models/admin_unit_verification_request.py b/project/models/admin_unit_verification_request.py index 2d92c2a..6931669 100644 --- a/project/models/admin_unit_verification_request.py +++ b/project/models/admin_unit_verification_request.py @@ -66,4 +66,10 @@ class AdminUnitVerificationRequest(db.Model, TrackableMixin): @listens_for(AdminUnitVerificationRequest, "before_insert") @listens_for(AdminUnitVerificationRequest, "before_update") def before_saving_admin_unit_verification_request(mapper, connect, self): + if self.review_status != AdminUnitVerificationRequestReviewStatus.rejected: + self.rejection_reason = None + + if self.rejection_reason == 0: + self.rejection_reason = None + self.validate() diff --git a/project/models/event_reference_request.py b/project/models/event_reference_request.py index 8df90b8..fddfdc4 100644 --- a/project/models/event_reference_request.py +++ b/project/models/event_reference_request.py @@ -1,6 +1,7 @@ from enum import IntEnum from sqlalchemy import Column, Integer, UniqueConstraint +from sqlalchemy.event import listens_for from sqlalchemy.ext.hybrid import hybrid_property from project import db @@ -38,3 +39,13 @@ class EventReferenceRequest(db.Model, TrackableMixin): @hybrid_property def verified(self): return self.review_status == EventReferenceRequestReviewStatus.verified + + +@listens_for(EventReferenceRequest, "before_insert") +@listens_for(EventReferenceRequest, "before_update") +def before_saving_event_reference_request(mapper, connect, self): + if self.review_status != EventReferenceRequestReviewStatus.rejected: + self.rejection_reason = None + + if self.rejection_reason == 0: + self.rejection_reason = None diff --git a/project/services/reference.py b/project/services/reference.py index d0b760b..e618d41 100644 --- a/project/services/reference.py +++ b/project/services/reference.py @@ -10,7 +10,10 @@ from project.models import ( EventReferenceRequestReviewStatus, ) from project.models.admin_unit import AdminUnit -from project.services.search_params import EventReferenceSearchParams +from project.services.search_params import ( + EventReferenceRequestSearchParams, + EventReferenceSearchParams, +) def create_event_reference_for_request(request): @@ -49,22 +52,42 @@ def get_reference_incoming_query(params: EventReferenceSearchParams): return result -def get_reference_outgoing_query(admin_unit): - return ( - EventReference.query.join(Event) - .filter(Event.admin_unit_id == admin_unit.id) - .order_by(EventReference.created_at.desc()) - ) +def get_reference_outgoing_query(params: EventReferenceSearchParams): + result = EventReference.query.join(Event) + + if params.admin_unit_id: + result = result.filter(Event.admin_unit_id == params.admin_unit_id) + + result = params.get_trackable_query(result, EventReference) + result = params.get_trackable_order_by(result, EventReference) + result = result.order_by(EventReference.created_at.desc()) + return result -def get_reference_requests_incoming_query(admin_unit): - return EventReferenceRequest.query.filter( - and_( - EventReferenceRequest.review_status - != EventReferenceRequestReviewStatus.verified, - EventReferenceRequest.admin_unit_id == admin_unit.id, +def get_reference_requests_incoming_query(params: EventReferenceRequestSearchParams): + result = EventReferenceRequest.query + + if params.admin_unit_id: + result = result.filter( + EventReferenceRequest.admin_unit_id == params.admin_unit_id ) - ) + + result = params.get_trackable_query(result, EventReferenceRequest) + result = params.get_trackable_order_by(result, EventReferenceRequest) + result = result.order_by(EventReferenceRequest.created_at.desc()) + return result + + +def get_reference_requests_outgoing_query(params: EventReferenceRequestSearchParams): + result = EventReferenceRequest.query.join(Event) + + if params.admin_unit_id: + result = result.filter(Event.admin_unit_id == params.admin_unit_id) + + result = params.get_trackable_query(result, EventReferenceRequest) + result = params.get_trackable_order_by(result, EventReferenceRequest) + result = result.order_by(EventReferenceRequest.created_at.desc()) + return result def get_reference_requests_incoming_badge_query(admin_unit): diff --git a/project/services/search_params.py b/project/services/search_params.py index f849d09..1b43f29 100644 --- a/project/services/search_params.py +++ b/project/services/search_params.py @@ -63,6 +63,13 @@ class EventReferenceSearchParams(TrackableSearchParams): self.admin_unit_id = None +class EventReferenceRequestSearchParams(TrackableSearchParams): + def __init__(self): + super().__init__() + self.admin_unit_id = None + self.review_status = None + + class AdminUnitSearchParams(TrackableSearchParams): def __init__(self): super().__init__() diff --git a/project/views/event.py b/project/views/event.py index 1ba0073..dab976b 100644 --- a/project/views/event.py +++ b/project/views/event.py @@ -45,7 +45,10 @@ from project.services.event import ( ) from project.utils import get_event_category_name, get_place_str from project.views.event_suggestion import send_event_suggestion_review_status_mail -from project.views.reference_request import handle_request_according_to_relation +from project.views.reference_request import ( + handle_request_according_to_relation, + send_reference_request_mails, +) from project.views.utils import ( flash_errors, flash_message, @@ -222,8 +225,11 @@ def event_create_for_admin_unit_id(id): reference_request.event_id = event.id reference_request.admin_unit_id = target_admin_unit_id db.session.add(reference_request) - msg = handle_request_according_to_relation(reference_request, event) + reference, msg = handle_request_according_to_relation( + reference_request, event + ) db.session.commit() + send_reference_request_mails(reference_request, reference) flash(msg, "success") return redirect(url_for("event_actions", event_id=event.id)) diff --git a/project/views/reference.py b/project/views/reference.py index a820894..e831374 100644 --- a/project/views/reference.py +++ b/project/views/reference.py @@ -128,7 +128,10 @@ def manage_admin_unit_references_incoming(id): @auth_required() def manage_admin_unit_references_outgoing(id): admin_unit = get_admin_unit_for_manage_or_404(id) - references = get_reference_outgoing_query(admin_unit).paginate() + + params = EventReferenceSearchParams() + params.admin_unit_id = admin_unit.id + references = get_reference_outgoing_query(params).paginate() return render_template( "manage/references_outgoing.html", diff --git a/project/views/reference_request.py b/project/views/reference_request.py index 36bf418..4eb236a 100644 --- a/project/views/reference_request.py +++ b/project/views/reference_request.py @@ -2,7 +2,6 @@ from flask import abort, flash, redirect, render_template, url_for from flask_babel import gettext from flask_security import auth_required from sqlalchemy.exc import SQLAlchemyError -from sqlalchemy.sql import desc from project import app, db from project.access import ( @@ -16,6 +15,7 @@ from project.models import ( EventReferenceRequest, EventReferenceRequestReviewStatus, ) +from project.models.event_reference import EventReference from project.services.admin_unit import ( get_admin_unit_by_id, get_admin_unit_relation, @@ -24,7 +24,9 @@ from project.services.admin_unit import ( from project.services.reference import ( create_event_reference_for_request, get_reference_requests_incoming_query, + get_reference_requests_outgoing_query, ) +from project.services.search_params import EventReferenceRequestSearchParams from project.views.utils import ( flash_errors, get_pagination_urls, @@ -37,11 +39,10 @@ from project.views.utils import ( @auth_required() def manage_admin_unit_reference_requests_incoming(id): admin_unit = get_admin_unit_for_manage_or_404(id) - requests = ( - get_reference_requests_incoming_query(admin_unit) - .order_by(desc(EventReferenceRequest.created_at)) - .paginate() - ) + + params = EventReferenceRequestSearchParams() + params.admin_unit_id = admin_unit.id + requests = get_reference_requests_incoming_query(params).paginate() return render_template( "manage/reference_requests_incoming.html", @@ -55,12 +56,10 @@ def manage_admin_unit_reference_requests_incoming(id): @auth_required() def manage_admin_unit_reference_requests_outgoing(id): admin_unit = get_admin_unit_for_manage_or_404(id) - requests = ( - EventReferenceRequest.query.join(Event) - .filter(Event.admin_unit_id == admin_unit.id) - .order_by(desc(EventReferenceRequest.created_at)) - .paginate() - ) + + params = EventReferenceRequestSearchParams() + params.admin_unit_id = admin_unit.id + requests = get_reference_requests_outgoing_query(params).paginate() return render_template( "manage/reference_requests_outgoing.html", @@ -99,15 +98,18 @@ def event_reference_request_create(event_id): ) if form.validate_on_submit(): - request = EventReferenceRequest() - form.populate_obj(request) - request.event = event + reference_request = EventReferenceRequest() + form.populate_obj(reference_request) + reference_request.event = event try: - db.session.add(request) + db.session.add(reference_request) - msg = handle_request_according_to_relation(request, event) + reference, msg = handle_request_according_to_relation( + reference_request, event + ) db.session.commit() + send_reference_request_mails(reference_request, reference) flash(msg, "success") return redirect( url_for( @@ -127,30 +129,62 @@ def event_reference_request_create(event_id): def handle_request_according_to_relation( request: EventReferenceRequest, event: Event ) -> str: - admin_unit = get_admin_unit_by_id(request.admin_unit_id) - relation = get_admin_unit_relation(request.admin_unit_id, event.admin_unit_id) + admin_unit = ( + request.admin_unit + if request.admin_unit + else get_admin_unit_by_id(request.admin_unit_id) + ) + relation = get_admin_unit_relation(admin_unit.id, event.admin_unit_id) auto_verify = relation and relation.auto_verify_event_reference_requests + reference = None if auto_verify: request.review_status = EventReferenceRequestReviewStatus.verified reference = create_event_reference_for_request(request) - send_auto_reference_inbox_mails(reference) + msg = gettext( "%(organization)s accepted your reference request", organization=admin_unit.name, ) else: request.review_status = EventReferenceRequestReviewStatus.inbox - send_reference_request_inbox_mails(request) + msg = gettext( "Reference request to %(organization)s successfully created. You will be notified after the other organization reviews the event.", organization=admin_unit.name, ) - return msg + return reference, msg -def send_member_reference_request_verify_mails( +def send_reference_request_mails( + request: EventReferenceRequest, reference: EventReference +): + if reference: + _send_auto_reference_mails(reference) + else: + _send_reference_request_inbox_mails(request) + + +def _send_reference_request_inbox_mails(request): + _send_member_reference_request_verify_mails( + request.admin_unit_id, + gettext("New reference request"), + "reference_request_notice", + request=request, + ) + + +def _send_auto_reference_mails(reference): + _send_member_reference_request_verify_mails( + reference.admin_unit_id, + gettext("New reference automatically verified"), + "reference_auto_verified_notice", + reference=reference, + ) + + +def _send_member_reference_request_verify_mails( admin_unit_id, subject, template, **context ): # Benachrichtige alle Mitglieder der AdminUnit, die Requests verifizieren können @@ -160,21 +194,3 @@ def send_member_reference_request_verify_mails( emails = list(map(lambda member: member.user.email, members)) send_mails_async(emails, subject, template, **context) - - -def send_reference_request_inbox_mails(request): - send_member_reference_request_verify_mails( - request.admin_unit_id, - gettext("New reference request"), - "reference_request_notice", - request=request, - ) - - -def send_auto_reference_inbox_mails(reference): - send_member_reference_request_verify_mails( - reference.admin_unit_id, - gettext("New reference automatically verified"), - "reference_auto_verified_notice", - reference=reference, - ) diff --git a/project/views/reference_request_review.py b/project/views/reference_request_review.py index 5dcb9c9..dd58390 100644 --- a/project/views/reference_request_review.py +++ b/project/views/reference_request_review.py @@ -44,12 +44,6 @@ def event_reference_request_review(id): if form.validate_on_submit(): form.populate_obj(request) - if request.review_status != EventReferenceRequestReviewStatus.rejected: - request.rejection_reason = None - - if request.rejection_reason == 0: - request.rejection_reason = None - try: if request.review_status == EventReferenceRequestReviewStatus.verified: reference = create_event_reference_for_request(request) diff --git a/project/views/verification_request_review.py b/project/views/verification_request_review.py index 688f5e2..789d011 100644 --- a/project/views/verification_request_review.py +++ b/project/views/verification_request_review.py @@ -38,12 +38,6 @@ def admin_unit_verification_request_review(id): if form.validate_on_submit(): form.populate_obj(request) - if request.review_status != AdminUnitVerificationRequestReviewStatus.rejected: - request.rejection_reason = None - - if request.rejection_reason == 0: - request.rejection_reason = None - try: if ( request.review_status diff --git a/tests/api/test_event_reference_request.py b/tests/api/test_event_reference_request.py new file mode 100644 index 0000000..d110721 --- /dev/null +++ b/tests/api/test_event_reference_request.py @@ -0,0 +1,126 @@ +from tests.seeder import Seeder +from tests.utils import UtilActions + + +def test_read(client, seeder: Seeder, utils: UtilActions): + user_id, admin_unit_id = seeder.setup_api_access() + ( + other_user_id, + other_admin_unit_id, + event_id, + reference_request_id, + ) = seeder.create_outgoing_reference_request(admin_unit_id) + + url = utils.get_url("api_v1_event_reference_request", id=reference_request_id) + utils.get_json_ok(url) + + +def test_read_noAccess(client, seeder: Seeder, utils: UtilActions): + user_id, admin_unit_id = seeder.setup_api_access() + third_user_id = seeder.create_user("third@test.de") + third_admin_unit_id = seeder.create_admin_unit(third_user_id, "Third Crew") + ( + other_user_id, + other_admin_unit_id, + event_id, + reference_request_id, + ) = seeder.create_outgoing_reference_request(third_admin_unit_id) + + url = utils.get_url("api_v1_event_reference_request", id=reference_request_id) + response = utils.get_json(url) + utils.assert_response_unauthorized(response) + + +def test_delete(client, app, db, seeder: Seeder, utils: UtilActions): + user_id, admin_unit_id = seeder.setup_api_access() + ( + other_user_id, + other_admin_unit_id, + event_id, + reference_request_id, + ) = seeder.create_outgoing_reference_request(admin_unit_id) + + url = utils.get_url("api_v1_event_reference_request", id=reference_request_id) + response = utils.delete(url) + utils.assert_response_no_content(response) + + with app.app_context(): + from project.models import EventReferenceRequest + + reference = db.session.get(EventReferenceRequest, reference_request_id) + assert reference is None + + +def test_verify(client, app, db, seeder: Seeder, utils: UtilActions): + user_id, admin_unit_id = seeder.setup_api_access() + ( + other_user_id, + other_admin_unit_id, + event_id, + reference_request_id, + ) = seeder.create_incoming_reference_request(admin_unit_id) + + url = utils.get_url( + "api_v1_event_reference_request_verify", id=reference_request_id + ) + data = { + "rating": 70, + } + response = utils.post_json(url, data) + utils.assert_response_created(response) + assert "id" in response.json + + with app.app_context(): + from project.models import ( + EventReference, + EventReferenceRequest, + EventReferenceRequestReviewStatus, + ) + + reference_request = db.session.get(EventReferenceRequest, reference_request_id) + assert ( + reference_request.review_status + == EventReferenceRequestReviewStatus.verified + ) + + reference = db.session.get(EventReference, int(response.json["id"])) + assert reference is not None + assert reference.admin_unit_id == admin_unit_id + assert reference.event_id == event_id + assert reference.rating == 70 + + +def test_reject(client, app, db, seeder: Seeder, utils: UtilActions): + user_id, admin_unit_id = seeder.setup_api_access() + ( + other_user_id, + other_admin_unit_id, + event_id, + reference_request_id, + ) = seeder.create_incoming_reference_request(admin_unit_id) + + url = utils.get_url( + "api_v1_event_reference_request_reject", id=reference_request_id + ) + data = { + "rejection_reason": "duplicate", + } + response = utils.post_json(url, data) + utils.assert_response_no_content(response) + + with app.app_context(): + from project.models import ( + EventReferenceRequest, + EventReferenceRequestRejectionReason, + EventReferenceRequestReviewStatus, + ) + + reference_request = db.session.get(EventReferenceRequest, reference_request_id) + assert ( + reference_request.review_status + == EventReferenceRequestReviewStatus.rejected + ) + assert ( + reference_request.rejection_reason + == EventReferenceRequestRejectionReason.duplicate + ) diff --git a/tests/api/test_organization.py b/tests/api/test_organization.py index a8d3712..529b2bc 100644 --- a/tests/api/test_organization.py +++ b/tests/api/test_organization.py @@ -508,6 +508,160 @@ def test_references_outgoing(client, seeder: Seeder, utils: UtilActions): utils.get_json_ok(url) +def test_reference_requests_incoming(client, seeder: Seeder, utils: UtilActions): + user_id, admin_unit_id = seeder.setup_api_access() + seeder.create_incoming_reference_request(admin_unit_id) + + url = utils.get_url( + "api_v1_organization_incoming_event_reference_request_list", + id=admin_unit_id, + ) + utils.get_json_ok(url) + + +def test_reference_requests_outgoing(client, seeder: Seeder, utils: UtilActions): + user_id, admin_unit_id = seeder.setup_api_access() + seeder.create_outgoing_reference_request(admin_unit_id) + + url = utils.get_url( + "api_v1_organization_outgoing_event_reference_request_list", + id=admin_unit_id, + ) + utils.get_json_ok(url) + + +def test_reference_requests_outgoing_post( + client, app, seeder: Seeder, utils: UtilActions, db, mocker +): + mail_mock = utils.mock_send_mails_async(mocker) + user_id, admin_unit_id = seeder.setup_api_access() + other_user_id = seeder.create_user("other@test.de") + other_admin_unit_id = seeder.create_admin_unit(other_user_id, "Other Crew") + event_id = seeder.create_event(admin_unit_id) + + url = utils.get_url( + "api_v1_organization_outgoing_event_reference_request_list", + id=admin_unit_id, + ) + data = { + "event": {"id": event_id}, + "target_organization": {"id": other_admin_unit_id}, + } + + response = utils.post_json(url, data) + utils.assert_response_created(response) + assert "id" in response.json + utils.assert_send_mail_called(mail_mock, "other@test.de") + + with app.app_context(): + from project.models import ( + EventReferenceRequest, + EventReferenceRequestReviewStatus, + ) + + reference_request = db.session.get( + EventReferenceRequest, int(response.json["id"]) + ) + assert reference_request is not None + assert reference_request.admin_unit_id == other_admin_unit_id + assert reference_request.event_id == event_id + assert ( + reference_request.review_status == EventReferenceRequestReviewStatus.inbox + ) + + +def test_reference_requests_outgoing_post_autoVerify( + client, app, seeder: Seeder, utils: UtilActions, db, mocker +): + mail_mock = utils.mock_send_mails_async(mocker) + user_id, admin_unit_id = seeder.setup_api_access() + event_id = seeder.create_event(admin_unit_id) + other_user_id = seeder.create_user("other@test.de") + other_admin_unit_id = seeder.create_admin_unit(other_user_id, "Other Crew") + seeder.create_admin_unit_relation(other_admin_unit_id, admin_unit_id, True) + + url = utils.get_url( + "api_v1_organization_outgoing_event_reference_request_list", + id=admin_unit_id, + ) + data = { + "event": {"id": event_id}, + "target_organization": {"id": other_admin_unit_id}, + } + + response = utils.post_json(url, data) + utils.assert_response_created(response) + assert "id" in response.json + utils.assert_send_mail_called(mail_mock, "other@test.de") + + with app.app_context(): + from project.models import ( + EventReference, + EventReferenceRequest, + EventReferenceRequestReviewStatus, + ) + + reference_request = db.session.get( + EventReferenceRequest, int(response.json["id"]) + ) + assert reference_request is not None + assert reference_request.admin_unit_id == other_admin_unit_id + assert reference_request.event_id == event_id + assert ( + reference_request.review_status + == EventReferenceRequestReviewStatus.verified + ) + + reference = ( + EventReference.query.filter( + EventReference.admin_unit_id == other_admin_unit_id + ) + .filter(EventReference.event_id == event_id) + .first() + ) + assert reference is not None + + +def test_reference_requests_outgoing_post_foreignEvent( + client, app, seeder: Seeder, utils: UtilActions, db +): + user_id, admin_unit_id = seeder.setup_api_access() + other_admin_unit_id = seeder.create_admin_unit(user_id, "Other Crew") + event_id = seeder.create_event(other_admin_unit_id) + + url = utils.get_url( + "api_v1_organization_outgoing_event_reference_request_list", + id=admin_unit_id, + ) + data = { + "event": {"id": event_id}, + "target_organization": {"id": other_admin_unit_id}, + } + + response = utils.post_json(url, data) + utils.assert_response_notFound(response) + + +def test_reference_requests_outgoing_post_draft( + client, app, seeder: Seeder, utils: UtilActions, db +): + user_id, admin_unit_id = seeder.setup_api_access() + other_admin_unit_id = seeder.create_admin_unit(user_id, "Other Crew") + event_id = seeder.create_event(admin_unit_id, draft=True) + + url = utils.get_url( + "api_v1_organization_outgoing_event_reference_request_list", + id=admin_unit_id, + ) + data = { + "event": {"id": event_id}, + "target_organization": {"id": other_admin_unit_id}, + } + + response = utils.post_json(url, data) + utils.assert_response_unauthorized(response) + + def test_outgoing_relation_read(client, seeder: Seeder, utils: UtilActions): user_id, admin_unit_id = seeder.setup_api_access() ( diff --git a/tests/seeder.py b/tests/seeder.py index 4fa27fd..633135f 100644 --- a/tests/seeder.py +++ b/tests/seeder.py @@ -758,6 +758,15 @@ class Seeder(object): reference_request_id = self.create_reference_request(event_id, admin_unit_id) return (other_user_id, other_admin_unit_id, event_id, reference_request_id) + def create_outgoing_reference_request(self, admin_unit_id): + other_user_id = self.create_user("other@test.de") + other_admin_unit_id = self.create_admin_unit(other_user_id, "Other Crew") + event_id = self.create_event(admin_unit_id) + reference_request_id = self.create_reference_request( + event_id, other_admin_unit_id + ) + return (other_user_id, other_admin_unit_id, event_id, reference_request_id) + def get_now_by_minute(self): from datetime import datetime diff --git a/tests/views/test_reference_request.py b/tests/views/test_reference_request.py index 518f1cf..4756d3b 100644 --- a/tests/views/test_reference_request.py +++ b/tests/views/test_reference_request.py @@ -157,11 +157,7 @@ def test_admin_unit_reference_requests_incoming(client, seeder, utils): def test_admin_unit_reference_requests_outgoing(client, seeder, utils): user_id, admin_unit_id = seeder.setup_base() - event_id = seeder.create_event(admin_unit_id) - - other_user_id = seeder.create_user("other@test.de") - other_admin_unit_id = seeder.create_admin_unit(other_user_id, "Other Crew") - seeder.create_reference_request(event_id, other_admin_unit_id) + seeder.create_outgoing_reference_request(admin_unit_id) utils.get_endpoint_ok( "manage_admin_unit_reference_requests_outgoing", id=admin_unit_id