Repository: fossasia/open-event-server

Issue:
Ticket-tags: 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
@@ -30,7 +30,7 @@
 from app.api.tax import TaxList, TaxDetail, TaxRelationship
 from app.api.settings import SettingDetail
 from app.api.discount_codes import DiscountCodeList, DiscountCodeDetail, DiscountCodeRelationship
-from app.api.ticket_tags import TicketTagList, TicketTagDetail, TicketTagRelationshipOptional, \
+from app.api.ticket_tags import TicketTagList, TicketTagListPost, TicketTagDetail, TicketTagRelationshipOptional, \
     TicketTagRelationshipRequired
 from app.api.attendees import AttendeeList, AttendeeDetail, AttendeeRelationshipOptional, \
     AttendeeRelationshipRequired
@@ -106,7 +106,8 @@
 api.route(TicketRelationshipOptional, 'ticket_access_code', '/tickets/<int:id>/relationships/access-codes')
 
 # ticket_tags
-api.route(TicketTagList, 'ticket_tag_list', '/ticket-tags', '/tickets/<int:ticket_id>/ticket-tags',
+api.route(TicketTagListPost, 'ticket_tag_list_post', '/ticket-tags')
+api.route(TicketTagList, 'ticket_tag_list', '/tickets/<int:ticket_id>/ticket-tags',
           '/events/<int:event_id>/ticket-tags', '/events/<event_identifier>/ticket-tags')
 api.route(TicketTagDetail, 'ticket_tag_detail', '/ticket-tags/<int:id>')
 api.route(TicketTagRelationshipOptional, 'ticket_tag_ticket', '/ticket-tags/<int:id>/relationships/tickets')
diff --git a/app/api/ticket_tags.py b/app/api/ticket_tags.py
--- a/app/api/ticket_tags.py
+++ b/app/api/ticket_tags.py
@@ -1,6 +1,8 @@
 from flask_rest_jsonapi import ResourceDetail, ResourceList, ResourceRelationship
 from marshmallow_jsonapi.flask import Schema, Relationship
 from marshmallow_jsonapi import fields
+from sqlalchemy.orm.exc import NoResultFound
+from flask_rest_jsonapi.exceptions import ObjectNotFound
 
 from app.api.helpers.utilities import dasherize
 from app.api.helpers.permissions import jwt_required
@@ -9,6 +11,8 @@
 from app.models.event import Event
 from app.api.helpers.db import safe_query
 from app.api.helpers.utilities import require_relationship
+from app.api.helpers.exceptions import ForbiddenException
+from app.api.helpers.permission_manager import has_access
 
 
 class TicketTagSchema(Schema):
@@ -44,14 +48,61 @@ class Meta:
                          type_='event')
 
 
-class TicketTagList(ResourceList):
+class TicketTagListPost(ResourceList):
     """
     List and create TicketTag
     """
     def before_post(self, args, kwargs, data):
+        """
+        before post method for checking required relationship
+        :param args:
+        :param kwargs:
+        :param data:
+        :return:
+        """
         require_relationship(['event'], data)
 
+        if not has_access('is_coorganizer', event_id=data['event']):
+            raise ForbiddenException({'source': ''}, 'Co-organizer access is required.')
+
+    def after_create_object(self, obj, data, view_kwargs):
+        """
+        method to add ticket tags and ticket in association table
+        :param obj:
+        :param data:
+        :param view_kwargs:
+        :return:
+        """
+        if 'tickets' in data:
+            ticket_ids = data['tickets']
+            for ticket_id in ticket_ids:
+                try:
+                    ticket = Ticket.query.filter_by(id=ticket_id).one()
+                except NoResultFound:
+                    raise ObjectNotFound({'parameter': 'ticket_id'},
+                                         "Ticket: {} not found".format(ticket_id))
+                else:
+                    ticket.tags.append(obj)
+                    self.session.commit()
+
+    schema = TicketTagSchema
+    data_layer = {'session': db.session,
+                  'model': TicketTag,
+                  'methods': {
+                    'after_create_object': after_create_object
+                  }}
+
+
+class TicketTagList(ResourceList):
+    """
+    List TicketTags based on event_id or ticket_id
+    """
     def query(self, view_kwargs):
+        """
+        method to query Ticket tags based on different params
+        :param view_kwargs:
+        :return:
+        """
         query_ = self.session.query(TicketTag)
         if view_kwargs.get('ticket_id'):
             ticket = safe_query(self, Ticket, 'id', view_kwargs['ticket_id'], 'ticket_id')
@@ -64,32 +115,13 @@ def query(self, view_kwargs):
             query_ = query_.join(Event).filter(Event.id == event.id)
         return query_
 
-    def before_create_object(self, data, view_kwargs):
-        if view_kwargs.get('ticket_id'):
-            ticket = safe_query(self, Ticket, 'id', view_kwargs['ticket_id'], 'ticket_id')
-            data['event_id'] = ticket.event_id
-        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, obj, data, view_kwargs):
-        if view_kwargs.get('ticket_id'):
-            ticket = safe_query(self, Ticket, 'id', view_kwargs['ticket_id'], 'ticket_id')
-            ticket.tags.append(obj)
-            self.session.commit()
-
     view_kwargs = True
-    decorators = (jwt_required,)
     schema = TicketTagSchema
+    methods = ['GET', ]
     data_layer = {'session': db.session,
                   'model': TicketTag,
                   'methods': {
-                      'query': query,
-                      'before_create_object': before_create_object,
-                      'after_create_object': after_create_object
+                      'query': query
                   }}
 
 
diff --git a/app/api/tickets.py b/app/api/tickets.py
--- a/app/api/tickets.py
+++ b/app/api/tickets.py
@@ -124,8 +124,8 @@ def before_create_object(self, data, view_kwargs):
 
     view_kwargs = True
     decorators = (api.has_permission('is_coorganizer', fetch='event_id',
-                  fetch_as="event_id", model=Ticket, methods="POST",
-                  check=lambda a: a.get('event_id') or a.get('event_identifier')), )
+                                     fetch_as="event_id", model=Ticket, methods="POST",
+                                     check=lambda a: a.get('event_id') or a.get('event_identifier')),)
     schema = TicketSchema
     data_layer = {'session': db.session,
                   'model': Ticket,


Test patch:
diff --git a/tests/hook_main.py b/tests/hook_main.py
--- a/tests/hook_main.py
+++ b/tests/hook_main.py
@@ -311,7 +311,7 @@ def copyright_delete(transaction):
 @hooks.before("Invoices > Event Invoices > Get Event Invoices")
 def invoice_get_list(transaction):
     """
-    GET /events/1/event-invoices
+    GET /event-invoices
     :param transaction:
     :return:
     """
@@ -324,7 +324,7 @@ def invoice_get_list(transaction):
 @hooks.before("Invoices > Event Invoices > Create Event Invoices")
 def invoice_post(transaction):
     """
-    POST /events/1/event-invoices
+    POST /event-invoices
     :param transaction:
     :return:
     """
@@ -1046,7 +1046,7 @@ def ticket_fees_delete(transaction):
 @hooks.before("Ticket Tags > Ticket Tags Collection > List All Ticket Tags")
 def ticket_tag_get_list(transaction):
     """
-    GET /tickets/1/ticket-tags
+    GET /ticket-tags
     :param transaction:
     :return:
     """
@@ -1059,13 +1059,10 @@ def ticket_tag_get_list(transaction):
 @hooks.before("Ticket Tags > Ticket Tags Collection > Create Ticket Tag")
 def ticket_tag_post(transaction):
     """
-    POST /tickets/1/ticket-tags
+    POST /ticket-tags
     :param transaction:
     :return:
     """
-    # Skip until docs for direct endpoints added
-    transaction['skip'] = True
-
     with stash['app'].app_context():
         tickets = TicketFactory()
         db.session.add(tickets)