diff --git a/app/api/helpers/notification.py b/app/api/helpers/notification.py index bbf6b6b5ae..e8b831e52b 100644 --- a/app/api/helpers/notification.py +++ b/app/api/helpers/notification.py @@ -304,11 +304,15 @@ def send_notif_ticket_cancel(order): send_notification( user=order.event.owner, title=NOTIFS[TICKET_CANCELLED_ORGANIZER]['title'].format( - invoice_id=order.invoice_number + invoice_id=order.invoice_number, + event_name=order.event.name ), message=NOTIFS[TICKET_CANCELLED_ORGANIZER]['message'].format( cancel_note=order.cancel_note, - invoice_id=order.invoice_number + invoice_id=order.invoice_number, + event_name=order.event.name, + cancel_order_page=make_frontend_url('/events/{identifier}/tickets/orders/cancelled' + .format(identifier=order.event.identifier)) ) ) diff --git a/app/api/orders.py b/app/api/orders.py index 4737f9a7c4..2c4e6291a9 100644 --- a/app/api/orders.py +++ b/app/api/orders.py @@ -284,26 +284,51 @@ def before_update_object(self, order, data, view_kwargs): if current_user.id == order.user_id: # Order created from the tickets tab. for element in data: - if data[element] and data[element]\ - != getattr(order, element, None) and element not in get_updatable_fields(): - raise ForbiddenException({'pointer': 'data/{}'.format(element)}, - "You cannot update {} of an order".format(element)) + if data[element]: + if element not in ['event', 'ticket_holders', 'user'] and data[element]\ + != getattr(order, element, None) and element not in get_updatable_fields(): + raise ForbiddenException({'pointer': 'data/{}'.format(element)}, + "You cannot update {} of an order".format(element)) + elif element in ['event', 'user'] and data[element]\ + != str(getattr(order, element, None).id) and element not in get_updatable_fields(): + raise ForbiddenException({'pointer': 'data/{}'.format(element)}, + "You cannot update {} of an order".format(element)) + elif element == 'ticket_holders': + ticket_holders = [] + for ticket_holder in order.ticket_holders: + ticket_holders.append(str(ticket_holder.id)) + if data[element] != ticket_holders and element not in get_updatable_fields(): + raise ForbiddenException({'pointer': 'data/{}'.format(element)}, + "You cannot update {} of an order".format(element)) else: # Order created from the public pages. for element in data: - if data[element] and data[element] != getattr(order, element, None): - if element != 'status' and element != 'deleted_at': + if data[element]: + if element not in ['event', 'ticket_holders', 'user'] and data[element]\ + != getattr(order, element, None): + if element != 'status' and element != 'deleted_at': + raise ForbiddenException({'pointer': 'data/{}'.format(element)}, + "You cannot update {} of an order".format(element)) + elif element == 'status' and order.amount and order.status == 'completed': + # Since we don't have a refund system. + raise ForbiddenException({'pointer': 'data/status'}, + "You cannot update the status of a completed paid order") + elif element == 'status' and order.status == 'cancelled': + # Since the tickets have been unlocked and we can't revert it. + raise ForbiddenException({'pointer': 'data/status'}, + "You cannot update the status of a cancelled order") + elif element in ['event', 'user'] and data[element]\ + != str(getattr(order, element, None).id): raise ForbiddenException({'pointer': 'data/{}'.format(element)}, "You cannot update {} of an order".format(element)) - elif element == 'status' and order.amount and order.status == 'completed': - # Since we don't have a refund system. - raise ForbiddenException({'pointer': 'data/status'}, - "You cannot update the status of a completed paid order") - elif element == 'status' and order.status == 'cancelled': - # Since the tickets have been unlocked and we can't revert it. - raise ForbiddenException({'pointer': 'data/status'}, - "You cannot update the status of a cancelled order") + elif element == 'ticket_holders': + ticket_holders = [] + for ticket_holder in order.ticket_holders: + ticket_holders.append(str(ticket_holder.id)) + if data[element] != ticket_holders: + raise ForbiddenException({'pointer': 'data/{}'.format(element)}, + "You cannot update {} of an order".format(element)) elif current_user.id == order.user_id: if order.status != 'initializing' and order.status != 'pending': @@ -311,14 +336,26 @@ def before_update_object(self, order, data, view_kwargs): "You cannot update a non-initialized or non-pending order") else: for element in data: - if element == 'is_billing_enabled' and order.status == 'completed' and data[element]\ - and data[element] != getattr(order, element, None): - raise ForbiddenException({'pointer': 'data/{}'.format(element)}, - "You cannot update {} of a completed order".format(element)) - elif data[element] and data[element]\ - != getattr(order, element, None) and element not in get_updatable_fields(): - raise ForbiddenException({'pointer': 'data/{}'.format(element)}, - "You cannot update {} of an order".format(element)) + if data[element]: + if element == 'is_billing_enabled' and order.status == 'completed'\ + and data[element] != getattr(order, element, None): + raise ForbiddenException({'pointer': 'data/{}'.format(element)}, + "You cannot update {} of a completed order".format(element)) + elif element not in ['event', 'ticket_holders', 'user'] and data[element]\ + != getattr(order, element, None) and element not in get_updatable_fields(): + raise ForbiddenException({'pointer': 'data/{}'.format(element)}, + "You cannot update {} of an order".format(element)) + elif element in ['event', 'user'] and data[element]\ + != str(getattr(order, element, None).id) and element not in get_updatable_fields(): + raise ForbiddenException({'pointer': 'data/{}'.format(element)}, + "You cannot update {} of an order".format(element)) + elif element == 'ticket_holders': + ticket_holders = [] + for ticket_holder in order.ticket_holders: + ticket_holders.append(str(ticket_holder.id)) + if data[element] != ticket_holders and element not in get_updatable_fields(): + raise ForbiddenException({'pointer': 'data/{}'.format(element)}, + "You cannot update {} of an order".format(element)) if has_access('is_organizer', event_id=order.event_id) and 'order_notes' in data: if order.order_notes and data['order_notes'] not in order.order_notes.split(","): diff --git a/tests/all/integration/api/test_order.py b/tests/all/integration/api/test_order.py new file mode 100644 index 0000000000..87efad4d73 --- /dev/null +++ b/tests/all/integration/api/test_order.py @@ -0,0 +1,46 @@ +import unittest +from flask import Request, request, jsonify + +from app import current_app as app, db +from app.api.helpers.db import save_to_db +from app.factories.attendee import AttendeeFactory +from app.factories.event import EventFactoryBasic +from app.factories.order import OrderFactory +from app.models.order import Order +from app.api.helpers.db import save_to_db +from tests.all.integration.setup_database import Setup + + +class TestOrderUtilities(OpenEventTestCase): + def setUp(self): + self.app = Setup.create_app() + + def test_order_cancellation(self): + """Method to test order cancellation""" + + with app.test_request_context(): + client = app.test_client() + attendee = AttendeeFactory() + save_to_db(attendee) + + obj = OrderFactory() + obj.ticket_holders = [attendee, ] + save_to_db(obj) + data = { + "data": { + "attributes": { + "status": "cancelled" + }, + "type": "order", + "id": obj.id + } + } + resp = client.patch('/v1/orders/1', data=dict( + order=obj, + data=data)) + res = resp.get_json() + self.assertEqual(res['status'], 'cancelled') + + +if __name__ == '__main__': + unittest.main()