From 668d97c56b1081ee907038f301ff3347ed81c02c Mon Sep 17 00:00:00 2001 From: Daniel Grams Date: Thu, 13 Jul 2023 22:41:41 +0200 Subject: [PATCH] API: organization-verification-requests #522 --- messages.pot | 57 +++--- project/api/__init__.py | 6 + .../api/event_reference_request/schemas.py | 6 - project/api/organization/resources.py | 98 +++++++++- .../__init__.py | 0 .../resources.py | 156 +++++++++++++++ .../schemas.py | 119 ++++++++++++ project/services/search_params.py | 8 + project/services/verification.py | 50 ++++- .../translations/de/LC_MESSAGES/messages.mo | Bin 46018 -> 46118 bytes .../translations/de/LC_MESSAGES/messages.po | 57 +++--- .../translations/en/LC_MESSAGES/messages.mo | Bin 4079 -> 4079 bytes .../translations/en/LC_MESSAGES/messages.po | 57 +++--- project/views/verification_request.py | 37 ++-- project/views/verification_request_review.py | 2 +- tests/api/test_organization.py | 81 ++++++++ .../test_organization_verification_request.py | 180 ++++++++++++++++++ tests/seeder.py | 48 +++-- 18 files changed, 835 insertions(+), 127 deletions(-) create mode 100644 project/api/organization_verification_request/__init__.py create mode 100644 project/api/organization_verification_request/resources.py create mode 100644 project/api/organization_verification_request/schemas.py create mode 100644 tests/api/test_organization_verification_request.py diff --git a/messages.pot b/messages.pot index abb46e2..35f58c2 100644 --- a/messages.pot +++ b/messages.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2023-07-09 21:57+0200\n" +"POT-Creation-Date: 2023-07-13 15:39+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -201,7 +201,7 @@ msgstr "" msgid "message" msgstr "" -#: project/api/organization/resources.py:479 +#: project/api/organization/resources.py:645 #: project/views/admin_unit_member_invitation.py:89 msgid "You have received an invitation" msgstr "" @@ -2561,7 +2561,7 @@ msgstr "" #: project/views/admin.py:85 project/views/admin_unit.py:202 #: project/views/admin_unit.py:235 project/views/manage.py:315 -#: project/views/verification_request.py:167 +#: project/views/verification_request.py:168 msgid "Entered name does not match organization name" msgstr "" @@ -2650,31 +2650,31 @@ msgstr "" msgid "Invitation successfully deleted" msgstr "" -#: project/views/event.py:205 +#: project/views/event.py:208 msgid "Event successfully published" msgstr "" -#: project/views/event.py:207 +#: project/views/event.py:210 msgid "Draft successfully saved" msgstr "" -#: project/views/event.py:209 +#: project/views/event.py:212 msgid "Event successfully planned" msgstr "" -#: project/views/event.py:266 +#: project/views/event.py:272 msgid "Event successfully updated" msgstr "" -#: project/views/event.py:292 +#: project/views/event.py:298 msgid "Event successfully deleted" msgstr "" -#: project/views/event.py:457 +#: project/views/event.py:463 msgid "Referenced event changed" msgstr "" -#: project/views/event.py:480 +#: project/views/event.py:486 msgid "New event report" msgstr "" @@ -2699,7 +2699,7 @@ msgid "Event suggestion successfully rejected" msgstr "" #: project/views/event_suggestion.py:87 -#: project/views/reference_request_review.py:135 +#: project/views/reference_request_review.py:129 msgid "Event review status updated" msgstr "" @@ -2779,45 +2779,44 @@ msgstr "" msgid "Reference successfully updated" msgstr "" -#: project/views/reference.py:153 +#: project/views/reference.py:156 msgid "Reference successfully deleted" msgstr "" -#: project/views/reference_request.py:138 +#: project/views/reference_request.py:145 #, python-format msgid "%(organization)s accepted your reference request" msgstr "" -#: project/views/reference_request.py:145 +#: project/views/reference_request.py:152 #, python-format msgid "" "Reference request to %(organization)s successfully created. You will be " "notified after the other organization reviews the event." msgstr "" -#: project/views/reference_request.py:168 +#: project/views/reference_request.py:172 msgid "New reference request" msgstr "" -#: project/views/reference_request.py:177 +#: project/views/reference_request.py:181 msgid "New reference automatically verified" msgstr "" #: project/views/reference_request_review.py:34 -#: project/views/verification_request_review.py:28 msgid "Request already verified" msgstr "" -#: project/views/reference_request_review.py:57 +#: project/views/reference_request_review.py:51 msgid "Reference successfully created" msgstr "" -#: project/views/reference_request_review.py:65 +#: project/views/reference_request_review.py:59 msgid "Request successfully updated" msgstr "" -#: project/views/reference_request_review.py:88 -#: project/views/verification_request_review.py:79 +#: project/views/reference_request_review.py:82 +#: project/views/verification_request_review.py:73 #, python-format msgid "" "If all upcoming reference requests of %(admin_unit_name)s should be " @@ -2863,29 +2862,33 @@ msgid "" " the invitation was sent to." msgstr "" -#: project/views/verification_request.py:131 +#: project/views/verification_request.py:132 msgid "" "Request successfully created. You will be notified after the other " "organization reviewed the request." msgstr "" -#: project/views/verification_request.py:172 +#: project/views/verification_request.py:173 msgid "Verification request successfully deleted" msgstr "" -#: project/views/verification_request.py:205 +#: project/views/verification_request.py:206 msgid "New verification request" msgstr "" -#: project/views/verification_request_review.py:60 +#: project/views/verification_request_review.py:28 +msgid "Verification request already verified" +msgstr "" + +#: project/views/verification_request_review.py:54 msgid "Organization successfully verified" msgstr "" -#: project/views/verification_request_review.py:62 +#: project/views/verification_request_review.py:56 msgid "Verification request successfully updated" msgstr "" -#: project/views/verification_request_review.py:115 +#: project/views/verification_request_review.py:109 msgid "Verification request review status updated" msgstr "" diff --git a/project/api/__init__.py b/project/api/__init__.py index e87d3b9..e8969aa 100644 --- a/project/api/__init__.py +++ b/project/api/__init__.py @@ -113,6 +113,9 @@ class RestApi(Api): if len(errors) > 0: data["errors"] = errors + elif isinstance(arg, str): + if arg: + data["message"] = arg class DocSecurityPlugin(BasePlugin): @@ -135,6 +138,8 @@ scope_list = [ "eventreference:write", "eventreferencerequest:read", "eventreferencerequest:write", + "organizationverificationrequest:read", + "organizationverificationrequest:write", ] scopes = {k: get_localized_scope(k) for v, k in enumerate(scope_list)} @@ -226,6 +231,7 @@ import project.api.event_reference_request.resources import project.api.organization.resources import project.api.organization_invitation.resources import project.api.organization_relation.resources +import project.api.organization_verification_request.resources import project.api.organizer.resources import project.api.place.resources import project.api.user.resources diff --git a/project/api/event_reference_request/schemas.py b/project/api/event_reference_request/schemas.py index ada812e..0cb8460 100644 --- a/project/api/event_reference_request/schemas.py +++ b/project/api/event_reference_request/schemas.py @@ -1,7 +1,6 @@ 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, @@ -55,11 +54,6 @@ class EventReferenceRequestSchema( 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 ): diff --git a/project/api/organization/resources.py b/project/api/organization/resources.py index 120f7c0..68ebfd6 100644 --- a/project/api/organization/resources.py +++ b/project/api/organization/resources.py @@ -70,6 +70,12 @@ from project.api.organization_relation.schemas import ( OrganizationRelationListResponseSchema, OrganizationRelationSchema, ) +from project.api.organization_verification_request.schemas import ( + OrganizationVerificationRequestIdSchema, + OrganizationVerificationRequestListRequestSchema, + OrganizationVerificationRequestListResponseSchema, + OrganizationVerificationRequestPostRequestSchema, +) from project.api.organizer.schemas import ( OrganizerIdSchema, OrganizerListRequestSchema, @@ -85,6 +91,9 @@ from project.api.place.schemas import ( from project.api.resources import BaseResource, require_api_access from project.models import AdminUnit, Event, PublicStatus from project.models.admin_unit import AdminUnitInvitation, AdminUnitRelation +from project.models.admin_unit_verification_request import ( + AdminUnitVerificationRequestReviewStatus, +) from project.services.admin_unit import ( get_admin_unit_invitation_query, get_admin_unit_query, @@ -105,6 +114,7 @@ from project.services.reference import ( ) from project.services.search_params import ( AdminUnitSearchParams, + AdminUnitVerificationRequestSearchParams, EventPlaceSearchParams, EventReferenceRequestSearchParams, EventReferenceSearchParams, @@ -112,11 +122,17 @@ from project.services.search_params import ( OrganizerSearchParams, TrackableSearchParams, ) +from project.services.verification import ( + admin_unit_can_verify_admin_unit, + get_verification_requests_incoming_query, + get_verification_requests_outgoing_query, +) 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 +from project.views.verification_request import send_verification_request_inbox_mails class OrganizationResource(BaseResource): @@ -461,6 +477,76 @@ class OrganizationOutgoingEventReferenceRequestListResource(BaseResource): return reference_request, 201 +class OrganizationIncomingOrganizationVerificationRequestListResource(BaseResource): + @doc( + summary="List incoming organization verification requests of organization", + tags=["Organizations", "Organization Verification Requests"], + ) + @use_kwargs(OrganizationVerificationRequestListRequestSchema, location=("query")) + @marshal_with(OrganizationVerificationRequestListResponseSchema) + @require_api_access("organizationverificationrequest:read") + def get(self, id, **kwargs): + login_api_user_or_401() + admin_unit = get_admin_unit_for_manage_or_404(id) + + params = AdminUnitVerificationRequestSearchParams() + params.load_from_request(**kwargs) + params.target_admin_unit_id = admin_unit.id + pagination = get_verification_requests_incoming_query(params).paginate() + return pagination + + +class OrganizationOutgoingOrganizationVerificationRequestListResource(BaseResource): + @doc( + summary="List outgoing organization verification requests of organization", + tags=["Organizations", "Organization Verification Requests"], + ) + @use_kwargs(OrganizationVerificationRequestListRequestSchema, location=("query")) + @marshal_with(OrganizationVerificationRequestListResponseSchema) + @require_api_access("organizationverificationrequest:read") + def get(self, id, **kwargs): + login_api_user_or_401() + admin_unit = get_admin_unit_for_manage_or_404(id) + + params = AdminUnitVerificationRequestSearchParams() + params.load_from_request(**kwargs) + params.source_admin_unit_id = admin_unit.id + pagination = get_verification_requests_outgoing_query(params).paginate() + return pagination + + @doc( + summary="Add verification request", + tags=["Organizations", "Organization Verification Requests"], + ) + @use_kwargs( + OrganizationVerificationRequestPostRequestSchema, location="json", apply=False + ) + @marshal_with(OrganizationVerificationRequestIdSchema, 201) + @require_api_access("organizationverificationrequest:write") + def post(self, id): + login_api_user_or_401() + admin_unit = get_admin_unit_for_manage_or_404(id) + access_or_401(admin_unit, "verification_request:create") + + verification_request = self.create_instance( + OrganizationVerificationRequestPostRequestSchema, + source_admin_unit_id=admin_unit.id, + review_status=AdminUnitVerificationRequestReviewStatus.inbox, + ) + target_admin_unit = verification_request.target_admin_unit + + if not admin_unit_can_verify_admin_unit( + admin_unit, target_admin_unit + ): # pragma: no cover + abort(401) + + db.session.add(verification_request) + db.session.commit() + send_verification_request_inbox_mails(verification_request) + + return verification_request, 201 + + class OrganizationOutgoingRelationListResource(BaseResource): @doc( summary="List outgoing relations of organization", @@ -708,7 +794,6 @@ add_api_resource( "/organizations//event-references/outgoing", "api_v1_organization_outgoing_event_reference_list", ) - add_api_resource( OrganizationIncomingEventReferenceRequestListResource, "/organizations//event-reference-requests/incoming", @@ -719,7 +804,16 @@ add_api_resource( "/organizations//event-reference-requests/outgoing", "api_v1_organization_outgoing_event_reference_request_list", ) - +add_api_resource( + OrganizationIncomingOrganizationVerificationRequestListResource, + "/organizations//organization-verification-requests/incoming", + "api_v1_organization_incoming_organization_verification_request_list", +) +add_api_resource( + OrganizationOutgoingOrganizationVerificationRequestListResource, + "/organizations//organization-verification-requests/outgoing", + "api_v1_organization_outgoing_organization_verification_request_list", +) add_api_resource( OrganizationOutgoingRelationListResource, "/organizations//relations/outgoing", diff --git a/project/api/organization_verification_request/__init__.py b/project/api/organization_verification_request/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/project/api/organization_verification_request/resources.py b/project/api/organization_verification_request/resources.py new file mode 100644 index 0000000..c652db3 --- /dev/null +++ b/project/api/organization_verification_request/resources.py @@ -0,0 +1,156 @@ +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.organization_relation.schemas import OrganizationRelationIdSchema +from project.api.organization_verification_request.schemas import ( + OrganizationVerificationRequestRejectRequestSchema, + OrganizationVerificationRequestSchema, + OrganizationVerificationRequestVerifyRequestSchema, +) +from project.api.resources import BaseResource, require_api_access +from project.models import AdminUnitVerificationRequest +from project.models.admin_unit_verification_request import ( + AdminUnitVerificationRequestReviewStatus, +) +from project.services.admin_unit import upsert_admin_unit_relation +from project.views.verification_request_review import ( + send_verification_request_review_status_mails, +) + + +class OrganizationVerificationRequestResource(BaseResource): + @doc( + summary="Get organization verification request", + tags=["Organization Verification Requests"], + ) + @marshal_with(OrganizationVerificationRequestSchema) + @require_api_access("organizationverificationrequest:read") + def get(self, id): + login_api_user_or_401() + verification_request = AdminUnitVerificationRequest.query.get_or_404(id) + + if not has_access( + verification_request.source_admin_unit, "verification_request:read" + ) and not has_access( + verification_request.target_admin_unit, "verification_request:verify" + ): + abort(401) + + return verification_request + + @doc( + summary="Delete verification request", + tags=["Organization Verification Requests"], + ) + @marshal_with(None, 204) + @require_api_access("organizationverificationrequest:write") + def delete(self, id): + login_api_user_or_401() + verification_request = AdminUnitVerificationRequest.query.get_or_404(id) + access_or_401( + verification_request.source_admin_unit, "verification_request:delete" + ) + + db.session.delete(verification_request) + db.session.commit() + + return make_response("", 204) + + +class OrganizationVerificationRequestVerifyResource(BaseResource): + @doc( + summary="Verify organization verification request. Returns relation id.", + tags=["Organization Verification Requests"], + ) + @use_kwargs( + OrganizationVerificationRequestVerifyRequestSchema, location="json", apply=True + ) + @marshal_with(OrganizationRelationIdSchema, 201) + @require_api_access("organizationverificationrequest:write") + def post(self, id, **kwargs): + login_api_user_or_401() + verification_request = AdminUnitVerificationRequest.query.get_or_404(id) + access_or_401( + verification_request.target_admin_unit, "verification_request:verify" + ) + + if ( + verification_request.review_status + == AdminUnitVerificationRequestReviewStatus.verified + ): + raise ValidationError("Verification request already verified") + + verification_request.review_status = ( + AdminUnitVerificationRequestReviewStatus.verified + ) + + relation = upsert_admin_unit_relation( + verification_request.target_admin_unit_id, + verification_request.source_admin_unit_id, + ) + relation.verify = True + relation.auto_verify_event_reference_requests = kwargs.get( + "auto_verify_event_reference_requests", + relation.auto_verify_event_reference_requests, + ) + db.session.commit() + + send_verification_request_review_status_mails(verification_request) + return relation, 201 + + +class OrganizationVerificationRequestRejectResource(BaseResource): + @doc( + summary="Reject organization verification request", + tags=["Organization Verification Requests"], + ) + @use_kwargs( + OrganizationVerificationRequestRejectRequestSchema, location="json", apply=False + ) + @marshal_with(None, 204) + @require_api_access("organizationverificationrequest:write") + def post(self, id): + login_api_user_or_401() + verification_request = AdminUnitVerificationRequest.query.get_or_404(id) + access_or_401( + verification_request.target_admin_unit, "verification_request:verify" + ) + + if ( + verification_request.review_status + == AdminUnitVerificationRequestReviewStatus.verified + ): # pragma: no cover + raise ValidationError("Verification request already verified") + + verification_request = self.update_instance( + OrganizationVerificationRequestRejectRequestSchema, + instance=verification_request, + ) + verification_request.review_status = ( + AdminUnitVerificationRequestReviewStatus.rejected + ) + db.session.commit() + + send_verification_request_review_status_mails(verification_request) + return make_response("", 204) + + +add_api_resource( + OrganizationVerificationRequestResource, + "/organization-verification-request/", + "api_v1_organization_verification_request", +) +add_api_resource( + OrganizationVerificationRequestVerifyResource, + "/organization-verification-request//verify", + "api_v1_organization_verification_request_verify", +) +add_api_resource( + OrganizationVerificationRequestRejectResource, + "/organization-verification-request//reject", + "api_v1_organization_verification_request_reject", +) diff --git a/project/api/organization_verification_request/schemas.py b/project/api/organization_verification_request/schemas.py new file mode 100644 index 0000000..6539776 --- /dev/null +++ b/project/api/organization_verification_request/schemas.py @@ -0,0 +1,119 @@ +from marshmallow import fields, validate +from marshmallow_enum import EnumField + +from project.api.organization.schemas import ( + OrganizationRefSchema, + OrganizationWriteIdSchema, +) +from project.api.schemas import ( + IdSchemaMixin, + PaginationRequestSchema, + PaginationResponseSchema, + SQLAlchemyBaseSchema, + TrackableRequestSchemaMixin, + TrackableSchemaMixin, +) +from project.models import AdminUnitVerificationRequest +from project.models.admin_unit_verification_request import ( + AdminUnitVerificationRequestRejectionReason, + AdminUnitVerificationRequestReviewStatus, +) + + +class OrganizationVerificationRequestModelSchema(SQLAlchemyBaseSchema): + class Meta: + model = AdminUnitVerificationRequest + load_instance = True + + +class OrganizationVerificationRequestIdSchema( + OrganizationVerificationRequestModelSchema, IdSchemaMixin +): + pass + + +class OrganizationVerificationRequestBaseSchemaMixin(TrackableSchemaMixin): + review_status = EnumField( + AdminUnitVerificationRequestReviewStatus, + load_default=AdminUnitVerificationRequestReviewStatus.inbox, + ) + rejection_reason = EnumField( + AdminUnitVerificationRequestRejectionReason, + ) + + +class OrganizationVerificationRequestRefSchema( + OrganizationVerificationRequestIdSchema, TrackableSchemaMixin +): + source_organization = fields.Nested( + OrganizationRefSchema, attribute="source_admin_unit" + ) + target_organization = fields.Nested( + OrganizationRefSchema, attribute="target_admin_unit" + ) + + +class OrganizationVerificationRequestSchema( + OrganizationVerificationRequestIdSchema, + OrganizationVerificationRequestBaseSchemaMixin, +): + source_organization = fields.Nested( + OrganizationRefSchema, attribute="source_admin_unit" + ) + target_organization = fields.Nested( + OrganizationRefSchema, attribute="target_admin_unit" + ) + + +class OrganizationVerificationRequestListRequestSchema( + PaginationRequestSchema, TrackableRequestSchemaMixin +): + sort = fields.Str( + metadata={"description": "Sort result items."}, + validate=validate.OneOf(["-created_at", "-updated_at", "-last_modified_at"]), + ) + + +class OrganizationVerificationRequestListResponseSchema(PaginationResponseSchema): + items = fields.List( + fields.Nested(OrganizationVerificationRequestRefSchema), + metadata={"description": "Organization verification requests"}, + ) + + +class OrganizationVerificationRequestWriteSchemaMixin(object): + target_organization = fields.Nested( + OrganizationWriteIdSchema, + attribute="target_admin_unit", + required=True, + metadata={"description": "Target organization."}, + ) + + +class OrganizationVerificationRequestPostRequestSchema( + OrganizationVerificationRequestModelSchema, + OrganizationVerificationRequestWriteSchemaMixin, +): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.make_post_schema() + + +class OrganizationVerificationRequestVerifyRequestSchema(SQLAlchemyBaseSchema): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.make_post_schema() + + auto_verify_event_reference_requests = fields.Bool() + + +class OrganizationVerificationRequestRejectRequestSchema( + OrganizationVerificationRequestModelSchema, +): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.make_post_schema() + + rejection_reason = EnumField( + AdminUnitVerificationRequestRejectionReason, + ) diff --git a/project/services/search_params.py b/project/services/search_params.py index 1b43f29..1f2973d 100644 --- a/project/services/search_params.py +++ b/project/services/search_params.py @@ -70,6 +70,14 @@ class EventReferenceRequestSearchParams(TrackableSearchParams): self.review_status = None +class AdminUnitVerificationRequestSearchParams(TrackableSearchParams): + def __init__(self): + super().__init__() + self.source_admin_unit_id = None + self.target_admin_unit_id = None + self.review_status = None + + class AdminUnitSearchParams(TrackableSearchParams): def __init__(self): super().__init__() diff --git a/project/services/verification.py b/project/services/verification.py index c6c4ffc..b881c53 100644 --- a/project/services/verification.py +++ b/project/services/verification.py @@ -5,18 +5,54 @@ from project.models import ( AdminUnitVerificationRequest, AdminUnitVerificationRequestReviewStatus, ) +from project.models.admin_unit import AdminUnit +from project.services.search_params import AdminUnitVerificationRequestSearchParams -def get_verification_requests_incoming_query(admin_unit): - return AdminUnitVerificationRequest.query.filter( - and_( - AdminUnitVerificationRequest.review_status - != AdminUnitVerificationRequestReviewStatus.verified, - AdminUnitVerificationRequest.target_admin_unit_id == admin_unit.id, - ) +def admin_unit_can_verify_admin_unit( + source_admin_unit: AdminUnit, target_admin_unit: AdminUnit +): + return ( + target_admin_unit.id != source_admin_unit.id + and target_admin_unit.can_verify_other + and target_admin_unit.incoming_verification_requests_allowed ) +def get_verification_requests_incoming_query( + params: AdminUnitVerificationRequestSearchParams, +): + result = AdminUnitVerificationRequest.query + + if params.target_admin_unit_id: + result = result.filter( + AdminUnitVerificationRequest.target_admin_unit_id + == params.target_admin_unit_id + ) + + result = params.get_trackable_query(result, AdminUnitVerificationRequest) + result = params.get_trackable_order_by(result, AdminUnitVerificationRequest) + result = result.order_by(AdminUnitVerificationRequest.created_at.desc()) + return result + + +def get_verification_requests_outgoing_query( + params: AdminUnitVerificationRequestSearchParams, +): + result = AdminUnitVerificationRequest.query + + if params.source_admin_unit_id: + result = result.filter( + AdminUnitVerificationRequest.source_admin_unit_id + == params.source_admin_unit_id + ) + + result = params.get_trackable_query(result, AdminUnitVerificationRequest) + result = params.get_trackable_order_by(result, AdminUnitVerificationRequest) + result = result.order_by(AdminUnitVerificationRequest.created_at.desc()) + return result + + def get_verification_requests_incoming_badge_query(admin_unit): return AdminUnitVerificationRequest.query.options( load_only(AdminUnitVerificationRequest.id) diff --git a/project/translations/de/LC_MESSAGES/messages.mo b/project/translations/de/LC_MESSAGES/messages.mo index d3fdc0f939f2ac8e3884ca009cd135793259e8ae..fd62890b85f9eedc9e780c71884a03f9d9e6657f 100644 GIT binary patch delta 9798 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\n" "Language: de\n" @@ -202,7 +202,7 @@ msgstr "." msgid "message" msgstr "message" -#: project/api/organization/resources.py:479 +#: project/api/organization/resources.py:645 #: project/views/admin_unit_member_invitation.py:89 msgid "You have received an invitation" msgstr "Du hast eine Einladung erhalten" @@ -2639,7 +2639,7 @@ msgstr "Organisation erfolgreich aktualisiert" #: project/views/admin.py:85 project/views/admin_unit.py:202 #: project/views/admin_unit.py:235 project/views/manage.py:315 -#: project/views/verification_request.py:167 +#: project/views/verification_request.py:168 msgid "Entered name does not match organization name" msgstr "Der eingegebene Name entspricht nicht dem Namen der Organisation" @@ -2731,31 +2731,31 @@ msgstr "Die eingegebene Email passt nicht zur Email der Einladung" msgid "Invitation successfully deleted" msgstr "Einladung erfolgreich gelöscht" -#: project/views/event.py:205 +#: project/views/event.py:208 msgid "Event successfully published" msgstr "Veranstaltung erfolgreich veröffentlicht" -#: project/views/event.py:207 +#: project/views/event.py:210 msgid "Draft successfully saved" msgstr "Entwurf erfolgreich gespeichert" -#: project/views/event.py:209 +#: project/views/event.py:212 msgid "Event successfully planned" msgstr "Veranstaltung erfolgreich geplant" -#: project/views/event.py:266 +#: project/views/event.py:272 msgid "Event successfully updated" msgstr "Veranstaltung erfolgreich aktualisiert" -#: project/views/event.py:292 +#: project/views/event.py:298 msgid "Event successfully deleted" msgstr "Veranstaltung erfolgreich gelöscht" -#: project/views/event.py:457 +#: project/views/event.py:463 msgid "Referenced event changed" msgstr "Empfohlene Veranstaltung wurde geändert" -#: project/views/event.py:480 +#: project/views/event.py:486 msgid "New event report" msgstr "Neue Meldung zu einer Veranstaltung" @@ -2780,7 +2780,7 @@ msgid "Event suggestion successfully rejected" msgstr "Veranstaltungsvorschlag erfolgreich abgelehnt" #: project/views/event_suggestion.py:87 -#: project/views/reference_request_review.py:135 +#: project/views/reference_request_review.py:129 msgid "Event review status updated" msgstr "Prüfungsstatus aktualisiert" @@ -2862,16 +2862,16 @@ msgstr "Veranstaltung erfolgreich empfohlen" msgid "Reference successfully updated" msgstr "Empfehlung erfolgreich empfohlen" -#: project/views/reference.py:153 +#: project/views/reference.py:156 msgid "Reference successfully deleted" msgstr "Empfehlung erfolgreich gelöscht" -#: project/views/reference_request.py:138 +#: project/views/reference_request.py:145 #, python-format msgid "%(organization)s accepted your reference request" msgstr "%(organization)s hat deine Empfehlungsanfrage akzeptiert" -#: project/views/reference_request.py:145 +#: project/views/reference_request.py:152 #, python-format msgid "" "Reference request to %(organization)s successfully created. You will be " @@ -2881,29 +2881,28 @@ msgstr "" "benachrichtigt, nachdem die andere Organisation die Veranstaltung geprüft" " hat." -#: project/views/reference_request.py:168 +#: project/views/reference_request.py:172 msgid "New reference request" msgstr "Neue Empfehlungsanfrage" -#: project/views/reference_request.py:177 +#: project/views/reference_request.py:181 msgid "New reference automatically verified" msgstr "Neue automatisch verifizierte Empfehlung" #: project/views/reference_request_review.py:34 -#: project/views/verification_request_review.py:28 msgid "Request already verified" msgstr "Empfehlungsanfrage ist bereits verifiziert" -#: project/views/reference_request_review.py:57 +#: project/views/reference_request_review.py:51 msgid "Reference successfully created" msgstr "Empfehlung erfolgreich erstellt" -#: project/views/reference_request_review.py:65 +#: project/views/reference_request_review.py:59 msgid "Request successfully updated" msgstr "Empfehlungsanfrage erfolgreich aktualisiert" -#: project/views/reference_request_review.py:88 -#: project/views/verification_request_review.py:79 +#: project/views/reference_request_review.py:82 +#: project/views/verification_request_review.py:73 #, python-format msgid "" "If all upcoming reference requests of %(admin_unit_name)s should be " @@ -2957,7 +2956,7 @@ msgstr "" "Die Einladung wurde für einen anderen Nutzer ausgestellt. Melde dich mit " "der Email-Adresse an, an die die Einladung geschickt wurde." -#: project/views/verification_request.py:131 +#: project/views/verification_request.py:132 msgid "" "Request successfully created. You will be notified after the other " "organization reviewed the request." @@ -2965,23 +2964,27 @@ msgstr "" "Verifizierungsanfrage erfolgreich erstellt. Du wirst benachrichtigt, " "nachdem die andere Organisation die Anfrage geprüft hat." -#: project/views/verification_request.py:172 +#: project/views/verification_request.py:173 msgid "Verification request successfully deleted" msgstr "Verifizierungsanfrage erfolgreich gelöscht" -#: project/views/verification_request.py:205 +#: project/views/verification_request.py:206 msgid "New verification request" msgstr "Neue Verifizierungsanfrage" -#: project/views/verification_request_review.py:60 +#: project/views/verification_request_review.py:28 +msgid "Verification request already verified" +msgstr "Verifizierungsanfrage ist bereits verifiziert" + +#: project/views/verification_request_review.py:54 msgid "Organization successfully verified" msgstr "Organisation erfolgreich verifiziert" -#: project/views/verification_request_review.py:62 +#: project/views/verification_request_review.py:56 msgid "Verification request successfully updated" msgstr "Verifizierungsanfrage erfolgreich aktualisiert" -#: project/views/verification_request_review.py:115 +#: project/views/verification_request_review.py:109 msgid "Verification request review status updated" msgstr "Prüfungsstatus der Verifizierungsanfrage aktualisiert" diff --git a/project/translations/en/LC_MESSAGES/messages.mo b/project/translations/en/LC_MESSAGES/messages.mo index 1fe580e4de8a777a94b0a48282a87570971854f5..61ad21b9c1a9618d76351138eabd03230e97deb1 100644 GIT binary patch delta 21 dcmaDa|6YE>D^3nWV+BJ~D`U&epE&=r0svh?2nYZG delta 21 dcmaDa|6YE>D^3mrO9dlCD^v5$pE&=r0sviQ2nqlI diff --git a/project/translations/en/LC_MESSAGES/messages.po b/project/translations/en/LC_MESSAGES/messages.po index 09f3dcd..86ab62e 100644 --- a/project/translations/en/LC_MESSAGES/messages.po +++ b/project/translations/en/LC_MESSAGES/messages.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2023-07-09 21:57+0200\n" +"POT-Creation-Date: 2023-07-13 15:39+0200\n" "PO-Revision-Date: 2021-04-30 15:04+0200\n" "Last-Translator: FULL NAME \n" "Language: en\n" @@ -202,7 +202,7 @@ msgstr "" msgid "message" msgstr "" -#: project/api/organization/resources.py:479 +#: project/api/organization/resources.py:645 #: project/views/admin_unit_member_invitation.py:89 msgid "You have received an invitation" msgstr "" @@ -2569,7 +2569,7 @@ msgstr "" #: project/views/admin.py:85 project/views/admin_unit.py:202 #: project/views/admin_unit.py:235 project/views/manage.py:315 -#: project/views/verification_request.py:167 +#: project/views/verification_request.py:168 msgid "Entered name does not match organization name" msgstr "" @@ -2658,31 +2658,31 @@ msgstr "" msgid "Invitation successfully deleted" msgstr "" -#: project/views/event.py:205 +#: project/views/event.py:208 msgid "Event successfully published" msgstr "" -#: project/views/event.py:207 +#: project/views/event.py:210 msgid "Draft successfully saved" msgstr "" -#: project/views/event.py:209 +#: project/views/event.py:212 msgid "Event successfully planned" msgstr "" -#: project/views/event.py:266 +#: project/views/event.py:272 msgid "Event successfully updated" msgstr "" -#: project/views/event.py:292 +#: project/views/event.py:298 msgid "Event successfully deleted" msgstr "" -#: project/views/event.py:457 +#: project/views/event.py:463 msgid "Referenced event changed" msgstr "" -#: project/views/event.py:480 +#: project/views/event.py:486 msgid "New event report" msgstr "" @@ -2707,7 +2707,7 @@ msgid "Event suggestion successfully rejected" msgstr "" #: project/views/event_suggestion.py:87 -#: project/views/reference_request_review.py:135 +#: project/views/reference_request_review.py:129 msgid "Event review status updated" msgstr "" @@ -2787,45 +2787,44 @@ msgstr "" msgid "Reference successfully updated" msgstr "" -#: project/views/reference.py:153 +#: project/views/reference.py:156 msgid "Reference successfully deleted" msgstr "" -#: project/views/reference_request.py:138 +#: project/views/reference_request.py:145 #, python-format msgid "%(organization)s accepted your reference request" msgstr "" -#: project/views/reference_request.py:145 +#: project/views/reference_request.py:152 #, python-format msgid "" "Reference request to %(organization)s successfully created. You will be " "notified after the other organization reviews the event." msgstr "" -#: project/views/reference_request.py:168 +#: project/views/reference_request.py:172 msgid "New reference request" msgstr "" -#: project/views/reference_request.py:177 +#: project/views/reference_request.py:181 msgid "New reference automatically verified" msgstr "" #: project/views/reference_request_review.py:34 -#: project/views/verification_request_review.py:28 msgid "Request already verified" msgstr "" -#: project/views/reference_request_review.py:57 +#: project/views/reference_request_review.py:51 msgid "Reference successfully created" msgstr "" -#: project/views/reference_request_review.py:65 +#: project/views/reference_request_review.py:59 msgid "Request successfully updated" msgstr "" -#: project/views/reference_request_review.py:88 -#: project/views/verification_request_review.py:79 +#: project/views/reference_request_review.py:82 +#: project/views/verification_request_review.py:73 #, python-format msgid "" "If all upcoming reference requests of %(admin_unit_name)s should be " @@ -2871,29 +2870,33 @@ msgid "" " the invitation was sent to." msgstr "" -#: project/views/verification_request.py:131 +#: project/views/verification_request.py:132 msgid "" "Request successfully created. You will be notified after the other " "organization reviewed the request." msgstr "" -#: project/views/verification_request.py:172 +#: project/views/verification_request.py:173 msgid "Verification request successfully deleted" msgstr "" -#: project/views/verification_request.py:205 +#: project/views/verification_request.py:206 msgid "New verification request" msgstr "" -#: project/views/verification_request_review.py:60 +#: project/views/verification_request_review.py:28 +msgid "Verification request already verified" +msgstr "" + +#: project/views/verification_request_review.py:54 msgid "Organization successfully verified" msgstr "" -#: project/views/verification_request_review.py:62 +#: project/views/verification_request_review.py:56 msgid "Verification request successfully updated" msgstr "" -#: project/views/verification_request_review.py:115 +#: project/views/verification_request_review.py:109 msgid "Verification request review status updated" msgstr "" diff --git a/project/views/verification_request.py b/project/views/verification_request.py index 91c8792..aa8090a 100644 --- a/project/views/verification_request.py +++ b/project/views/verification_request.py @@ -15,8 +15,15 @@ from project.models import ( ) from project.models.admin_unit import AdminUnit from project.services.admin_unit import get_admin_unit_query -from project.services.search_params import AdminUnitSearchParams -from project.services.verification import get_verification_requests_incoming_query +from project.services.search_params import ( + AdminUnitSearchParams, + AdminUnitVerificationRequestSearchParams, +) +from project.services.verification import ( + admin_unit_can_verify_admin_unit, + get_verification_requests_incoming_query, + get_verification_requests_outgoing_query, +) from project.views.utils import ( flash_errors, get_pagination_urls, @@ -32,11 +39,10 @@ from project.views.utils import ( @manage_required("verification_request:read") def manage_admin_unit_verification_requests_incoming(id): admin_unit = g.manage_admin_unit - requests = ( - get_verification_requests_incoming_query(admin_unit) - .order_by(AdminUnitVerificationRequest.created_at.desc()) - .paginate() - ) + + params = AdminUnitVerificationRequestSearchParams() + params.target_admin_unit_id = admin_unit.id + requests = get_verification_requests_incoming_query(params).paginate() return render_template( "manage/verification_requests_incoming.html", @@ -51,13 +57,10 @@ def manage_admin_unit_verification_requests_incoming(id): @manage_required("verification_request:read") def manage_admin_unit_verification_requests_outgoing(id): admin_unit = g.manage_admin_unit - requests = ( - AdminUnitVerificationRequest.query.filter( - AdminUnitVerificationRequest.source_admin_unit_id == admin_unit.id - ) - .order_by(AdminUnitVerificationRequest.created_at.desc()) - .paginate() - ) + + params = AdminUnitVerificationRequestSearchParams() + params.target_admin_unit_id = admin_unit.id + requests = get_verification_requests_outgoing_query(params).paginate() if not admin_unit.is_verified and requests.total == 0: return redirect( @@ -103,10 +106,8 @@ def manage_admin_unit_verification_requests_outgoing_create(id, target_id): admin_unit = g.manage_admin_unit target_admin_unit = AdminUnit.query.get_or_404(target_id) - if ( - target_admin_unit.id == admin_unit.id - or not target_admin_unit.can_verify_other - or not target_admin_unit.incoming_verification_requests_allowed + if not admin_unit_can_verify_admin_unit( + admin_unit, target_admin_unit ): # pragma: no cover return redirect( url_for( diff --git a/project/views/verification_request_review.py b/project/views/verification_request_review.py index 789d011..8741d78 100644 --- a/project/views/verification_request_review.py +++ b/project/views/verification_request_review.py @@ -25,7 +25,7 @@ def admin_unit_verification_request_review(id): access_or_401(request.target_admin_unit, "verification_request:verify") if request.review_status == AdminUnitVerificationRequestReviewStatus.verified: - flash(gettext("Request already verified"), "danger") + flash(gettext("Verification request already verified"), "danger") return redirect( url_for( "manage_admin_unit_verification_requests_incoming", diff --git a/tests/api/test_organization.py b/tests/api/test_organization.py index 529b2bc..067226e 100644 --- a/tests/api/test_organization.py +++ b/tests/api/test_organization.py @@ -662,6 +662,87 @@ def test_reference_requests_outgoing_post_draft( utils.assert_response_unauthorized(response) +def test_organization_verification_requests_incoming( + client, seeder: Seeder, utils: UtilActions +): + user_id, admin_unit_id = seeder.setup_api_access() + seeder.create_incoming_admin_unit_verification_request(admin_unit_id) + + url = utils.get_url( + "api_v1_organization_incoming_organization_verification_request_list", + id=admin_unit_id, + ) + utils.get_json_ok(url) + + +def test_organization_verification_requests_outgoing( + client, seeder: Seeder, utils: UtilActions +): + ( + verifier_user_id, + verifier_admin_unit_id, + unverified_user_id, + unverified_admin_unit_id, + ) = seeder.setup_admin_unit_missing_verification_scenario(api=True) + seeder.create_admin_unit_verification_request( + unverified_admin_unit_id, verifier_admin_unit_id + ) + + url = utils.get_url( + "api_v1_organization_outgoing_organization_verification_request_list", + id=unverified_admin_unit_id, + ) + utils.get_json_ok(url) + + +def test_organization_verification_requests_outgoing_post( + client, app, seeder: Seeder, utils: UtilActions, db, mocker +): + mail_mock = utils.mock_send_mails_async(mocker) + ( + verifier_user_id, + verifier_admin_unit_id, + unverified_user_id, + unverified_admin_unit_id, + ) = seeder.setup_admin_unit_missing_verification_scenario(api=True) + + url = utils.get_url( + "api_v1_organization_outgoing_organization_verification_request_list", + id=unverified_admin_unit_id, + ) + data = { + "target_organization": {"id": verifier_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, "test@test.de") + + with app.app_context(): + from project.models import ( + AdminUnitVerificationRequest, + AdminUnitVerificationRequestReviewStatus, + ) + + organization_verification_request = db.session.get( + AdminUnitVerificationRequest, int(response.json["id"]) + ) + assert organization_verification_request is not None + assert ( + organization_verification_request.source_admin_unit_id + == unverified_admin_unit_id + ) + assert ( + organization_verification_request.target_admin_unit_id + == verifier_admin_unit_id + ) + assert ( + organization_verification_request.review_status + == AdminUnitVerificationRequestReviewStatus.inbox + ) + + def test_outgoing_relation_read(client, seeder: Seeder, utils: UtilActions): user_id, admin_unit_id = seeder.setup_api_access() ( diff --git a/tests/api/test_organization_verification_request.py b/tests/api/test_organization_verification_request.py new file mode 100644 index 0000000..67cc4d7 --- /dev/null +++ b/tests/api/test_organization_verification_request.py @@ -0,0 +1,180 @@ +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, + request_id, + ) = seeder.create_incoming_admin_unit_verification_request(admin_unit_id) + + url = utils.get_url("api_v1_organization_verification_request", id=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, + request_id, + ) = seeder.create_incoming_admin_unit_verification_request(third_admin_unit_id) + + url = utils.get_url("api_v1_organization_verification_request", id=request_id) + response = utils.get_json(url) + utils.assert_response_unauthorized(response) + + +def test_delete(client, app, db, seeder: Seeder, utils: UtilActions): + ( + verifier_user_id, + verifier_admin_unit_id, + unverified_user_id, + unverified_admin_unit_id, + ) = seeder.setup_admin_unit_missing_verification_scenario(api=True) + reference_request_id = seeder.create_admin_unit_verification_request( + unverified_admin_unit_id, verifier_admin_unit_id + ) + + url = utils.get_url( + "api_v1_organization_verification_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, mocker): + mail_mock = utils.mock_send_mails_async(mocker) + ( + verifier_user_id, + verifier_admin_unit_id, + unverified_user_id, + unverified_admin_unit_id, + ) = seeder.setup_admin_unit_missing_verification_scenario( + log_in_verifier=True, api=True + ) + reference_request_id = seeder.create_admin_unit_verification_request( + unverified_admin_unit_id, verifier_admin_unit_id + ) + + url = utils.get_url( + "api_v1_organization_verification_request_verify", id=reference_request_id + ) + data = { + "auto_verify_event_reference_requests": True, + } + response = utils.post_json(url, data) + utils.assert_response_created(response) + assert "id" in response.json + utils.assert_send_mail_called(mail_mock, "mitglied@verein.de") + + with app.app_context(): + from project.models import AdminUnitVerificationRequest + from project.services.admin_unit import get_admin_unit_relation + + verification_request = db.session.get( + AdminUnitVerificationRequest, reference_request_id + ) + assert verification_request.verified + + relation = get_admin_unit_relation( + verifier_admin_unit_id, unverified_admin_unit_id + ) + assert relation is not None + assert relation.id == int(response.json["id"]) + assert relation.verify + assert relation.auto_verify_event_reference_requests + + +def test_verify_alreadyVerified( + client, app, db, seeder: Seeder, utils: UtilActions, mocker +): + ( + verifier_user_id, + verifier_admin_unit_id, + unverified_user_id, + unverified_admin_unit_id, + ) = seeder.setup_admin_unit_missing_verification_scenario( + log_in_verifier=True, api=True + ) + reference_request_id = seeder.create_admin_unit_verification_request( + unverified_admin_unit_id, verifier_admin_unit_id + ) + + with app.app_context(): + from project.models import ( + AdminUnitVerificationRequest, + AdminUnitVerificationRequestReviewStatus, + ) + + verification_request = db.session.get( + AdminUnitVerificationRequest, reference_request_id + ) + + verification_request.review_status = ( + AdminUnitVerificationRequestReviewStatus.verified + ) + db.session.commit() + + url = utils.get_url( + "api_v1_organization_verification_request_verify", id=reference_request_id + ) + data = { + "auto_verify_event_reference_requests": True, + } + response = utils.post_json(url, data) + utils.assert_response_unprocessable_entity(response) + + +def test_reject(client, app, db, seeder: Seeder, utils: UtilActions, mocker): + mail_mock = utils.mock_send_mails_async(mocker) + ( + verifier_user_id, + verifier_admin_unit_id, + unverified_user_id, + unverified_admin_unit_id, + ) = seeder.setup_admin_unit_missing_verification_scenario( + log_in_verifier=True, api=True + ) + reference_request_id = seeder.create_admin_unit_verification_request( + unverified_admin_unit_id, verifier_admin_unit_id + ) + + url = utils.get_url( + "api_v1_organization_verification_request_reject", id=reference_request_id + ) + data = { + "rejection_reason": "unknown", + } + response = utils.post_json(url, data) + utils.assert_response_no_content(response) + utils.assert_send_mail_called(mail_mock, "mitglied@verein.de") + + with app.app_context(): + from project.models import ( + AdminUnitVerificationRequest, + AdminUnitVerificationRequestRejectionReason, + AdminUnitVerificationRequestReviewStatus, + ) + + verification_request = db.session.get( + AdminUnitVerificationRequest, reference_request_id + ) + assert ( + verification_request.review_status + == AdminUnitVerificationRequestReviewStatus.rejected + ) + assert ( + verification_request.rejection_reason + == AdminUnitVerificationRequestRejectionReason.unknown + ) diff --git a/tests/seeder.py b/tests/seeder.py index 633135f..8afb212 100644 --- a/tests/seeder.py +++ b/tests/seeder.py @@ -305,9 +305,16 @@ class Seeder(object): return client_id - def setup_api_access(self, admin=True, admin_unit_verified=True, user_access=True): + def setup_api_access( + self, + admin=True, + admin_unit_verified=True, + user_access=True, + email="test@test.de", + admin_unit_name="Meine Crew", + ): user_id, admin_unit_id = self.setup_base( - admin=admin, log_in=False, admin_unit_verified=admin_unit_verified + admin, False, admin_unit_verified, email, admin_unit_name ) if user_access: @@ -322,13 +329,17 @@ class Seeder(object): with self._app.app_context(): from project.models import OAuth2Client + from project.services.user import get_user + + user = get_user(user_id) + email = user.email oauth2_client = self._db.session.get(OAuth2Client, oauth2_client_id) client_id = oauth2_client.client_id client_secret = oauth2_client.client_secret scope = oauth2_client.scope - self._utils.login(follow_redirects=False) + self._utils.login(email=email, follow_redirects=False) self._utils.authorize(client_id, client_secret, scope) self._utils.logout() return (user_id, admin_unit_id) @@ -673,7 +684,9 @@ class Seeder(object): ) return (other_user_id, other_admin_unit_id, relation_id) - def setup_admin_unit_missing_verification_scenario(self, log_in_verifier=False): + def setup_admin_unit_missing_verification_scenario( + self, log_in_verifier=False, api=False + ): verifier_user_id = self.create_user() verifier_admin_unit_id = self.create_admin_unit( verifier_user_id, @@ -684,15 +697,26 @@ class Seeder(object): incoming_verification_requests_text="Please give us a call", ) - unverified_user_id, unverified_admin_unit_id = self.setup_base( - log_in=not log_in_verifier, - admin_unit_verified=False, - email="mitglied@verein.de", - name="Verein", - ) + if api: + unverified_user_id, unverified_admin_unit_id = self.setup_api_access( + admin=False, + admin_unit_verified=False, + email="mitglied@verein.de", + admin_unit_name="Verein", + ) - if log_in_verifier: - self._utils.login() + if log_in_verifier: + self.authorize_api_access(verifier_user_id, verifier_admin_unit_id) + else: + unverified_user_id, unverified_admin_unit_id = self.setup_base( + log_in=not log_in_verifier, + admin_unit_verified=False, + email="mitglied@verein.de", + name="Verein", + ) + + if log_in_verifier: + self._utils.login() return ( verifier_user_id,