fossasia/open-event-orga-server

View on GitHub
app/api/attendees.py

Summary

Maintainability
D
2 days
Test Coverage
import datetime

from flask_jwt_extended import current_user
from flask_rest_jsonapi import ResourceDetail, ResourceList, ResourceRelationship
from sqlalchemy import and_, or_

from app.api.bootstrap import api
from app.api.helpers.custom_forms import validate_custom_form_constraints_request
from app.api.helpers.db import safe_query, safe_query_by_id, safe_query_kwargs
from app.api.helpers.errors import ForbiddenError, UnprocessableEntityError
from app.api.helpers.permission_manager import has_access
from app.api.helpers.permissions import jwt_required
from app.api.helpers.query import event_query
from app.api.schema.attendees import AttendeeSchema
from app.models import db
from app.models.order import Order
from app.models.ticket import Ticket
from app.models.ticket_holder import TicketHolder
from app.models.user import User
from app.settings import get_settings


def get_sold_and_reserved_tickets_count(ticket_id):
    order_expiry_time = get_settings()['order_expiry_time']
    return (
        db.session.query(TicketHolder.id)
        .join(Order)
        .filter(
            TicketHolder.ticket_id == ticket_id,
            TicketHolder.order_id == Order.id,
            TicketHolder.deleted_at.is_(None),
        )
        .filter(
            or_(
                Order.status == 'placed',
                Order.status == 'completed',
                and_(
                    Order.status == 'initializing',
                    Order.created_at + datetime.timedelta(minutes=order_expiry_time)
                    > datetime.datetime.utcnow(),
                ),
                and_(
                    Order.status == 'pending',
                    Order.created_at + datetime.timedelta(minutes=30 + order_expiry_time)
                    > (datetime.datetime.utcnow()),
                ),
            ),
        )
        .count()
    )


class AttendeeList(ResourceList):
    """
    List Attendees
    """

    def query(self, view_kwargs):
        """
        query method for Attendees List
        :param view_kwargs:
        :return:
        """
        query_ = self.session.query(TicketHolder)

        if view_kwargs.get('order_identifier'):
            order = safe_query_kwargs(
                Order,
                view_kwargs,
                'order_identifier',
                'identifier',
            )

            is_coorganizer = has_access(
                'is_coorganizer',
                event_id=order.event_id,
            )
            if not (
                is_coorganizer
                or current_user.id == order.user_id
                or order.is_attendee(current_user)
            ):
                raise ForbiddenError({'source': ''}, 'Access Forbidden')
            query_ = query_.join(Order).filter(Order.id == order.id)
            if not is_coorganizer and current_user.id != order.user_id:
                query_ = query_.filter(TicketHolder.user == current_user)

        if view_kwargs.get('ticket_id'):
            ticket = safe_query_kwargs(Ticket, view_kwargs, 'ticket_id')
            # if not has_access('is_registrar', event_id=ticket.event_id):
            #     raise ForbiddenError({'source': ''}, 'Access Forbidden')
            query_ = query_.join(Ticket).filter(Ticket.id == ticket.id)

        if view_kwargs.get('user_id'):
            user = safe_query_kwargs(User, view_kwargs, 'user_id')
            if not has_access('is_user_itself', user_id=user.id):
                raise ForbiddenError({'source': ''}, 'Access Forbidden')
            query_ = query_.join(User, User.email == TicketHolder.email).filter(
                User.id == user.id
            )

        query_ = event_query(query_, view_kwargs, restrict=True)
        return query_

    view_kwargs = True
    methods = [
        'GET',
    ]
    decorators = (jwt_required,)
    schema = AttendeeSchema
    data_layer = {
        'session': db.session,
        'model': TicketHolder,
        'methods': {'query': query},
    }


class AttendeeDetail(ResourceDetail):
    """
    Attendee detail by id
    """

    def before_get_object(self, view_kwargs):
        """
        before get object method for attendee detail
        :param view_kwargs:
        :return:
        """
        attendee = safe_query(TicketHolder, 'id', view_kwargs['id'], 'attendee_id')
        if not has_access(
            'is_registrar_or_user_itself',
            user_id=current_user.id,
            event_id=attendee.event_id,
        ):
            raise ForbiddenError(
                {'source': 'User'}, 'You are not authorized to access this.'
            )

    def before_delete_object(self, obj, kwargs):
        """
        before delete object method for attendee detail
        :param obj:
        :param kwargs:
        :return:
        """
        if not has_access('is_registrar', event_id=obj.event_id):
            raise ForbiddenError(
                {'source': 'User'}, 'You are not authorized to access this.'
            )

    def before_update_object(self, obj, data, kwargs):
        """
        before update object method for attendee detail
        :param obj:
        :param data:
        :param kwargs:
        :return:
        """
        order = safe_query_by_id(Order, obj.order_id)

        if not (current_user.is_staff or current_user.id == order.user_id):
            raise ForbiddenError(
                'Only admin or that user itself can update attendee info',
            )

        if order.status != 'initializing' and ('checkin_times' not in data):
            raise UnprocessableEntityError(
                {'pointer': '/data/id'},
                "Attendee can't be updated because the corresponding order is not in initializing state",
            )

        if 'device_name_checkin' in data:
            if 'checkin_times' not in data or data['checkin_times'] is None:
                raise UnprocessableEntityError(
                    {'pointer': '/data/attributes/device_name_checkin'},
                    "Check in Times missing",
                )

        if 'is_checked_in' in data and data['is_checked_in']:
            if 'checkin_times' not in data or data['checkin_times'] is None:
                raise UnprocessableEntityError(
                    {'pointer': '/data/attributes/checkin_times'},
                    "Check in time missing while trying to check in attendee",
                )
            if obj.checkin_times and data['checkin_times'] not in obj.checkin_times.split(
                ","
            ):
                data['checkin_times'] = '{},{}'.format(
                    obj.checkin_times, data['checkin_times']
                )

            if 'device_name_checkin' in data and data['device_name_checkin'] is not None:
                if obj.device_name_checkin is not None:
                    data['device_name_checkin'] = '{},{}'.format(
                        obj.device_name_checkin, data['device_name_checkin']
                    )

                if len(data['checkin_times'].split(",")) != len(
                    data['device_name_checkin'].split(",")
                ):
                    raise UnprocessableEntityError(
                        {'pointer': '/data/attributes/device_name_checkin'},
                        "Check in Time missing for the corresponding device name",
                    )
            else:
                if obj.device_name_checkin is not None:
                    data['device_name_checkin'] = '{},{}'.format(
                        obj.device_name_checkin, '-'
                    )
                else:
                    data['device_name_checkin'] = '-'

        if 'is_checked_out' in data and data['is_checked_out']:
            attendee = safe_query(TicketHolder, 'id', kwargs['id'], 'attendee_id')
            if not attendee.is_checked_out:
                checkout_times = (
                    obj.checkout_times.split(',') if obj.checkout_times else []
                )
                checkout_times.append(str(datetime.datetime.utcnow()))
                data['checkout_times'] = ','.join(checkout_times)

        if 'attendee_notes' in data:
            if obj.attendee_notes and data[
                'attendee_notes'
            ] not in obj.attendee_notes.split(","):
                data['attendee_notes'] = '{},{}'.format(
                    obj.attendee_notes, data['attendee_notes']
                )

        data['complex_field_values'] = (
            validate_custom_form_constraints_request(
                'attendee', self.resource.schema, obj, data
            )
            if obj.event.is_ticket_form_enabled
            else None
        )

    decorators = (jwt_required,)
    schema = AttendeeSchema
    data_layer = {
        'session': db.session,
        'model': TicketHolder,
        'methods': {
            'before_get_object': before_get_object,
            'before_update_object': before_update_object,
            'before_delete_object': before_delete_object,
        },
    }


class AttendeeRelationshipRequired(ResourceRelationship):
    """
    Attendee Relationship (Required)
    """

    decorators = (jwt_required,)
    methods = ['GET', 'PATCH']
    schema = AttendeeSchema
    data_layer = {'session': db.session, 'model': TicketHolder}


class AttendeeRelationshipOptional(ResourceRelationship):
    """
    Attendee Relationship(Optional)
    """

    decorators = (
        api.has_permission(
            'is_user_itself', fetch="user_id", fetch_as="id", model=TicketHolder
        ),
    )
    schema = AttendeeSchema
    data_layer = {'session': db.session, 'model': TicketHolder}