Repository: fossasia/open-event-server

Issue:
Role Invites: Add seperate resourcelist class, docs, permissions for direct endpoints
Parent issue #4101.


Discussion:

Resolving patch:
diff --git a/app/api/__init__.py b/app/api/__init__.py
--- a/app/api/__init__.py
+++ b/app/api/__init__.py
@@ -19,7 +19,7 @@
 from app.api.speakers_calls import SpeakersCallList, SpeakersCallDetail, SpeakersCallRelationship
 from app.api.event_invoices import EventInvoiceList, EventInvoiceListPost, EventInvoiceDetail, \
     EventInvoiceRelationshipRequired, EventInvoiceRelationshipOptional
-from app.api.role_invites import RoleInviteList, RoleInviteDetail, RoleInviteRelationship
+from app.api.role_invites import RoleInviteListPost, RoleInviteList, RoleInviteDetail, RoleInviteRelationship
 from app.api.image_sizes import ImageSizeList, ImageSizeDetail
 from app.api.roles import RoleList, RoleDetail
 from app.api.session_types import SessionTypeList, SessionTypeDetail, SessionTypeRelationshipRequired, \
@@ -93,7 +93,8 @@
 api.route(RoleDetail, 'role_detail', '/roles/<int:id>', '/role-invites/<int:role_invite_id>/role')
 
 # role_invites
-api.route(RoleInviteList, 'role_invite_list', '/role-invites', '/events/<int:event_id>/role-invites',
+api.route(RoleInviteListPost, 'role_invite_list_post', '/role-invites')
+api.route(RoleInviteList, 'role_invite_list', '/events/<int:event_id>/role-invites',
           '/events/<event_identifier>/role-invites')
 api.route(RoleInviteDetail, 'role_invite_detail', '/role-invites/<int:id>')
 api.route(RoleInviteRelationship, 'role_invite_event', '/role-invites/<int:id>/relationships/event')
diff --git a/app/api/role_invites.py b/app/api/role_invites.py
--- a/app/api/role_invites.py
+++ b/app/api/role_invites.py
@@ -2,6 +2,7 @@
 from marshmallow_jsonapi.flask import Schema, Relationship
 from marshmallow_jsonapi import fields
 import marshmallow.validate as validate
+from marshmallow import validates_schema
 
 from app.api.helpers.utilities import dasherize
 from app.models import db
@@ -16,6 +17,7 @@
 from app.api.helpers.utilities import require_relationship
 from app.api.helpers.permission_manager import has_access
 from app.api.helpers.exceptions import UnprocessableEntity
+from app.api.helpers.exceptions import ForbiddenException
 
 
 class RoleInviteSchema(Schema):
@@ -32,6 +34,24 @@ class Meta:
         self_view_kwargs = {'id': '<id>'}
         inflect = dasherize
 
+    @validates_schema(pass_original=True)
+    def validate_satus(self, data, original_data):
+        if 'role' in data and 'role_name' in data:
+            role = Role.query.filter_by(id=data['role']).one()
+            if role.name != data['role_name']:
+                raise UnprocessableEntity({'pointer': '/data/attributes/role'},
+                                          "Role id do not match role name")
+        if 'id' in original_data['data']:
+            role_invite = RoleInvite.query.filter_by(id=original_data['data']['id']).one()
+            if 'role' not in data:
+                data['role'] = role_invite.role.id
+            if 'role_name' in data:
+                role = Role.query.filter_by(id=data['role']).one()
+                if role.name != data['role_name']:
+                    raise UnprocessableEntity({'pointer': '/data/attributes/role'},
+                                              "Role id do not match role name")
+
+
     id = fields.Str(dump_only=True)
     email = fields.Str(required=True)
     hash = fields.Str(dump_only=True)
@@ -56,14 +76,40 @@ class Meta:
                         type_='role')
 
 
-class RoleInviteList(ResourceList):
+class RoleInviteListPost(ResourceList):
     """
-    List and create role invites
+    Create role invites
     """
 
     def before_post(self, args, kwargs, data):
         require_relationship(['event', 'role'], data)
+        if not has_access('is_organizer', event_id=data['event']):
+            raise ForbiddenException({'source': ''}, 'Organizer access is required.')
+
+    def after_create_object(self, role_invite, data, view_kwargs):
+        user = User.query.filter_by(email=role_invite.email).first()
+        if 'status' in data and data['status'] == 'accepted':
+            role = Role.query.filter_by(name=role_invite.role_name).first()
+            event = Event.query.filter_by(id=role_invite.event_id).first()
+            uer = UsersEventsRoles.query.filter_by(user=user).filter_by(event=event).filter_by(role=role).first()
+            if not uer:
+                uer = UsersEventsRoles(user, event, role)
+                save_to_db(uer, 'Role Invite accepted')
+
+    view_kwargs = True
+    decorators = (api.has_permission('is_admin', methods="GET"),)
+    schema = RoleInviteSchema
+    data_layer = {'session': db.session,
+                  'model': RoleInvite,
+                  'methods': {
+                      'after_create_object': after_create_object
+                  }}
 
+
+class RoleInviteList(ResourceList):
+    """
+    List role invites based on event_id
+    """
     def query(self, view_kwargs):
         """
         query method for role invites list
@@ -79,41 +125,15 @@ def query(self, view_kwargs):
             query_ = query_.join(Event).filter(Event.id == event.id)
         return query_
 
-    def before_create_object(self, data, view_kwargs):
-        """
-        method to create object before post
-        :param data:
-        :param view_kwargs:
-        :return:
-        """
-        if view_kwargs.get('event_id'):
-            event = safe_query(self, Event, 'id', view_kwargs['event_id'], 'event_id')
-            data['event_id'] = event.id
-
-        elif view_kwargs.get('event_identifier'):
-            event = safe_query(self, Event, 'identifier', view_kwargs['event_identifier'], 'event_identifier')
-            data['event_id'] = event.id
-
-    def after_create_object(self, role_invite, data, view_kwargs):
-        user = User.query.filter_by(email=role_invite.email).first()
-        if 'status' in data and data['status'] == 'accepted':
-            role = Role.query.filter_by(name=role_invite.role_name).first()
-            event = Event.query.filter_by(id=role_invite.event_id).first()
-            uer = UsersEventsRoles.query.filter_by(user=user).filter_by(event=event).filter_by(role=role).first()
-            if not uer:
-                uer = UsersEventsRoles(user, event, role)
-                save_to_db(uer, 'Role Invite accepted')
-
     view_kwargs = True
-    decorators = (api.has_permission('is_organizer', fetch='event_id', fetch_as="event_id", methods="POST",
+    methods = ['GET']
+    decorators = (api.has_permission('is_organizer', fetch='event_id', fetch_as="event_id",
                                      check=lambda a: a.get('event_id') or a.get('event_identifier')),)
     schema = RoleInviteSchema
     data_layer = {'session': db.session,
                   'model': RoleInvite,
                   'methods': {
-                      'query': query,
-                      'before_create_object': before_create_object,
-                      'after_create_object': after_create_object
+                      'query': query
                   }}
 
 
diff --git a/app/factories/role_invite.py b/app/factories/role_invite.py
--- a/app/factories/role_invite.py
+++ b/app/factories/role_invite.py
@@ -1,5 +1,6 @@
 import factory
 from app.models.role_invite import db, RoleInvite
+from app.factories.role import RoleFactory
 from app.factories.event import EventFactoryBasic
 import app.factories.common as common
 
@@ -10,9 +11,11 @@ class Meta:
         sqlalchemy_session = db.session
 
     event = factory.RelatedFactory(EventFactoryBasic)
+    role = factory.RelatedFactory(RoleFactory)
     email = common.email_
     created_at = common.date_
     hash = common.string_
     status = common.string_
     role_name = common.string_
     event_id = 1
+    role_id = 1


Test patch:
diff --git a/tests/hook_main.py b/tests/hook_main.py
--- a/tests/hook_main.py
+++ b/tests/hook_main.py
@@ -1864,7 +1864,7 @@ def custom_form_delete(transaction):
 
 
 # ------------------------- Role Invites -------------------------
-@hooks.before("Role Invites > Role Invites Collection > List All Role Invites")
+@hooks.before("Role Invites > Role Invites Collection List > List All Role Invites")
 def role_invite_get_list(transaction):
     """
     GET /events/1/role-invites
@@ -1880,13 +1880,10 @@ def role_invite_get_list(transaction):
 @hooks.before("Role Invites > Role Invites Collection > Create Role Invite")
 def role_invite_post(transaction):
     """
-    POST /events/1/role-invites
+    POST /role-invites
     :param transaction:
     :return:
     """
-    # Skip until docs for direct endpoints added
-    transaction['skip'] = True
-
     with stash['app'].app_context():
         event = EventFactoryBasic()
         db.session.add(event)