Repository: fossasia/open-event-server

Issue:
Tickets: Add seperate resourcelist class, docs, permissions for direct endpoints
**I'm submitting a ...**  (check one with "x")
- [x] bug report
- [ ] feature request
- [ ] support request => Please do not submit support requests here, instead ask your query in out Gitter channel at https://gitter.im/fossasia/open-event-orga-server
Endpoint
```
/v1/tickets
```
Payload
```
{
   "data":{
      "attributes":{
         "name":"FREE",
         "type":"free",
         "price":null,
         "quantity":100,
         "description":null,
         "is-description-visible":true,
         "is-hidden":false,
         "sales-starts-at":"2017-07-24T18:30:00.000Z",
         "sales-ends-at":"2017-08-03T18:30:00.000Z",
         "min-order":1,
         "max-order":10,
         "is-fee-absorbed":true,
         "position":null
      },
      "relationships":{
         "event":{
            "data":{
               "type":"event",
               "id":"167"
            }
         }
      },
      "type":"ticket"
   }
}
```
Response
```
{
   "jsonapi":{
      "version":"1.0"
   },
   "errors":[
      {
         "status":403,
         "detail":"Access forbidden",
         "source":{
            "source":""
         },
         "title":"Access Forbidden"
      }
   ]
}
```
**Solution**:
Add seperate resourcelist class, docs, permissions for direct endpoints

(Edited by @shubham-padia)


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
@@ -4,7 +4,8 @@
 from app.api.notifications import NotificationList, NotificationDetail, NotificationRelationship
 from app.api.email_notifications import EmailNotificationList, EmailNotificationListPost, EmailNotificationDetail,\
     EmailNotificationRelationshipOptional, EmailNotificationRelationshipRequired
-from app.api.tickets import TicketList, TicketDetail, TicketRelationshipRequired, TicketRelationshipOptional
+from app.api.tickets import TicketList, TicketListPost, TicketDetail, TicketRelationshipRequired,\
+    TicketRelationshipOptional
 from app.api.events import EventList, EventDetail, EventRelationship
 from app.api.event_types import EventTypeList, EventTypeDetail, EventTypeRelationship
 from app.api.event_topics import EventTopicList, EventTopicDetail, EventTopicRelationship
@@ -104,7 +105,8 @@
 api.route(RoleInviteRelationship, 'role_invite_role', '/role-invites/<int:id>/relationships/role')
 
 # tickets
-api.route(TicketList, 'ticket_list', '/tickets', '/events/<int:event_id>/tickets',
+api.route(TicketListPost, 'ticket_list_post', '/tickets')
+api.route(TicketList, 'ticket_list', '/events/<int:event_id>/tickets',
           '/events/<event_identifier>/tickets', '/ticket-tags/<int:ticket_tag_id>/tickets',
           '/access-codes/<int:access_code_id>/tickets')
 api.route(TicketDetail, 'ticket_detail', '/tickets/<int:id>', '/attendees/<int:attendee_id>/ticket')
diff --git a/app/api/tickets.py b/app/api/tickets.py
--- a/app/api/tickets.py
+++ b/app/api/tickets.py
@@ -2,6 +2,7 @@
 from marshmallow_jsonapi.flask import Schema, Relationship
 from marshmallow_jsonapi import fields
 from marshmallow import validates_schema
+from flask_rest_jsonapi.exceptions import ObjectNotFound
 
 from app.api.bootstrap import api
 from app.api.helpers.utilities import dasherize
@@ -14,6 +15,7 @@
 from app.models.ticket_holder import TicketHolder
 from app.api.helpers.db import safe_query
 from app.api.helpers.utilities import require_relationship
+from app.api.helpers.permission_manager import has_access
 
 
 class TicketSchema(Schema):
@@ -88,10 +90,27 @@ def validate_quantity(self, data):
                                 type_='access-code')
 
 
-class TicketList(ResourceList):
+class TicketListPost(ResourceList):
     """
     Create and List Tickets
     """
+    def before_post(self, args, kwargs, data):
+        require_relationship(['event'], data)
+
+        if not has_access('is_coorganizer', event_id=data['event']):
+            raise ObjectNotFound({'parameter': 'event_id'},
+                                 "Event: {} not found".format(data['event_id']))
+
+    schema = TicketSchema
+    methods = ['POST', ]
+    data_layer = {'session': db.session,
+                  'model': Ticket}
+
+
+class TicketList(ResourceList):
+    """
+    List Tickets based on different params
+    """
     def before_post(self, args, kwargs, data):
         require_relationship(['event'], data)
 
@@ -113,25 +132,13 @@ def query(self, view_kwargs):
 
         return query_
 
-    def before_create_object(self, data, view_kwargs):
-        event = None
-        if view_kwargs.get('event_id'):
-            event = safe_query(self, Event, 'id', view_kwargs['event_id'], 'event_id')
-        elif view_kwargs.get('event_identifier'):
-            event = safe_query(self, Event, 'identifier', view_kwargs['event_identifier'], 'event_identifier')
-        if event:
-            data['event_id'] = event.id
-
     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')),)
+    methods = ['GET', ]
     schema = TicketSchema
     data_layer = {'session': db.session,
                   'model': Ticket,
                   'methods': {
                       'query': query,
-                      'before_create_object': before_create_object
                   }}
 
 


Test patch:
diff --git a/tests/hook_main.py b/tests/hook_main.py
--- a/tests/hook_main.py
+++ b/tests/hook_main.py
@@ -926,29 +926,13 @@ def tax_delete(transaction):
 
 
 # ------------------------- Tickets -------------------------
-@hooks.before("Tickets > Tickets Collection > List All Tickets")
-def ticket_get_list(transaction):
-    """
-    GET /events/1/tickets
-    :param transaction:
-    :return:
-    """
-    with stash['app'].app_context():
-        ticket = TicketFactory()
-        db.session.add(ticket)
-        db.session.commit()
-
-
 @hooks.before("Tickets > Tickets Collection > Create Ticket")
 def ticket_post(transaction):
     """
-    POST /events/1/tickets
+    POST /tickets
     :param transaction:
     :return:
     """
-    # Skip until docs for direct endpoints added
-    transaction['skip'] = True
-
     with stash['app'].app_context():
         event = EventFactoryBasic()
         db.session.add(event)
@@ -993,9 +977,47 @@ def ticket_delete(transaction):
         db.session.add(ticket)
         db.session.commit()
 
-# ------------------------- Ticket Fees -------------------------
+
+@hooks.before("Tickets > List Tickets under an Event > List Tickets")
+def event_ticket(transaction):
+    """
+    GET /events/1/tickets
+    :param transaction:
+    :return:
+    """
+    with stash['app'].app_context():
+        ticket = TicketFactory()
+        db.session.add(ticket)
+        db.session.commit()
+
+
+@hooks.before("Tickets > List Tickets under a Ticket-tag > List Tickets")
+def tikcet_tag_ticket(transaction):
+    """
+    GET /tikcet-tags/1/tickets
+    :param transaction:
+    :return:
+    """
+    with stash['app'].app_context():
+        ticket_tag = TicketTagFactory()
+        db.session.add(ticket_tag)
+        db.session.commit()
 
 
+@hooks.before("Tickets > List Tickets for an Access Code > List Tickets")
+def access_code_ticket(transaction):
+    """
+    GET /access-codes/1/tickets
+    :param transaction:
+    :return:
+    """
+    with stash['app'].app_context():
+        access_code = AccessCodeFactory()
+        db.session.add(access_code)
+        db.session.commit()
+
+
+# ------------------------- Ticket Fees -------------------------
 @hooks.before("Ticket Fees > Ticket Fees Collection > List Ticket Fees")
 def ticket_fees_get_list(transaction):
     """