We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Allow GET access to ticket attendees for an order, for non logged in users
parent issue #5546 

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `app/api/attendees.py`
Content:
```
1 from datetime import datetime
2 
3 from flask import Blueprint, request, jsonify, abort, make_response
4 from flask_jwt import current_identity, jwt_required
5 from flask_rest_jsonapi import ResourceDetail, ResourceList, ResourceRelationship
6 from flask_rest_jsonapi.exceptions import ObjectNotFound
7 from sqlalchemy.orm.exc import NoResultFound
8 
9 from app.api.bootstrap import api
10 from app.api.helpers.db import safe_query, get_count
11 from app.api.helpers.exceptions import (
12     ConflictException,
13     ForbiddenException,
14     UnprocessableEntity,
15 )
16 from app.api.helpers.mail import send_email_to_attendees
17 from app.api.helpers.permission_manager import has_access
18 from app.api.helpers.permissions import jwt_required
19 from app.api.helpers.query import event_query
20 from app.api.helpers.utilities import require_relationship
21 from app.api.schema.attendees import AttendeeSchema
22 from app.models import db
23 from app.models.order import Order
24 from app.models.ticket import Ticket
25 from app.models.ticket_holder import TicketHolder
26 from app.models.user import User
27 
28 attendee_misc_routes = Blueprint('attendee_misc', __name__, url_prefix='/v1')
29 
30 
31 class AttendeeListPost(ResourceList):
32     """
33        List and create Attendees through direct URL
34     """
35 
36     def before_post(self, args, kwargs, data):
37         """
38         Before post method to check for required relationship and proper permissions
39         :param args:
40         :param kwargs:
41         :param data:
42         :return:
43         """
44         require_relationship(['ticket', 'event'], data)
45 
46         ticket = db.session.query(Ticket).filter_by(
47             id=int(data['ticket']), deleted_at=None
48             ).first()
49         if ticket is None:
50             raise UnprocessableEntity(
51                 {'pointer': '/data/relationships/ticket'}, "Invalid Ticket"
52                 )
53         if ticket.event_id != int(data['event']):
54             raise UnprocessableEntity(
55                 {'pointer': '/data/relationships/ticket'},
56                 "Ticket belongs to a different Event"
57             )
58         # Check if the ticket is already sold out or not.
59         if get_count(db.session.query(TicketHolder.id).
60                      filter_by(ticket_id=int(data['ticket']), deleted_at=None)) >= ticket.quantity:
61             raise ConflictException(
62                 {'pointer': '/data/attributes/ticket_id'},
63                 "Ticket already sold out"
64             )
65 
66         if 'device_name_checkin' in data and data['device_name_checkin'] is not None:
67             if 'is_checked_in' not in data or not data['is_checked_in']:
68                 raise UnprocessableEntity(
69                     {'pointer': '/data/attributes/device_name_checkin'},
70                     "Attendee needs to be checked in first"
71                 )
72             elif 'checkin_times' not in data or data['checkin_times'] is None:
73                 raise UnprocessableEntity(
74                     {'pointer': '/data/attributes/device_name_checkin'},
75                     "Check in Times missing"
76                 )
77             elif len(data['checkin_times'].split(",")) != len(data['device_name_checkin'].split(",")):
78                 raise UnprocessableEntity(
79                     {'pointer': '/data/attributes/device_name_checkin'},
80                     "Check in Times missing for the corresponding device name"
81                 )
82 
83         if 'checkin_times' in data:
84             if 'device_name_checkin' not in data or data['device_name_checkin'] is None:
85                 data['device_name_checkin'] = '-'
86 
87     decorators = (jwt_required,)
88     methods = ['POST']
89     schema = AttendeeSchema
90     data_layer = {'session': db.session,
91                   'model': TicketHolder}
92 
93 
94 class AttendeeList(ResourceList):
95     """
96     List Attendees
97     """
98     def query(self, view_kwargs):
99         """
100         query method for Attendees List
101         :param view_kwargs:
102         :return:
103         """
104         query_ = self.session.query(TicketHolder)
105 
106         if view_kwargs.get('order_identifier'):
107             order = safe_query(self, Order, 'identifier', view_kwargs['order_identifier'], 'order_identifier')
108             if not has_access('is_registrar', event_id=order.event_id) and not has_access('is_user_itself',
109                                                                                          user_id=order.user_id):
110                 raise ForbiddenException({'source': ''}, 'Access Forbidden')
111             query_ = query_.join(Order).filter(Order.id == order.id)
112 
113         if view_kwargs.get('ticket_id'):
114             ticket = safe_query(self, Ticket, 'id', view_kwargs['ticket_id'], 'ticket_id')
115             if not has_access('is_registrar', event_id=ticket.event_id):
116                 raise ForbiddenException({'source': ''}, 'Access Forbidden')
117             query_ = query_.join(Ticket).filter(Ticket.id == ticket.id)
118 
119         if view_kwargs.get('user_id'):
120             user = safe_query(self, User, 'id', view_kwargs['user_id'], 'user_id')
121             if not has_access('is_user_itself', user_id=user.id):
122                 raise ForbiddenException({'source': ''}, 'Access Forbidden')
123             query_ = query_.join(User, User.email == TicketHolder.email).filter(User.id == user.id)
124 
125         query_ = event_query(self, query_, view_kwargs, permission='is_registrar')
126         return query_
127 
128     view_kwargs = True
129     methods = ['GET', ]
130     schema = AttendeeSchema
131     data_layer = {'session': db.session,
132                   'model': TicketHolder,
133                   'methods': {
134                       'query': query
135                   }}
136 
137 
138 class AttendeeDetail(ResourceDetail):
139     """
140     Attendee detail by id
141     """
142     def before_get_object(self, view_kwargs):
143         """
144         before get object method for attendee detail
145         :param view_kwargs:
146         :return:
147         """
148         attendee = safe_query(self, TicketHolder, 'id', view_kwargs['id'], 'attendee_id')
149         if not has_access('is_registrar_or_user_itself', user_id=current_identity.id, event_id=attendee.event_id):
150             raise ForbiddenException({'source': 'User'}, 'You are not authorized to access this.')
151 
152     def before_delete_object(self, obj, kwargs):
153         """
154         before delete object method for attendee detail
155         :param obj:
156         :param kwargs:
157         :return:
158         """
159         if not has_access('is_registrar', event_id=obj.event_id):
160             raise ForbiddenException({'source': 'User'}, 'You are not authorized to access this.')
161 
162     def before_update_object(self, obj, data, kwargs):
163         """
164         before update object method for attendee detail
165         :param obj:
166         :param data:
167         :param kwargs:
168         :return:
169         """
170 #         if not has_access('is_registrar', event_id=obj.event_id):
171 #         raise ForbiddenException({'source': 'User'}, 'You are not authorized to access this.')
172 
173         if 'device_name_checkin' in data:
174             if 'checkin_times' not in data or data['checkin_times'] is None:
175                 raise UnprocessableEntity(
176                     {'pointer': '/data/attributes/device_name_checkin'},
177                     "Check in Times missing"
178                 )
179 
180         if 'is_checked_in' in data and data['is_checked_in']:
181             if 'checkin_times' not in data or data['checkin_times'] is None:
182                 raise UnprocessableEntity({'pointer': '/data/attributes/checkin_times'},
183                                           "Check in time missing while trying to check in attendee")
184             else:
185                 if obj.checkin_times and data['checkin_times'] not in obj.checkin_times.split(","):
186                     data['checkin_times'] = '{},{}'.format(obj.checkin_times, data['checkin_times'])
187                 elif obj.checkin_times and data['checkin_times'] in obj.checkin_times.split(","):
188                     raise UnprocessableEntity(
189                         {'pointer': '/data/attributes/checkin_times'},
190                         "Check in time already present"
191                     )
192 
193                 if 'device_name_checkin' in data and data['device_name_checkin'] is not None:
194                     if obj.device_name_checkin is not None:
195                         data['device_name_checkin'] = '{},{}'.format(obj.device_name_checkin,
196                                                                      data['device_name_checkin'])
197 
198                     if len(data['checkin_times'].split(",")) != len(data['device_name_checkin'].split(",")):
199                         raise UnprocessableEntity(
200                             {'pointer': '/data/attributes/device_name_checkin'},
201                             "Check in Time missing for the corresponding device name"
202                         )
203                 else:
204                     if obj.device_name_checkin is not None:
205                         data['device_name_checkin'] = '{},{}'.format(obj.device_name_checkin, '-')
206                     else:
207                         data['device_name_checkin'] = '-'
208 
209         if 'is_checked_out' in data and data['is_checked_out']:
210             attendee = safe_query(db, TicketHolder, 'id', kwargs['id'], 'attendee_id')
211             if not attendee.is_checked_out:
212                 checkout_times = obj.checkout_times.split(',') if obj.checkout_times else []
213                 checkout_times.append(str(datetime.utcnow()))
214                 data['checkout_times'] = ','.join(checkout_times)
215 
216         if 'attendee_notes' in data:
217             if obj.attendee_notes and data['attendee_notes'] not in obj.attendee_notes.split(","):
218                 data['attendee_notes'] = '{},{}'.format(obj.attendee_notes, data['attendee_notes'])
219 
220     decorators = (jwt_required,)
221     schema = AttendeeSchema
222     data_layer = {'session': db.session,
223                   'model': TicketHolder,
224                   'methods': {
225                       'before_get_object': before_get_object,
226                       'before_update_object': before_update_object,
227                       'before_delete_object': before_delete_object
228                   }}
229 
230 
231 class AttendeeRelationshipRequired(ResourceRelationship):
232     """
233     Attendee Relationship (Required)
234     """
235     decorators = (jwt_required,)
236     methods = ['GET', 'PATCH']
237     schema = AttendeeSchema
238     data_layer = {'session': db.session,
239                   'model': TicketHolder}
240 
241 
242 class AttendeeRelationshipOptional(ResourceRelationship):
243     """
244     Attendee Relationship(Optional)
245     """
246     decorators = (api.has_permission('is_user_itself', fetch="user_id", fetch_as="id", model=TicketHolder),)
247     schema = AttendeeSchema
248     data_layer = {'session': db.session,
249                   'model': TicketHolder}
250 
251 
252 @attendee_misc_routes.route('/attendees/send-receipt', methods=['POST'])
253 @jwt_required
254 def send_receipt():
255     """
256     Send receipts to attendees related to the provided order.
257     :return:
258     """
259     order_identifier = request.json.get('order-identifier')
260     if order_identifier:
261         try:
262             order = db.session.query(Order).filter_by(identifier=order_identifier).one()
263         except NoResultFound:
264             raise ObjectNotFound({'parameter': '{identifier}'}, "Order not found")
265 
266         if (order.user_id != current_identity.id) and (not has_access('is_registrar', event_id=order.event_id)):
267             abort(
268                 make_response(jsonify(error="You need to be the event organizer or order buyer to send receipts."), 403)
269             )
270         elif order.status != 'completed':
271             abort(
272                 make_response(jsonify(error="Cannot send receipt for an incomplete order"), 409)
273             )
274         else:
275             send_email_to_attendees(order, current_identity.id)
276             return jsonify(message="receipt sent to attendees")
277     else:
278         abort(
279             make_response(jsonify(error="Order identifier missing"), 422)
280         )
281
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/app/api/attendees.py b/app/api/attendees.py
--- a/app/api/attendees.py
+++ b/app/api/attendees.py
@@ -112,8 +112,8 @@
 
         if view_kwargs.get('ticket_id'):
             ticket = safe_query(self, Ticket, 'id', view_kwargs['ticket_id'], 'ticket_id')
-            if not has_access('is_registrar', event_id=ticket.event_id):
-                raise ForbiddenException({'source': ''}, 'Access Forbidden')
+            # if not has_access('is_registrar', event_id=ticket.event_id):
+            #     raise ForbiddenException({'source': ''}, 'Access Forbidden')
             query_ = query_.join(Ticket).filter(Ticket.id == ticket.id)
 
         if view_kwargs.get('user_id'):

{"golden_diff": "diff --git a/app/api/attendees.py b/app/api/attendees.py\n--- a/app/api/attendees.py\n+++ b/app/api/attendees.py\n@@ -112,8 +112,8 @@\n \n         if view_kwargs.get('ticket_id'):\n             ticket = safe_query(self, Ticket, 'id', view_kwargs['ticket_id'], 'ticket_id')\n-            if not has_access('is_registrar', event_id=ticket.event_id):\n-                raise ForbiddenException({'source': ''}, 'Access Forbidden')\n+            # if not has_access('is_registrar', event_id=ticket.event_id):\n+            #     raise ForbiddenException({'source': ''}, 'Access Forbidden')\n             query_ = query_.join(Ticket).filter(Ticket.id == ticket.id)\n \n         if view_kwargs.get('user_id'):\n", "issue": "Allow GET access to ticket attendees for an order, for non logged in users\nparent issue #5546 \n", "before_files": [{"content": "from datetime import datetime\n\nfrom flask import Blueprint, request, jsonify, abort, make_response\nfrom flask_jwt import current_identity, jwt_required\nfrom flask_rest_jsonapi import ResourceDetail, ResourceList, ResourceRelationship\nfrom flask_rest_jsonapi.exceptions import ObjectNotFound\nfrom sqlalchemy.orm.exc import NoResultFound\n\nfrom app.api.bootstrap import api\nfrom app.api.helpers.db import safe_query, get_count\nfrom app.api.helpers.exceptions import (\n    ConflictException,\n    ForbiddenException,\n    UnprocessableEntity,\n)\nfrom app.api.helpers.mail import send_email_to_attendees\nfrom app.api.helpers.permission_manager import has_access\nfrom app.api.helpers.permissions import jwt_required\nfrom app.api.helpers.query import event_query\nfrom app.api.helpers.utilities import require_relationship\nfrom app.api.schema.attendees import AttendeeSchema\nfrom app.models import db\nfrom app.models.order import Order\nfrom app.models.ticket import Ticket\nfrom app.models.ticket_holder import TicketHolder\nfrom app.models.user import User\n\nattendee_misc_routes = Blueprint('attendee_misc', __name__, url_prefix='/v1')\n\n\nclass AttendeeListPost(ResourceList):\n    \"\"\"\n       List and create Attendees through direct URL\n    \"\"\"\n\n    def before_post(self, args, kwargs, data):\n        \"\"\"\n        Before post method to check for required relationship and proper permissions\n        :param args:\n        :param kwargs:\n        :param data:\n        :return:\n        \"\"\"\n        require_relationship(['ticket', 'event'], data)\n\n        ticket = db.session.query(Ticket).filter_by(\n            id=int(data['ticket']), deleted_at=None\n            ).first()\n        if ticket is None:\n            raise UnprocessableEntity(\n                {'pointer': '/data/relationships/ticket'}, \"Invalid Ticket\"\n                )\n        if ticket.event_id != int(data['event']):\n            raise UnprocessableEntity(\n                {'pointer': '/data/relationships/ticket'},\n                \"Ticket belongs to a different Event\"\n            )\n        # Check if the ticket is already sold out or not.\n        if get_count(db.session.query(TicketHolder.id).\n                     filter_by(ticket_id=int(data['ticket']), deleted_at=None)) >= ticket.quantity:\n            raise ConflictException(\n                {'pointer': '/data/attributes/ticket_id'},\n                \"Ticket already sold out\"\n            )\n\n        if 'device_name_checkin' in data and data['device_name_checkin'] is not None:\n            if 'is_checked_in' not in data or not data['is_checked_in']:\n                raise UnprocessableEntity(\n                    {'pointer': '/data/attributes/device_name_checkin'},\n                    \"Attendee needs to be checked in first\"\n                )\n            elif 'checkin_times' not in data or data['checkin_times'] is None:\n                raise UnprocessableEntity(\n                    {'pointer': '/data/attributes/device_name_checkin'},\n                    \"Check in Times missing\"\n                )\n            elif len(data['checkin_times'].split(\",\")) != len(data['device_name_checkin'].split(\",\")):\n                raise UnprocessableEntity(\n                    {'pointer': '/data/attributes/device_name_checkin'},\n                    \"Check in Times missing for the corresponding device name\"\n                )\n\n        if 'checkin_times' in data:\n            if 'device_name_checkin' not in data or data['device_name_checkin'] is None:\n                data['device_name_checkin'] = '-'\n\n    decorators = (jwt_required,)\n    methods = ['POST']\n    schema = AttendeeSchema\n    data_layer = {'session': db.session,\n                  'model': TicketHolder}\n\n\nclass AttendeeList(ResourceList):\n    \"\"\"\n    List Attendees\n    \"\"\"\n    def query(self, view_kwargs):\n        \"\"\"\n        query method for Attendees List\n        :param view_kwargs:\n        :return:\n        \"\"\"\n        query_ = self.session.query(TicketHolder)\n\n        if view_kwargs.get('order_identifier'):\n            order = safe_query(self, Order, 'identifier', view_kwargs['order_identifier'], 'order_identifier')\n            if not has_access('is_registrar', event_id=order.event_id) and not has_access('is_user_itself',\n                                                                                         user_id=order.user_id):\n                raise ForbiddenException({'source': ''}, 'Access Forbidden')\n            query_ = query_.join(Order).filter(Order.id == order.id)\n\n        if view_kwargs.get('ticket_id'):\n            ticket = safe_query(self, Ticket, 'id', view_kwargs['ticket_id'], 'ticket_id')\n            if not has_access('is_registrar', event_id=ticket.event_id):\n                raise ForbiddenException({'source': ''}, 'Access Forbidden')\n            query_ = query_.join(Ticket).filter(Ticket.id == ticket.id)\n\n        if view_kwargs.get('user_id'):\n            user = safe_query(self, User, 'id', view_kwargs['user_id'], 'user_id')\n            if not has_access('is_user_itself', user_id=user.id):\n                raise ForbiddenException({'source': ''}, 'Access Forbidden')\n            query_ = query_.join(User, User.email == TicketHolder.email).filter(User.id == user.id)\n\n        query_ = event_query(self, query_, view_kwargs, permission='is_registrar')\n        return query_\n\n    view_kwargs = True\n    methods = ['GET', ]\n    schema = AttendeeSchema\n    data_layer = {'session': db.session,\n                  'model': TicketHolder,\n                  'methods': {\n                      'query': query\n                  }}\n\n\nclass AttendeeDetail(ResourceDetail):\n    \"\"\"\n    Attendee detail by id\n    \"\"\"\n    def before_get_object(self, view_kwargs):\n        \"\"\"\n        before get object method for attendee detail\n        :param view_kwargs:\n        :return:\n        \"\"\"\n        attendee = safe_query(self, TicketHolder, 'id', view_kwargs['id'], 'attendee_id')\n        if not has_access('is_registrar_or_user_itself', user_id=current_identity.id, event_id=attendee.event_id):\n            raise ForbiddenException({'source': 'User'}, 'You are not authorized to access this.')\n\n    def before_delete_object(self, obj, kwargs):\n        \"\"\"\n        before delete object method for attendee detail\n        :param obj:\n        :param kwargs:\n        :return:\n        \"\"\"\n        if not has_access('is_registrar', event_id=obj.event_id):\n            raise ForbiddenException({'source': 'User'}, 'You are not authorized to access this.')\n\n    def before_update_object(self, obj, data, kwargs):\n        \"\"\"\n        before update object method for attendee detail\n        :param obj:\n        :param data:\n        :param kwargs:\n        :return:\n        \"\"\"\n#         if not has_access('is_registrar', event_id=obj.event_id):\n#         raise ForbiddenException({'source': 'User'}, 'You are not authorized to access this.')\n\n        if 'device_name_checkin' in data:\n            if 'checkin_times' not in data or data['checkin_times'] is None:\n                raise UnprocessableEntity(\n                    {'pointer': '/data/attributes/device_name_checkin'},\n                    \"Check in Times missing\"\n                )\n\n        if 'is_checked_in' in data and data['is_checked_in']:\n            if 'checkin_times' not in data or data['checkin_times'] is None:\n                raise UnprocessableEntity({'pointer': '/data/attributes/checkin_times'},\n                                          \"Check in time missing while trying to check in attendee\")\n            else:\n                if obj.checkin_times and data['checkin_times'] not in obj.checkin_times.split(\",\"):\n                    data['checkin_times'] = '{},{}'.format(obj.checkin_times, data['checkin_times'])\n                elif obj.checkin_times and data['checkin_times'] in obj.checkin_times.split(\",\"):\n                    raise UnprocessableEntity(\n                        {'pointer': '/data/attributes/checkin_times'},\n                        \"Check in time already present\"\n                    )\n\n                if 'device_name_checkin' in data and data['device_name_checkin'] is not None:\n                    if obj.device_name_checkin is not None:\n                        data['device_name_checkin'] = '{},{}'.format(obj.device_name_checkin,\n                                                                     data['device_name_checkin'])\n\n                    if len(data['checkin_times'].split(\",\")) != len(data['device_name_checkin'].split(\",\")):\n                        raise UnprocessableEntity(\n                            {'pointer': '/data/attributes/device_name_checkin'},\n                            \"Check in Time missing for the corresponding device name\"\n                        )\n                else:\n                    if obj.device_name_checkin is not None:\n                        data['device_name_checkin'] = '{},{}'.format(obj.device_name_checkin, '-')\n                    else:\n                        data['device_name_checkin'] = '-'\n\n        if 'is_checked_out' in data and data['is_checked_out']:\n            attendee = safe_query(db, TicketHolder, 'id', kwargs['id'], 'attendee_id')\n            if not attendee.is_checked_out:\n                checkout_times = obj.checkout_times.split(',') if obj.checkout_times else []\n                checkout_times.append(str(datetime.utcnow()))\n                data['checkout_times'] = ','.join(checkout_times)\n\n        if 'attendee_notes' in data:\n            if obj.attendee_notes and data['attendee_notes'] not in obj.attendee_notes.split(\",\"):\n                data['attendee_notes'] = '{},{}'.format(obj.attendee_notes, data['attendee_notes'])\n\n    decorators = (jwt_required,)\n    schema = AttendeeSchema\n    data_layer = {'session': db.session,\n                  'model': TicketHolder,\n                  'methods': {\n                      'before_get_object': before_get_object,\n                      'before_update_object': before_update_object,\n                      'before_delete_object': before_delete_object\n                  }}\n\n\nclass AttendeeRelationshipRequired(ResourceRelationship):\n    \"\"\"\n    Attendee Relationship (Required)\n    \"\"\"\n    decorators = (jwt_required,)\n    methods = ['GET', 'PATCH']\n    schema = AttendeeSchema\n    data_layer = {'session': db.session,\n                  'model': TicketHolder}\n\n\nclass AttendeeRelationshipOptional(ResourceRelationship):\n    \"\"\"\n    Attendee Relationship(Optional)\n    \"\"\"\n    decorators = (api.has_permission('is_user_itself', fetch=\"user_id\", fetch_as=\"id\", model=TicketHolder),)\n    schema = AttendeeSchema\n    data_layer = {'session': db.session,\n                  'model': TicketHolder}\n\n\n@attendee_misc_routes.route('/attendees/send-receipt', methods=['POST'])\n@jwt_required\ndef send_receipt():\n    \"\"\"\n    Send receipts to attendees related to the provided order.\n    :return:\n    \"\"\"\n    order_identifier = request.json.get('order-identifier')\n    if order_identifier:\n        try:\n            order = db.session.query(Order).filter_by(identifier=order_identifier).one()\n        except NoResultFound:\n            raise ObjectNotFound({'parameter': '{identifier}'}, \"Order not found\")\n\n        if (order.user_id != current_identity.id) and (not has_access('is_registrar', event_id=order.event_id)):\n            abort(\n                make_response(jsonify(error=\"You need to be the event organizer or order buyer to send receipts.\"), 403)\n            )\n        elif order.status != 'completed':\n            abort(\n                make_response(jsonify(error=\"Cannot send receipt for an incomplete order\"), 409)\n            )\n        else:\n            send_email_to_attendees(order, current_identity.id)\n            return jsonify(message=\"receipt sent to attendees\")\n    else:\n        abort(\n            make_response(jsonify(error=\"Order identifier missing\"), 422)\n        )\n", "path": "app/api/attendees.py"}], "after_files": [{"content": "from datetime import datetime\n\nfrom flask import Blueprint, request, jsonify, abort, make_response\nfrom flask_jwt import current_identity, jwt_required\nfrom flask_rest_jsonapi import ResourceDetail, ResourceList, ResourceRelationship\nfrom flask_rest_jsonapi.exceptions import ObjectNotFound\nfrom sqlalchemy.orm.exc import NoResultFound\n\nfrom app.api.bootstrap import api\nfrom app.api.helpers.db import safe_query, get_count\nfrom app.api.helpers.exceptions import (\n    ConflictException,\n    ForbiddenException,\n    UnprocessableEntity,\n)\nfrom app.api.helpers.mail import send_email_to_attendees\nfrom app.api.helpers.permission_manager import has_access\nfrom app.api.helpers.permissions import jwt_required\nfrom app.api.helpers.query import event_query\nfrom app.api.helpers.utilities import require_relationship\nfrom app.api.schema.attendees import AttendeeSchema\nfrom app.models import db\nfrom app.models.order import Order\nfrom app.models.ticket import Ticket\nfrom app.models.ticket_holder import TicketHolder\nfrom app.models.user import User\n\nattendee_misc_routes = Blueprint('attendee_misc', __name__, url_prefix='/v1')\n\n\nclass AttendeeListPost(ResourceList):\n    \"\"\"\n       List and create Attendees through direct URL\n    \"\"\"\n\n    def before_post(self, args, kwargs, data):\n        \"\"\"\n        Before post method to check for required relationship and proper permissions\n        :param args:\n        :param kwargs:\n        :param data:\n        :return:\n        \"\"\"\n        require_relationship(['ticket', 'event'], data)\n\n        ticket = db.session.query(Ticket).filter_by(\n            id=int(data['ticket']), deleted_at=None\n            ).first()\n        if ticket is None:\n            raise UnprocessableEntity(\n                {'pointer': '/data/relationships/ticket'}, \"Invalid Ticket\"\n                )\n        if ticket.event_id != int(data['event']):\n            raise UnprocessableEntity(\n                {'pointer': '/data/relationships/ticket'},\n                \"Ticket belongs to a different Event\"\n            )\n        # Check if the ticket is already sold out or not.\n        if get_count(db.session.query(TicketHolder.id).\n                     filter_by(ticket_id=int(data['ticket']), deleted_at=None)) >= ticket.quantity:\n            raise ConflictException(\n                {'pointer': '/data/attributes/ticket_id'},\n                \"Ticket already sold out\"\n            )\n\n        if 'device_name_checkin' in data and data['device_name_checkin'] is not None:\n            if 'is_checked_in' not in data or not data['is_checked_in']:\n                raise UnprocessableEntity(\n                    {'pointer': '/data/attributes/device_name_checkin'},\n                    \"Attendee needs to be checked in first\"\n                )\n            elif 'checkin_times' not in data or data['checkin_times'] is None:\n                raise UnprocessableEntity(\n                    {'pointer': '/data/attributes/device_name_checkin'},\n                    \"Check in Times missing\"\n                )\n            elif len(data['checkin_times'].split(\",\")) != len(data['device_name_checkin'].split(\",\")):\n                raise UnprocessableEntity(\n                    {'pointer': '/data/attributes/device_name_checkin'},\n                    \"Check in Times missing for the corresponding device name\"\n                )\n\n        if 'checkin_times' in data:\n            if 'device_name_checkin' not in data or data['device_name_checkin'] is None:\n                data['device_name_checkin'] = '-'\n\n    decorators = (jwt_required,)\n    methods = ['POST']\n    schema = AttendeeSchema\n    data_layer = {'session': db.session,\n                  'model': TicketHolder}\n\n\nclass AttendeeList(ResourceList):\n    \"\"\"\n    List Attendees\n    \"\"\"\n    def query(self, view_kwargs):\n        \"\"\"\n        query method for Attendees List\n        :param view_kwargs:\n        :return:\n        \"\"\"\n        query_ = self.session.query(TicketHolder)\n\n        if view_kwargs.get('order_identifier'):\n            order = safe_query(self, Order, 'identifier', view_kwargs['order_identifier'], 'order_identifier')\n            if not has_access('is_registrar', event_id=order.event_id) and not has_access('is_user_itself',\n                                                                                         user_id=order.user_id):\n                raise ForbiddenException({'source': ''}, 'Access Forbidden')\n            query_ = query_.join(Order).filter(Order.id == order.id)\n\n        if view_kwargs.get('ticket_id'):\n            ticket = safe_query(self, Ticket, 'id', view_kwargs['ticket_id'], 'ticket_id')\n            # if not has_access('is_registrar', event_id=ticket.event_id):\n            #     raise ForbiddenException({'source': ''}, 'Access Forbidden')\n            query_ = query_.join(Ticket).filter(Ticket.id == ticket.id)\n\n        if view_kwargs.get('user_id'):\n            user = safe_query(self, User, 'id', view_kwargs['user_id'], 'user_id')\n            if not has_access('is_user_itself', user_id=user.id):\n                raise ForbiddenException({'source': ''}, 'Access Forbidden')\n            query_ = query_.join(User, User.email == TicketHolder.email).filter(User.id == user.id)\n\n        query_ = event_query(self, query_, view_kwargs, permission='is_registrar')\n        return query_\n\n    view_kwargs = True\n    methods = ['GET', ]\n    schema = AttendeeSchema\n    data_layer = {'session': db.session,\n                  'model': TicketHolder,\n                  'methods': {\n                      'query': query\n                  }}\n\n\nclass AttendeeDetail(ResourceDetail):\n    \"\"\"\n    Attendee detail by id\n    \"\"\"\n    def before_get_object(self, view_kwargs):\n        \"\"\"\n        before get object method for attendee detail\n        :param view_kwargs:\n        :return:\n        \"\"\"\n        attendee = safe_query(self, TicketHolder, 'id', view_kwargs['id'], 'attendee_id')\n        if not has_access('is_registrar_or_user_itself', user_id=current_identity.id, event_id=attendee.event_id):\n            raise ForbiddenException({'source': 'User'}, 'You are not authorized to access this.')\n\n    def before_delete_object(self, obj, kwargs):\n        \"\"\"\n        before delete object method for attendee detail\n        :param obj:\n        :param kwargs:\n        :return:\n        \"\"\"\n        if not has_access('is_registrar', event_id=obj.event_id):\n            raise ForbiddenException({'source': 'User'}, 'You are not authorized to access this.')\n\n    def before_update_object(self, obj, data, kwargs):\n        \"\"\"\n        before update object method for attendee detail\n        :param obj:\n        :param data:\n        :param kwargs:\n        :return:\n        \"\"\"\n#         if not has_access('is_registrar', event_id=obj.event_id):\n#         raise ForbiddenException({'source': 'User'}, 'You are not authorized to access this.')\n\n        if 'device_name_checkin' in data:\n            if 'checkin_times' not in data or data['checkin_times'] is None:\n                raise UnprocessableEntity(\n                    {'pointer': '/data/attributes/device_name_checkin'},\n                    \"Check in Times missing\"\n                )\n\n        if 'is_checked_in' in data and data['is_checked_in']:\n            if 'checkin_times' not in data or data['checkin_times'] is None:\n                raise UnprocessableEntity({'pointer': '/data/attributes/checkin_times'},\n                                          \"Check in time missing while trying to check in attendee\")\n            else:\n                if obj.checkin_times and data['checkin_times'] not in obj.checkin_times.split(\",\"):\n                    data['checkin_times'] = '{},{}'.format(obj.checkin_times, data['checkin_times'])\n                elif obj.checkin_times and data['checkin_times'] in obj.checkin_times.split(\",\"):\n                    raise UnprocessableEntity(\n                        {'pointer': '/data/attributes/checkin_times'},\n                        \"Check in time already present\"\n                    )\n\n                if 'device_name_checkin' in data and data['device_name_checkin'] is not None:\n                    if obj.device_name_checkin is not None:\n                        data['device_name_checkin'] = '{},{}'.format(obj.device_name_checkin,\n                                                                     data['device_name_checkin'])\n\n                    if len(data['checkin_times'].split(\",\")) != len(data['device_name_checkin'].split(\",\")):\n                        raise UnprocessableEntity(\n                            {'pointer': '/data/attributes/device_name_checkin'},\n                            \"Check in Time missing for the corresponding device name\"\n                        )\n                else:\n                    if obj.device_name_checkin is not None:\n                        data['device_name_checkin'] = '{},{}'.format(obj.device_name_checkin, '-')\n                    else:\n                        data['device_name_checkin'] = '-'\n\n        if 'is_checked_out' in data and data['is_checked_out']:\n            attendee = safe_query(db, TicketHolder, 'id', kwargs['id'], 'attendee_id')\n            if not attendee.is_checked_out:\n                checkout_times = obj.checkout_times.split(',') if obj.checkout_times else []\n                checkout_times.append(str(datetime.utcnow()))\n                data['checkout_times'] = ','.join(checkout_times)\n\n        if 'attendee_notes' in data:\n            if obj.attendee_notes and data['attendee_notes'] not in obj.attendee_notes.split(\",\"):\n                data['attendee_notes'] = '{},{}'.format(obj.attendee_notes, data['attendee_notes'])\n\n    decorators = (jwt_required,)\n    schema = AttendeeSchema\n    data_layer = {'session': db.session,\n                  'model': TicketHolder,\n                  'methods': {\n                      'before_get_object': before_get_object,\n                      'before_update_object': before_update_object,\n                      'before_delete_object': before_delete_object\n                  }}\n\n\nclass AttendeeRelationshipRequired(ResourceRelationship):\n    \"\"\"\n    Attendee Relationship (Required)\n    \"\"\"\n    decorators = (jwt_required,)\n    methods = ['GET', 'PATCH']\n    schema = AttendeeSchema\n    data_layer = {'session': db.session,\n                  'model': TicketHolder}\n\n\nclass AttendeeRelationshipOptional(ResourceRelationship):\n    \"\"\"\n    Attendee Relationship(Optional)\n    \"\"\"\n    decorators = (api.has_permission('is_user_itself', fetch=\"user_id\", fetch_as=\"id\", model=TicketHolder),)\n    schema = AttendeeSchema\n    data_layer = {'session': db.session,\n                  'model': TicketHolder}\n\n\n@attendee_misc_routes.route('/attendees/send-receipt', methods=['POST'])\n@jwt_required\ndef send_receipt():\n    \"\"\"\n    Send receipts to attendees related to the provided order.\n    :return:\n    \"\"\"\n    order_identifier = request.json.get('order-identifier')\n    if order_identifier:\n        try:\n            order = db.session.query(Order).filter_by(identifier=order_identifier).one()\n        except NoResultFound:\n            raise ObjectNotFound({'parameter': '{identifier}'}, \"Order not found\")\n\n        if (order.user_id != current_identity.id) and (not has_access('is_registrar', event_id=order.event_id)):\n            abort(\n                make_response(jsonify(error=\"You need to be the event organizer or order buyer to send receipts.\"), 403)\n            )\n        elif order.status != 'completed':\n            abort(\n                make_response(jsonify(error=\"Cannot send receipt for an incomplete order\"), 409)\n            )\n        else:\n            send_email_to_attendees(order, current_identity.id)\n            return jsonify(message=\"receipt sent to attendees\")\n    else:\n        abort(\n            make_response(jsonify(error=\"Order identifier missing\"), 422)\n        )\n", "path": "app/api/attendees.py"}]}