Repository: fossasia/open-event-server

Issue:
Add functionality to delete User Event Role
**Is your feature request related to a problem? Please describe.**
Currently there is no functionality to delete User Event Role. Adding this functionality can allow event organizers to delete role assigned to any user.

Ref issue: #7235 


Discussion:
@iamareebjamal @mariobehling can we approve this issue? :sweat_smile: 
@iamareebjamal for creating this endpoint I was wondering if we can pass the id of user event role for deletion or not since I don't see it returned otherwise at any place. So should I instead pass role_invite id and from that query the user event role object and delete? Thanks :v: 
No, event user role, like all other endpoints
@iamareebjamal so I should add this as a standalone misc endpoint in role_invites,py or create the api and schema file and set up the resource manager architecture since right now I don't see those files and creation for this model is handled in accept_invite function itself? :sweat_smile: 
create the api and schema file and set up the resource manager architecture

Resolving patch:
diff --git a/app/api/routes.py b/app/api/routes.py
--- a/app/api/routes.py
+++ b/app/api/routes.py
@@ -235,6 +235,11 @@
 )
 from app.api.user_permission import UserPermissionDetail, UserPermissionList
 from app.api.users import UserDetail, UserList, UserRelationship
+from app.api.users_events_roles import (
+    UsersEventsRolesDetail,
+    UsersEventsRolesList,
+    UsersEventsRolesRelationship,
+)
 from app.api.video_channel import VideoChannelDetail, VideoChannelList
 from app.api.video_stream import (
     VideoStreamDetail,
@@ -261,6 +266,7 @@
     '/alternate-emails/<int:user_email_id>/user',
     '/favourite-events/<int:user_favourite_event_id>/user',
     '/speakers/<int:speaker_id>/user',
+    '/users-events-roles/<int:users_events_roles_id>/user',
 )
 api.route(
     UserRelationship, 'user_notification', '/users/<int:id>/relationships/notifications'
@@ -490,6 +496,7 @@
     'role_detail',
     '/roles/<int:id>',
     '/role-invites/<int:role_invite_id>/role',
+    '/users-events-roles/<int:users_events_roles_id>/role',
 )
 
 # custom system roles
@@ -531,6 +538,32 @@
     '/role-invites/<int:id>/relationships/role',
 )
 
+# users_events_roles
+api.route(
+    UsersEventsRolesDetail, 'users_events_roles_detail', '/users-events-roles/<int:id>'
+)
+api.route(
+    UsersEventsRolesList,
+    'users_events_roles_list',
+    '/events/<int:event_id>/users-events-roles',
+    '/events/<event_identifier>/users-events-roles',
+)
+api.route(
+    UsersEventsRolesRelationship,
+    'users_events_roles_user',
+    '/users-events-roles/<int:id>/relationships/user',
+)
+api.route(
+    UsersEventsRolesRelationship,
+    'users_events_roles_event',
+    '/users-events-roles/<int:id>/relationships/event',
+)
+api.route(
+    UsersEventsRolesRelationship,
+    'users_events_roles_role',
+    '/users-events-roles/<int:id>/relationships/role',
+)
+
 # tickets
 api.route(TicketListPost, 'ticket_list_post', '/tickets')
 api.route(
@@ -652,6 +685,7 @@
     '/user-favourite-events/<int:user_favourite_event_id>/event',
     '/discount-codes/<int:discount_code_id>/event',
     '/video-streams/<int:video_stream_id>/event',
+    '/users-events-roles/<int:users_events_roles_id>/event',
 )
 api.route(
     EventRelationship,
diff --git a/app/api/schema/users_events_roles.py b/app/api/schema/users_events_roles.py
new file mode 100644
--- /dev/null
+++ b/app/api/schema/users_events_roles.py
@@ -0,0 +1,50 @@
+from marshmallow_jsonapi import fields
+from marshmallow_jsonapi.flask import Relationship
+
+from app.api.helpers.utilities import dasherize
+from app.api.schema.base import SoftDeletionSchema
+
+
+class UsersEventsRolesSchema(SoftDeletionSchema):
+    """
+    Api schema for users_events_role Model
+    """
+
+    class Meta:
+        """
+        Meta class for users_events_role Api Schema
+        """
+
+        type_ = 'users-events-roles'
+        self_view = 'v1.users_events_roles_detail'
+        self_view_kwargs = {'id': '<id>'}
+        inflect = dasherize
+
+    id = fields.Str(dump_only=True)
+
+    event = Relationship(
+        self_view='v1.users_events_roles_event',
+        self_view_kwargs={'id': '<id>'},
+        related_view='v1.event_detail',
+        related_view_kwargs={'users_events_roles_id': '<id>'},
+        schema='EventSchemaPublic',
+        type_='event',
+    )
+
+    user = Relationship(
+        self_view='v1.users_events_roles_user',
+        self_view_kwargs={'id': '<id>'},
+        related_view='v1.user_detail',
+        related_view_kwargs={'users_events_roles_id': '<id>'},
+        schema='UserSchemaPublic',
+        type_="user",
+    )
+
+    role = Relationship(
+        self_view='v1.users_events_roles_role',
+        self_view_kwargs={'id': '<id>'},
+        related_view='v1.role_detail',
+        related_view_kwargs={'users_events_roles_id': '<id>'},
+        schema='RoleSchema',
+        type_="role",
+    )
diff --git a/app/api/users_events_roles.py b/app/api/users_events_roles.py
new file mode 100644
--- /dev/null
+++ b/app/api/users_events_roles.py
@@ -0,0 +1,53 @@
+from flask_rest_jsonapi import ResourceDetail, ResourceList, ResourceRelationship
+
+from app.api.bootstrap import api
+from app.api.helpers.query import event_query
+from app.api.schema.users_events_roles import UsersEventsRolesSchema
+from app.models import db
+from app.models.users_events_role import UsersEventsRoles
+
+
+class UsersEventsRolesList(ResourceList):
+    """
+    List and create users_events_roles
+    """
+
+    def query(self, view_kwargs):
+        query_ = self.session.query(UsersEventsRoles)
+        # users_events_roles under an event
+        query_ = event_query(query_, view_kwargs)
+
+        return query_
+
+    view_kwargs = True
+    decorators = (
+        api.has_permission('is_coorganizer', fetch='event_id', fetch_as="event_id"),
+    )
+    methods = ['GET']
+    schema = UsersEventsRolesSchema
+    data_layer = {
+        'session': db.session,
+        'model': UsersEventsRoles,
+        'methods': {'query': query},
+    }
+
+
+class UsersEventsRolesDetail(ResourceDetail):
+    """
+    users_events_roles detail by id
+    """
+
+    decorators = (api.has_permission('is_coorganizer', methods="GET,PATCH,DELETE"),)
+    methods = ['GET', 'PATCH', 'DELETE']
+    schema = UsersEventsRolesSchema
+    data_layer = {'session': db.session, 'model': UsersEventsRoles}
+
+
+class UsersEventsRolesRelationship(ResourceRelationship):
+    """
+    users_events_roles Relationship
+    """
+
+    methods = ['GET', 'PATCH']
+    schema = UsersEventsRolesSchema
+    data_layer = {'session': db.session, 'model': UsersEventsRoles}


Test patch:
diff --git a/tests/factories/users_events_roles.py b/tests/factories/users_events_roles.py
new file mode 100644
--- /dev/null
+++ b/tests/factories/users_events_roles.py
@@ -0,0 +1,30 @@
+import factory
+
+from app.models.users_events_role import UsersEventsRoles
+from tests.factories.base import BaseFactory
+from tests.factories.event import EventFactoryBasic
+from tests.factories.role import RoleFactory
+from tests.factories.user import UserFactory
+
+
+class UsersEventsRolesFactoryBasic(BaseFactory):
+    class Meta:
+        model = UsersEventsRoles
+
+
+class UsersEventsRolesFactory(UsersEventsRolesFactoryBasic):
+    class Meta:
+        model = UsersEventsRoles
+
+    event = factory.RelatedFactory(EventFactoryBasic)
+    role = factory.RelatedFactory(RoleFactory)
+    user = factory.RelatedFactory(UserFactory)
+    event_id = 1
+    role_id = 1
+    user_id = 1
+
+
+class UsersEventsRolesSubFactory(UsersEventsRolesFactoryBasic):
+    user = factory.SubFactory(UserFactory)
+    event = factory.SubFactory(EventFactoryBasic)
+    role = factory.SubFactory(RoleFactory)
diff --git a/tests/hook_main.py b/tests/hook_main.py
--- a/tests/hook_main.py
+++ b/tests/hook_main.py
@@ -58,6 +58,10 @@
 from tests.factories.role import RoleFactory
 from tests.factories.ticket_fee import TicketFeesFactory
 from tests.factories.role_invite import RoleInviteFactory
+from tests.factories.users_events_roles import (
+    UsersEventsRolesFactory,
+    UsersEventsRolesSubFactory,
+)
 from tests.factories.custom_placeholder import CustomPlaceholderFactory
 from tests.factories.user_permission import UserPermissionFactory
 from tests.factories.email_notification import EmailNotificationFactory
@@ -3405,6 +3409,67 @@ def role_invite_delete(transaction):
         db.session.commit()
 
 
+# ------------------------- Users Events Roles -------------------------
+@hooks.before(
+    "Users Events Roles > Users Events Roles Collection List > List All Users Events Roles"
+)
+def users_events_roles_list(transaction):
+    """
+    GET /events/1/users-events-roles
+    :param transaction:
+    :return:
+    """
+    with stash['app'].app_context():
+        users_events_roles = UsersEventsRolesFactory()
+        db.session.add(users_events_roles)
+        db.session.commit()
+
+
+@hooks.before(
+    "Users Events Roles > Users Events Roles Details > Users Events Roles Details"
+)
+def users_events_roles_get_detail(transaction):
+    """
+    GET /users-events-roles/1
+    :param transaction:
+    :return:
+    """
+    with stash['app'].app_context():
+        users_events_roles = UsersEventsRolesFactory()
+        db.session.add(users_events_roles)
+        db.session.commit()
+
+
+@hooks.before(
+    "Users Events Roles > Users Events Roles Details > Update Users Events Roles"
+)
+def users_events_roles_patch(transaction):
+    """
+    PATCH /users-events-roles/1
+    :param transaction:
+    :return:
+    """
+    with stash['app'].app_context():
+        users_events_roles = UsersEventsRolesSubFactory()
+        db.session.add(users_events_roles)
+        db.session.commit()
+
+
+@hooks.before(
+    "Users Events Roles > Users Events Roles Details > Delete Users Events Roles"
+)
+def users_events_roles_delete(transaction):
+    """
+    DELETE /users-events-roles/1
+    :param transaction:
+    :return:
+    """
+    with stash['app'].app_context():
+        users_events_roles = UsersEventsRolesFactory()
+        db.session.add(users_events_roles)
+        db.session.commit()
+
+
 # ------------------------- Upload -------------------------
 @hooks.before("Upload > Image Upload > Upload an Image in temporary location")
 def image_upload_post(transaction):