API: organization-verification-requests #522

This commit is contained in:
Daniel Grams 2023-07-13 22:41:41 +02:00
parent ca31c139dc
commit 668d97c56b
18 changed files with 835 additions and 127 deletions

View File

@ -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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\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 ""

View File

@ -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

View File

@ -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
):

View File

@ -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/<int:id>/event-references/outgoing",
"api_v1_organization_outgoing_event_reference_list",
)
add_api_resource(
OrganizationIncomingEventReferenceRequestListResource,
"/organizations/<int:id>/event-reference-requests/incoming",
@ -719,7 +804,16 @@ add_api_resource(
"/organizations/<int:id>/event-reference-requests/outgoing",
"api_v1_organization_outgoing_event_reference_request_list",
)
add_api_resource(
OrganizationIncomingOrganizationVerificationRequestListResource,
"/organizations/<int:id>/organization-verification-requests/incoming",
"api_v1_organization_incoming_organization_verification_request_list",
)
add_api_resource(
OrganizationOutgoingOrganizationVerificationRequestListResource,
"/organizations/<int:id>/organization-verification-requests/outgoing",
"api_v1_organization_outgoing_organization_verification_request_list",
)
add_api_resource(
OrganizationOutgoingRelationListResource,
"/organizations/<int:id>/relations/outgoing",

View File

@ -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/<int:id>",
"api_v1_organization_verification_request",
)
add_api_resource(
OrganizationVerificationRequestVerifyResource,
"/organization-verification-request/<int:id>/verify",
"api_v1_organization_verification_request_verify",
)
add_api_resource(
OrganizationVerificationRequestRejectResource,
"/organization-verification-request/<int:id>/reject",
"api_v1_organization_verification_request_reject",
)

View File

@ -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,
)

View File

@ -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__()

View File

@ -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)

View File

@ -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: 2020-06-07 18:51+0200\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\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"

View File

@ -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 <EMAIL@ADDRESS>\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 ""

View File

@ -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(

View File

@ -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",

View File

@ -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()
(

View File

@ -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
)

View File

@ -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,