diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index f85f6dd8a1..f3b015dcd6 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -1,5 +1,5 @@ -name-template: v$NEXT_PATCH_VERSION 🌈 -tag-template: v$NEXT_PATCH_VERSION +name-template: Release v$NEXT_MINOR_VERSION 🌈 +tag-template: v$NEXT_MINOR_VERSION categories: - title: 🚀 Features label: feature diff --git a/app/api/__init__.py b/app/api/__init__.py index 56566a2d91..66171f12b3 100644 --- a/app/api/__init__.py +++ b/app/api/__init__.py @@ -87,7 +87,7 @@ from app.api.users import UserList, UserDetail, UserRelationship # users -api.route(UserList, 'user_list', '/users') +api.route(UserList, 'user_list', '/users', '/events//organizers') api.route(UserDetail, 'user_detail', '/users/', '/notifications//user', '/event-invoices//user', '/speakers//user', '/access-codes//marketer', '/email-notifications//user', @@ -102,12 +102,12 @@ api.route(UserRelationship, 'user_access_codes', '/users//relationships/access-codes') api.route(UserRelationship, 'user_discount_codes', '/users//relationships/discount-codes') api.route(UserRelationship, 'user_email_notifications', '/users//relationships/email-notifications') -api.route(UserRelationship, 'user_owner_event', '/users//relationships/owner-events') -api.route(UserRelationship, 'user_organizer_event', '/users//relationships/organizer-events') -api.route(UserRelationship, 'user_coorganizer_event', '/users//relationships/coorganizer-events') -api.route(UserRelationship, 'user_track_organizer_event', '/users//relationships/track-organizer-events') -api.route(UserRelationship, 'user_registrar_event', '/users//relationships/registrar-events') -api.route(UserRelationship, 'user_moderator_event', '/users//relationships/moderator-events') +api.route(UserRelationship, 'user_owner_events', '/users//relationships/owner-events') +api.route(UserRelationship, 'user_organizer_events', '/users//relationships/organizer-events') +api.route(UserRelationship, 'user_coorganizer_events', '/users//relationships/coorganizer-events') +api.route(UserRelationship, 'user_track_organizer_events', '/users//relationships/track-organizer-events') +api.route(UserRelationship, 'user_registrar_events', '/users//relationships/registrar-events') +api.route(UserRelationship, 'user_moderator_events', '/users//relationships/moderator-events') api.route(UserRelationship, 'user_attendees', '/users//relationships/attendees') api.route(UserRelationship, 'user_events', '/users//relationships/events') api.route(UserRelationship, 'user_orders', '/users//relationships/orders') @@ -200,7 +200,6 @@ api.route(PanelPermissionRelationship, 'panel_permissions_custom_system_roles', '/panel-permissions//relationships/custom-system-roles') - # roles api.route(RoleList, 'role_list', '/roles') api.route(RoleDetail, 'role_detail', '/roles/', '/role-invites//role') @@ -246,7 +245,16 @@ api.route(EventList, 'event_list', '/events', '/event-types//events', '/event-topics//events', '/event-sub-topics//events', '/discount-codes//events', - '/users//events') + '/users//events', + '/users//owner-events', + '/users//organizer-events', + '/users//coorganizer-events', + '/users//track-organizer-events', + '/users//registrar-events', + '/users//moderator-events', + '/users//marketer-events', + '/users//sales-admin-events') + api.route(EventDetail, 'event_detail', '/events/', '/events/', '/tickets//event', '/microlocations//event', '/social-links//event', @@ -544,7 +552,7 @@ '/tickets//access-codes') api.route(AccessCodeDetail, 'access_code_detail', '/access-codes/', '/events//access-codes/', - '/events//access-codes/',) + '/events//access-codes/',) api.route(AccessCodeRelationshipRequired, 'access_code_event', '/access-codes//relationships/event') api.route(AccessCodeRelationshipOptional, 'access_code_user', diff --git a/app/api/access_codes.py b/app/api/access_codes.py index c2190a5bed..730f2603b7 100644 --- a/app/api/access_codes.py +++ b/app/api/access_codes.py @@ -115,7 +115,7 @@ def before_get(self, args, kwargs): if kwargs.get('access_event_identifier'): event = safe_query( - db, Event, 'identifier', kwargs['discount_event_identifier'], + db, Event, 'identifier', kwargs['access_event_identifier'], 'event_identifier') kwargs['access_event_id'] = event.id if kwargs.get('code') and kwargs.get('access_event_id'): diff --git a/app/api/custom_forms.py b/app/api/custom_forms.py index cb2dd354f1..18c6dd9791 100644 --- a/app/api/custom_forms.py +++ b/app/api/custom_forms.py @@ -88,7 +88,10 @@ def before_get_object(self, view_kwargs): fetch_as="event_id", model=CustomForms, methods="PATCH,DELETE"), ) schema = CustomFormSchema data_layer = {'session': db.session, - 'model': CustomForms} + 'model': CustomForms, + 'methods': { + 'before_get_object': before_get_object + }} class CustomFormRelationshipRequired(ResourceRelationship): diff --git a/app/api/custom_system_roles.py b/app/api/custom_system_roles.py index d63bb80f8d..13e30a3602 100644 --- a/app/api/custom_system_roles.py +++ b/app/api/custom_system_roles.py @@ -54,7 +54,10 @@ def before_get_object(self, view_kwargs): decorators = (api.has_permission('is_admin', methods="PATCH,DELETE"),) schema = CustomSystemRoleSchema data_layer = {'session': db.session, - 'model': CustomSysRole} + 'model': CustomSysRole, + 'methods': { + 'before_get_object': before_get_object + }} class CustomSystemRoleRelationship(ResourceRelationship): diff --git a/app/api/events.py b/app/api/events.py index 5649e9ddf7..7722c3fd64 100644 --- a/app/api/events.py +++ b/app/api/events.py @@ -46,10 +46,12 @@ from app.models.ticket_holder import TicketHolder from app.models.track import Track from app.models.user_favourite_event import UserFavouriteEvent -from app.models.user import User, ATTENDEE, OWNER, ORGANIZER, COORGANIZER +from app.models.user import User, ATTENDEE, OWNER, ORGANIZER, COORGANIZER, TRACK_ORGANIZER, REGISTRAR, MODERATOR, \ + SALES_ADMIN, MARKETER from app.models.users_events_role import UsersEventsRoles from app.models.stripe_authorization import StripeAuthorization + def validate_event(user, modules, data): if not user.can_create_event(): raise ForbiddenException({'source': ''}, @@ -105,8 +107,13 @@ def validate_date(event, data): "ends-at should be after starts-at") if datetime.timestamp(data['starts_at']) <= datetime.timestamp(datetime.now()): - raise UnprocessableEntity({'pointer': '/data/attributes/starts-at'}, - "starts-at should be after current date-time") + if event and event.deleted_at and not data.get('deleted_at'): + data['state'] = 'draft' + elif event and not event.deleted_at and data.get('deleted_at'): + pass + else: + raise UnprocessableEntity({'pointer': '/data/attributes/starts-at'}, + "starts-at should be after current date-time") class EventList(ResourceList): def before_get(self, args, kwargs): @@ -142,6 +149,62 @@ def query(self, view_kwargs): query_ = query_.join(Event.roles).filter_by(user_id=user.id).join(UsersEventsRoles.role). \ filter(Role.name != ATTENDEE) + if view_kwargs.get('user_owner_id') and 'GET' in request.method: + if not has_access('is_user_itself', user_id=int(view_kwargs['user_owner_id'])): + raise ForbiddenException({'source': ''}, 'Access Forbidden') + user = safe_query(db, User, 'id', view_kwargs['user_owner_id'], 'user_owner_id') + query_ = query_.join(Event.roles).filter_by(user_id=user.id).join(UsersEventsRoles.role). \ + filter(Role.name == OWNER) + + if view_kwargs.get('user_organizer_id') and 'GET' in request.method: + if not has_access('is_user_itself', user_id=int(view_kwargs['user_organizer_id'])): + raise ForbiddenException({'source': ''}, 'Access Forbidden') + user = safe_query(db, User, 'id', view_kwargs['user_organizer_id'], 'user_organizer_id') + query_ = query_.join(Event.roles).filter_by(user_id=user.id).join(UsersEventsRoles.role). \ + filter(Role.name == ORGANIZER) + + if view_kwargs.get('user_coorganizer_id') and 'GET' in request.method: + if not has_access('is_user_itself', user_id=int(view_kwargs['user_coorganizer_id'])): + raise ForbiddenException({'source': ''}, 'Access Forbidden') + user = safe_query(db, User, 'id', view_kwargs['user_coorganizer_id'], 'user_coorganizer_id') + query_ = query_.join(Event.roles).filter_by(user_id=user.id).join(UsersEventsRoles.role). \ + filter(Role.name == COORGANIZER) + + if view_kwargs.get('user_track_organizer_id') and 'GET' in request.method: + if not has_access('is_user_itself', user_id=int(view_kwargs['user_track_organizer_id'])): + raise ForbiddenException({'source': ''}, 'Access Forbidden') + user = safe_query(db, User, 'id', view_kwargs['user_track_organizer_id'], 'user_organizer_id') + query_ = query_.join(Event.roles).filter_by(user_id=user.id).join(UsersEventsRoles.role). \ + filter(Role.name == TRACK_ORGANIZER) + + if view_kwargs.get('user_registrar_id') and 'GET' in request.method: + if not has_access('is_user_itself', user_id=int(view_kwargs['user_registrar_id'])): + raise ForbiddenException({'source': ''}, 'Access Forbidden') + user = safe_query(db, User, 'id', view_kwargs['user_registrar_id'], 'user_registrar_id') + query_ = query_.join(Event.roles).filter_by(user_id=user.id).join(UsersEventsRoles.role). \ + filter(Role.name == REGISTRAR) + + if view_kwargs.get('user_moderator_id') and 'GET' in request.method: + if not has_access('is_user_itself', user_id=int(view_kwargs['user_moderator_id'])): + raise ForbiddenException({'source': ''}, 'Access Forbidden') + user = safe_query(db, User, 'id', view_kwargs['user_moderator_id'], 'user_moderator_id') + query_ = query_.join(Event.roles).filter_by(user_id=user.id).join(UsersEventsRoles.role). \ + filter(Role.name == MODERATOR) + + if view_kwargs.get('user_marketer_id') and 'GET' in request.method: + if not has_access('is_user_itself', user_id=int(view_kwargs['user_marketer_id'])): + raise ForbiddenException({'source': ''}, 'Access Forbidden') + user = safe_query(db, User, 'id', view_kwargs['user_marketer_id'], 'user_marketer_id') + query_ = query_.join(Event.roles).filter_by(user_id=user.id).join(UsersEventsRoles.role). \ + filter(Role.name == MARKETER) + + if view_kwargs.get('user_sales_admin_id') and 'GET' in request.method: + if not has_access('is_user_itself', user_id=int(view_kwargs['user_sales_admin_id'])): + raise ForbiddenException({'source': ''}, 'Access Forbidden') + user = safe_query(db, User, 'id', view_kwargs['user_sales_admin_id'], 'user_sales_admin_id') + query_ = query_.join(Event.roles).filter_by(user_id=user.id).join(UsersEventsRoles.role). \ + filter(Role.name == SALES_ADMIN) + if view_kwargs.get('event_type_id') and 'GET' in request.method: query_ = self.session.query(Event).filter( getattr(Event, 'event_type_id') == view_kwargs['event_type_id']) @@ -175,7 +238,8 @@ def before_post(self, args, kwargs, data=None): user = User.query.filter_by(id=kwargs['user_id']).first() modules = Module.query.first() validate_event(user, modules, data) - validate_date(None, data) + if data['state'] != 'draft': + validate_date(None, data) def after_create_object(self, event, data, view_kwargs): """ @@ -492,7 +556,11 @@ def before_update_object(self, event, data, view_kwargs): :param view_kwargs: :return: """ - if data.get('starts_at') != event.starts_at or data.get('ends_at') != event.ends_at: + is_date_updated = (data.get('starts_at') != event.starts_at or data.get('ends_at') != event.ends_at) + is_draft_published = (event.state == "draft" and data.get('state') == "published") + is_event_restored = (event.deleted_at and not data.get('deleted_at')) + + if is_date_updated or is_draft_published or is_event_restored: validate_date(event, data) if has_access('is_admin') and data.get('deleted_at') != event.deleted_at: @@ -522,6 +590,7 @@ def after_update_object(self, event, data, view_kwargs): 'model': Event, 'methods': { 'before_update_object': before_update_object, + 'before_get_object': before_get_object, 'after_update_object': after_update_object, 'before_patch': before_patch }} diff --git a/app/api/feedbacks.py b/app/api/feedbacks.py index 14d8f304a9..f24f54ca9e 100644 --- a/app/api/feedbacks.py +++ b/app/api/feedbacks.py @@ -146,7 +146,10 @@ def before_update_object(self, feedback, data, view_kwargs): schema = FeedbackSchema data_layer = {'session': db.session, 'model': Feedback, - 'methods': {'before_update_object': before_update_object}} + 'methods': { + 'before_update_object': before_update_object, + 'before_get_object': before_get_object + }} class FeedbackRelationship(ResourceRelationship): 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..8cfd69bde0 100644 --- a/app/api/orders.py +++ b/app/api/orders.py @@ -40,6 +40,20 @@ alipay_blueprint = Blueprint('alipay_blueprint', __name__, url_prefix='/v1/alipay') +def check_event_user_ticket_holders(order, data, element): + if 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 == '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)) + + class OrdersListPost(ResourceList): """ OrderListPost class for OrderSchema @@ -284,26 +298,33 @@ 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)) + else: + check_event_user_ticket_holders(order, data, 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': - 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") + 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") + else: + check_event_user_ticket_holders(order, data, element) elif current_user.id == order.user_id: if order.status != 'initializing' and order.status != 'pending': @@ -311,14 +332,17 @@ 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)) + else: + check_event_user_ticket_holders(order, data, 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/app/api/schema/events.py b/app/api/schema/events.py index 532b5e25af..ecd8beb43b 100644 --- a/app/api/schema/events.py +++ b/app/api/schema/events.py @@ -82,6 +82,7 @@ def validate_timezone(self, data, original_data): revenue = fields.Float(dump_only=True) paypal_email = fields.Str(allow_none=True) is_tax_enabled = fields.Bool(default=False) + is_billing_info_mandatory = fields.Bool(default=False) is_donation_enabled = fields.Bool(default=False) can_pay_by_paypal = fields.Bool(default=False) can_pay_by_stripe = fields.Bool(default=False) @@ -277,6 +278,13 @@ def validate_timezone(self, data, original_data): schema='UserSchemaPublic', type_='user', many=True) + stripe_authorization = Relationship(attribute='stripe_authorization', + self_view='v1.stripe_authorization_event', + self_view_kwargs={'id': ''}, + related_view='v1.stripe_authorization_detail', + related_view_kwargs={'event_id': ''}, + schema='StripeAuthorizationSchema', + type_='stripe-authorization') class EventSchema(EventSchemaPublic): @@ -354,10 +362,3 @@ class Meta: schema='AttendeeSchema', many=True, type_='attendee') - stripe_authorization = Relationship(attribute='stripe_authorization', - self_view='v1.stripe_authorization_event', - self_view_kwargs={'id': ''}, - related_view='v1.stripe_authorization_detail', - related_view_kwargs={'event_id': ''}, - schema='StripeAuthorizationSchema', - type_='stripe-authorization') diff --git a/app/api/schema/stripe_authorization.py b/app/api/schema/stripe_authorization.py index 676bbe174f..f223375428 100644 --- a/app/api/schema/stripe_authorization.py +++ b/app/api/schema/stripe_authorization.py @@ -5,7 +5,7 @@ from app.api.schema.base import SoftDeletionSchema -class StripeAuthorizationSchema(SoftDeletionSchema): +class StripeAuthorizationSchemaPublic(SoftDeletionSchema): """ Stripe Authorization Schema """ @@ -21,7 +21,6 @@ class Meta: id = fields.Str(dump_only=True) stripe_publishable_key = fields.Str(dump_only=True) - stripe_auth_code = fields.Str(load_only=True, required=True) event = Relationship(attribute='event', self_view='v1.stripe_authorization_event', @@ -30,3 +29,22 @@ class Meta: related_view_kwargs={'stripe_authorization_id': ''}, schema="EventSchema", type_='event') + + +class StripeAuthorizationSchema(StripeAuthorizationSchemaPublic): + """ + Stripe Authorization Schema + """ + + class Meta: + """ + Meta class for StripeAuthorization Api Schema + """ + type_ = 'stripe-authorization' + self_view = 'v1.stripe_authorization_detail' + self_view_kwargs = {'id': ''} + inflect = dasherize + + stripe_auth_code = fields.Str(load_only=True, required=True) + + diff --git a/app/api/schema/users.py b/app/api/schema/users.py index 3bb20dc3d9..c2b6dc379a 100644 --- a/app/api/schema/users.py +++ b/app/api/schema/users.py @@ -160,44 +160,50 @@ class Meta: many=True, type_='session') owner_events = Relationship( - self_view='v1.user_owner_event', + self_view='v1.user_owner_events', self_view_kwargs={'id': ''}, related_view='v1.event_list', + related_view_kwargs={'user_owner_id': ''}, schema='EventSchema', many=True, type_='event') organizer_events = Relationship( - self_view='v1.user_organizer_event', + self_view='v1.user_organizer_events', self_view_kwargs={'id': ''}, + related_view_kwargs={'user_organizer_id': ''}, related_view='v1.event_list', schema='EventSchema', many=True, type_='event') coorganizer_events = Relationship( - self_view='v1.user_coorganizer_event', + self_view='v1.user_coorganizer_events', self_view_kwargs={'id': ''}, related_view='v1.event_list', + related_view_kwargs={'user_coorganizer_id': ''}, schema='EventSchema', many=True, type_='event') track_organizer_events = Relationship( - self_view='v1.user_track_organizer_event', + self_view='v1.user_track_organizer_events', self_view_kwargs={'id': ''}, related_view='v1.event_list', + related_view_kwargs={'user_track_organizer_id': ''}, schema='EventSchema', many=True, type_='event') registrar_events = Relationship( - self_view='v1.user_registrar_event', + self_view='v1.user_registrar_events', self_view_kwargs={'id': ''}, related_view='v1.event_list', + related_view_kwargs={'user_registrar_id': ''}, schema='EventSchema', many=True, type_='event') moderator_events = Relationship( - self_view='v1.user_moderator_event', + self_view='v1.user_moderator_events', self_view_kwargs={'id': ''}, related_view='v1.event_list', + related_view_kwargs={'user_moderator_id': ''}, schema='EventSchema', many=True, type_='event') diff --git a/app/api/stripe_authorization.py b/app/api/stripe_authorization.py index fe772366b2..4f84633ff1 100644 --- a/app/api/stripe_authorization.py +++ b/app/api/stripe_authorization.py @@ -2,13 +2,14 @@ from sqlalchemy.orm.exc import NoResultFound from app.api.bootstrap import api +from flask import request from app.api.helpers.db import safe_query, get_count, save_to_db from app.api.helpers.exceptions import ForbiddenException, ConflictException, UnprocessableEntity from app.api.helpers.payment import StripePaymentsManager from app.api.helpers.permission_manager import has_access from app.api.helpers.permissions import jwt_required from app.api.helpers.utilities import require_relationship -from app.api.schema.stripe_authorization import StripeAuthorizationSchema +from app.api.schema.stripe_authorization import StripeAuthorizationSchema, StripeAuthorizationSchemaPublic from app.models import db from app.models.event import Event from app.models.stripe_authorization import StripeAuthorization @@ -69,7 +70,8 @@ def after_create_object(self, stripe_authorization, data, view_kwargs): save_to_db(event) schema = StripeAuthorizationSchema - decorators = (jwt_required, ) + decorators = (api.has_permission('is_coorganizer', fetch="event_id", + fetch_as="event_id", model=StripeAuthorization),) methods = ['POST'] data_layer = {'session': db.session, 'model': StripeAuthorization, @@ -83,6 +85,20 @@ class StripeAuthorizationDetail(ResourceDetail): """ Stripe Authorization Detail Resource by ID """ + + def before_get(self, args, kwargs): + """ + method for assigning schema based on access + :param args: + :param kwargs: + :return: + """ + kwargs = get_id(kwargs) + if 'Authorization' in request.headers and has_access('is_coorganizer', event_id=kwargs['id']): + self.schema = StripeAuthorizationSchema + else: + self.schema = StripeAuthorizationSchemaPublic + def before_get_object(self, view_kwargs): """ method to get id of stripe authorization related to an event @@ -107,8 +123,7 @@ def after_delete_object(self, stripe_authorization, view_kwargs): event.is_stripe_linked = False save_to_db(event) - decorators = (api.has_permission('is_coorganizer', fetch="event_id", - fetch_as="event_id", model=StripeAuthorization),) + decorators = (jwt_required,) schema = StripeAuthorizationSchema data_layer = {'session': db.session, 'model': StripeAuthorization, @@ -123,8 +138,25 @@ class StripeAuthorizationRelationship(ResourceDetail): Stripe Authorization Relationship """ - decorators = (api.has_permission('is_coorganizer', fetch="event_id", - fetch_as="event_id", model=StripeAuthorization),) + decorators = (jwt_required,) schema = StripeAuthorizationSchema data_layer = {'session': db.session, 'model': StripeAuthorization} + + +def get_id(view_kwargs): + """ + method to get the resource id for fetching details + :param view_kwargs: + :return: + """ + + if view_kwargs.get('event_identifier') is not None: + event = safe_query(db, Event, 'identifier', view_kwargs['event_identifier'], 'event_identifier') + if event.id is not None: + view_kwargs['event_id'] = event.id + + if view_kwargs.get('event_id') is not None: + stripe_authorization = safe_query(db, StripeAuthorization, 'event_id', view_kwargs['event_id'], 'event_id') + view_kwargs['id'] = stripe_authorization.id + return view_kwargs diff --git a/app/models/event.py b/app/models/event.py index c08b742502..268e5338bd 100644 --- a/app/models/event.py +++ b/app/models/event.py @@ -98,6 +98,7 @@ class Event(SoftDeletionModel): payment_currency = db.Column(db.String) paypal_email = db.Column(db.String) is_tax_enabled = db.Column(db.Boolean, default=False) + is_billing_info_mandatory = db.Column(db.Boolean, default=False) can_pay_by_paypal = db.Column(db.Boolean, default=False) can_pay_by_stripe = db.Column(db.Boolean, default=False) can_pay_by_cheque = db.Column(db.Boolean, default=False) @@ -235,6 +236,7 @@ def __init__(self, discount_code_id=None, onsite_details=None, is_tax_enabled=None, + is_billing_info_mandatory=False, is_sponsors_enabled=None, stripe_authorization=None, tax=None, @@ -301,6 +303,7 @@ def __init__(self, self.discount_code_id = discount_code_id self.created_at = datetime.now(pytz.utc) self.is_tax_enabled = is_tax_enabled + self.is_billing_info_mandatory = is_billing_info_mandatory self.is_sponsors_enabled = is_sponsors_enabled self.stripe_authorization = stripe_authorization self.tax = tax diff --git a/docs/api/api_blueprint.apib b/docs/api/api_blueprint.apib index 7c60931e73..4076684909 100644 --- a/docs/api/api_blueprint.apib +++ b/docs/api/api_blueprint.apib @@ -17483,7 +17483,7 @@ Create a new Discount Code for event. (Only Admin) + sort (optional, string, `code`) - Sort the resources according to the given attribute in ascending order. Append '-' to sort in descending order. + filter (optional, string, `[]`) - Filter according to the flask-rest-jsonapi filtering system. Please refer: http://flask-rest-jsonapi.readthedocs.io/en/latest/filtering.html for more. -### Create Ticket Discount Code [POST] +### Create Ticket Discount Code [POST /v1/discount-codes{?page%5bsize%5d,page%5bnumber%5d,sort,filter}] Create a new Discount Code for event. (Only by Co-organizers) + Request (application/vnd.api+json) diff --git a/migrations/versions/2504915ffd08_.py b/migrations/versions/2504915ffd08_.py new file mode 100644 index 0000000000..47af9983e2 --- /dev/null +++ b/migrations/versions/2504915ffd08_.py @@ -0,0 +1,31 @@ +"""empty message + +Revision ID: 2504915ffd08 +Revises: 96bca587b3ca +Create Date: 2019-07-30 08:48:30.947153 + +""" + +from alembic import op +import sqlalchemy as sa +import sqlalchemy_utils + + +# revision identifiers, used by Alembic. +revision = '2504915ffd08' +down_revision = '96bca587b3ca' + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('events', sa.Column('is_billing_info_mandatory', sa.Boolean(), server_default='False' + , nullable=False)) + op.add_column('events_version', sa.Column('is_billing_info_mandatory', sa.Boolean(), server_default='False', + autoincrement=False, nullable=False)) + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('events_version', 'is_billing_info_mandatory') + op.drop_column('events', 'is_billing_info_mandatory') + # ### end Alembic commands ### diff --git a/requirements/common.txt b/requirements/common.txt index 6820c0c2a0..5fe386cf7f 100644 --- a/requirements/common.txt +++ b/requirements/common.txt @@ -17,7 +17,7 @@ SQLAlchemy-Utils~=0.34.1 itsdangerous~=0.24 humanize~=0.5.1 celery~=4.3 -redis~=3.2 +redis~=3.3 apscheduler~=3.6.1 pillow~=6.1.0 amqp~=2.5 @@ -25,10 +25,10 @@ gunicorn~=19.9 boto~=2.49 geoip2~=2.9.0 SQLAlchemy-Continuum~=1.3.9 -arrow~=0.14.2 +arrow~=0.14.3 unicode-slugify~=0.1 bleach~=3.1 -stripe~=2.32.1 +stripe~=2.33.0 xhtml2pdf~=0.2 flask-caching~=1.4 forex-python~=1.5