fossasia/open-event-orga-server

View on GitHub
app/api/events.py

Summary

Maintainability
F
3 days
Test Coverage
from datetime import datetime

import pytz
from flask import g, request
from flask.blueprints import Blueprint
from flask.json import jsonify
from flask_jwt_extended import current_user, get_jwt_identity, verify_jwt_in_request
from flask_jwt_extended.view_decorators import jwt_optional
from flask_rest_jsonapi import ResourceDetail, ResourceList, ResourceRelationship
from flask_rest_jsonapi.exceptions import ObjectNotFound
from marshmallow_jsonapi import fields
from marshmallow_jsonapi.flask import Schema
from sqlalchemy import and_, or_

from app.api.bootstrap import api
from app.api.chat.rocket_chat import (
    RocketChatException,
    get_rocket_chat_token,
    get_rocket_chat_token_virtual_room,
)
from app.api.data_layers.EventCopyLayer import EventCopyLayer
from app.api.helpers.db import safe_query, safe_query_kwargs, save_to_db
from app.api.helpers.errors import (
    ConflictError,
    ForbiddenError,
    NotFoundError,
    UnprocessableEntityError,
)
from app.api.helpers.events import create_custom_forms_for_attendees
from app.api.helpers.export_helpers import create_export_job
from app.api.helpers.permission_manager import has_access, is_logged_in
from app.api.helpers.permissions import jwt_required, to_event_id
from app.api.helpers.utilities import dasherize
from app.api.schema.events import EventSchema, EventSchemaPublic

# models
from app.models import db
from app.models.access_code import AccessCode
from app.models.custom_form import CustomForms
from app.models.discount_code import DiscountCode
from app.models.email_notification import EmailNotification
from app.models.event import Event
from app.models.event_copyright import EventCopyright
from app.models.event_invoice import EventInvoice
from app.models.exhibitor import Exhibitor
from app.models.faq import Faq
from app.models.faq_type import FaqType
from app.models.feedback import Feedback
from app.models.group import Group
from app.models.microlocation import Microlocation
from app.models.order import Order
from app.models.role import Role
from app.models.role_invite import RoleInvite
from app.models.session import Session
from app.models.session_type import SessionType
from app.models.social_link import SocialLink
from app.models.speaker import Speaker
from app.models.speaker_invite import SpeakerInvite
from app.models.speakers_call import SpeakersCall
from app.models.sponsor import Sponsor
from app.models.stripe_authorization import StripeAuthorization
from app.models.tax import Tax
from app.models.ticket import Ticket, TicketTag
from app.models.ticket_holder import TicketHolder
from app.models.track import Track
from app.models.user import (
    MARKETER,
    MODERATOR,
    REGISTRAR,
    SALES_ADMIN,
    TRACK_ORGANIZER,
    User,
)
from app.models.user_favourite_event import UserFavouriteEvent
from app.models.users_events_role import UsersEventsRoles
from app.models.video_stream import VideoStream

events_blueprint = Blueprint('events_blueprint', __name__, url_prefix='/v1/events')


@events_blueprint.route('/<string:event_identifier>/has-streams')
@jwt_optional
@to_event_id
def has_streams(event_id):
    event = Event.query.get_or_404(event_id)

    exists = False
    if event.video_stream:
        exists = True
    else:
        exists = db.session.query(
            VideoStream.query.join(VideoStream.rooms)
            .filter(Microlocation.event_id == event.id)
            .exists()
        ).scalar()
    can_access = VideoStream(event_id=event.id).user_can_access
    return jsonify(dict(exists=exists, can_access=can_access))


@events_blueprint.route(
    '/<string:event_identifier>/chat-token',
)
@jwt_required
@to_event_id
def get_chat_token(event_id: int):
    event = Event.query.get_or_404(event_id)

    if not VideoStream(event_id=event.id).user_can_access:
        raise NotFoundError({'source': ''}, 'Video Stream Not Found')

    if not event.is_chat_enabled:
        raise NotFoundError({'source': ''}, 'Chat Not Enabled')

    try:
        data = get_rocket_chat_token(current_user, event)
        return jsonify({'success': True, 'token': data['token']})
    except RocketChatException as rce:
        if rce.code == RocketChatException.CODES.DISABLED:
            return jsonify({'success': False, 'code': rce.code})
        else:
            return jsonify(
                {
                    'success': False,
                    'code': rce.code,
                    'response': rce.response is not None and rce.response.json(),
                }
            )


@events_blueprint.route(
    '/<string:event_identifier>/room/<string:microlocation_id>/chat-token',
)
@jwt_required
@to_event_id
def get_room_chat_token(event_id: int, microlocation_id: int):
    """
    Get room chat token for specific room
    @param event_id: event identifier
    @param microlocation_id: microlocation id
    @return: room chat token
    """
    event = Event.query.get_or_404(event_id)
    microlocation = Microlocation.query.get_or_404(microlocation_id)

    if not VideoStream(event_id=event.id).user_can_access:
        raise NotFoundError({'source': ''}, 'Video Stream Not Found')

    if not event.is_chat_enabled:
        raise NotFoundError({'source': ''}, 'Chat Not Enabled')

    if not microlocation.is_chat_enabled and not microlocation.is_global_event_room:
        raise NotFoundError({'source': ''}, 'Chat Not Enabled For This Room')

    try:
        data = get_rocket_chat_token(current_user, event, microlocation)
        return jsonify({'success': True, 'token': data['token']})
    except RocketChatException as rce:
        if rce.code == RocketChatException.CODES.DISABLED:
            return jsonify({'success': False, 'code': rce.code})
        return jsonify(
            {
                'success': False,
                'code': rce.code,
                'response': rce.response is not None and rce.response.json(),
            }
        )


@events_blueprint.route(
    '/<string:event_identifier>/virtual-room/<int:video_stream_id>/chat-token',
)
@jwt_required
@to_event_id
def get_virtual_room_chat_token(event_id: int, video_stream_id: int):
    """
    Get room chat token for specific room
    @param event_id: event identifier
    @param video_stream_id: microlocation id
    @return: room chat token
    """
    event = Event.query.get_or_404(event_id)
    videoStream = VideoStream.query.get_or_404(video_stream_id)

    if not VideoStream(event_id=event.id).user_can_access:
        raise NotFoundError({'source': ''}, 'Video Stream Not Found')

    if not event.is_chat_enabled:
        raise NotFoundError({'source': ''}, 'Chat Not Enabled')

    if not videoStream.is_chat_enabled and not videoStream.is_global_event_room:
        raise NotFoundError({'source': ''}, 'Chat Not Enabled For This Room')

    try:
        data = get_rocket_chat_token_virtual_room(current_user, event, videoStream)
        return jsonify({'success': True, 'token': data['token']})
    except RocketChatException as rce:
        if rce.code == RocketChatException.CODES.DISABLED:
            return jsonify({'success': False, 'code': rce.code})
        return jsonify(
            {
                'success': False,
                'code': rce.code,
                'response': rce.response is not None and rce.response.json(),
            }
        )


def validate_event(user, data):
    if not user.can_create_event():
        raise ForbiddenError({'source': ''}, "Please verify your Email")

    if data.get('state', None) == Event.State.PUBLISHED and not user.can_publish_event():
        raise ForbiddenError({'source': ''}, "Only verified accounts can publish events")

    if not data.get('name', None) and data.get('state', None) == Event.State.PUBLISHED:
        raise ConflictError(
            {'pointer': '/data/attributes/name'},
            "Event Name is required to publish the event",
        )


def validate_date(event, data):
    if event:
        if 'starts_at' not in data:
            data['starts_at'] = event.starts_at

        if 'ends_at' not in data:
            data['ends_at'] = event.ends_at

    if not data.get('starts_at') or not data.get('ends_at'):
        raise UnprocessableEntityError(
            {'pointer': '/data/attributes/date'},
            "enter required fields starts-at/ends-at",
        )

    if data['starts_at'] >= data['ends_at']:
        raise UnprocessableEntityError(
            {'pointer': '/data/attributes/ends-at'}, "ends-at should be after starts-at"
        )

    if (data['ends_at'] - data['starts_at']).days > 20:
        raise UnprocessableEntityError(
            {'pointer': '/data/attributes/ends-at'},
            "Event duration can not be more than 20 days",
        )


def get_event_query():
    query_ = Event.query
    if get_jwt_identity() is None or not current_user.is_staff:
        # If user is not admin, we only show published events
        query_ = query_.filter_by(state=Event.State.PUBLISHED)
    if is_logged_in():
        # For a specific user accessing the API, we show all
        # events managed by them, even if they're not published
        verify_jwt_in_request()
        query2 = Event.query
        query2 = (
            query2.join(Event.roles)
            .filter_by(user_id=current_user.id)
            .join(UsersEventsRoles.role)
            .filter(
                or_(
                    Role.name == Role.COORGANIZER,
                    Role.name == Role.ORGANIZER,
                    Role.name == Role.OWNER,
                )
            )
        )
        query_ = query_.union(query2)

    return query_


class EventList(ResourceList):
    def before_get(self, args, kwargs):
        """
        method for assigning schema based on admin access
        :param args:
        :param kwargs:
        :return:
        """
        if is_logged_in() and (has_access('is_admin') or kwargs.get('user_id')):
            self.schema = EventSchema
        else:
            self.schema = EventSchemaPublic

    def query(self, view_kwargs):
        """
        query method for EventList class
        :param view_kwargs:
        :return:
        """
        query_ = get_event_query()

        if view_kwargs.get('user_id') and 'GET' in request.method:
            if not has_access('is_user_itself', user_id=int(view_kwargs['user_id'])):
                raise ForbiddenError({'source': ''}, 'Access Forbidden')
            user = safe_query_kwargs(User, view_kwargs, 'user_id')
            query_ = query_.join(Event.roles).filter_by(user_id=user.id)

        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 ForbiddenError({'source': ''}, 'Access Forbidden')
            user = safe_query_kwargs(User, view_kwargs, 'user_owner_id')
            query_ = (
                query_.join(Event.roles)
                .filter_by(user_id=user.id)
                .join(UsersEventsRoles.role)
                .filter(Role.name == Role.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 ForbiddenError({'source': ''}, 'Access Forbidden')
            user = safe_query_kwargs(User, view_kwargs, 'user_organizer_id')

            query_ = (
                query_.join(Event.roles)
                .filter_by(user_id=user.id)
                .join(UsersEventsRoles.role)
                .filter(Role.name == Role.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 ForbiddenError({'source': ''}, 'Access Forbidden')
            user = safe_query_kwargs(User, view_kwargs, 'user_coorganizer_id')
            query_ = (
                query_.join(Event.roles)
                .filter_by(user_id=user.id)
                .join(UsersEventsRoles.role)
                .filter(Role.name == Role.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 ForbiddenError({'source': ''}, 'Access Forbidden')
            user = safe_query(
                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 ForbiddenError({'source': ''}, 'Access Forbidden')
            user = safe_query_kwargs(User, view_kwargs, '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 ForbiddenError({'source': ''}, 'Access Forbidden')
            user = safe_query_kwargs(User, view_kwargs, '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 ForbiddenError({'source': ''}, 'Access Forbidden')
            user = safe_query_kwargs(User, view_kwargs, '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 ForbiddenError({'source': ''}, 'Access Forbidden')
            user = safe_query_kwargs(User, view_kwargs, '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']
            )

        if view_kwargs.get('event_topic_id') and 'GET' in request.method:
            query_ = self.session.query(Event).filter(
                getattr(Event, 'event_topic_id') == view_kwargs['event_topic_id']
            )

        if view_kwargs.get('event_sub_topic_id') and 'GET' in request.method:
            query_ = self.session.query(Event).filter(
                getattr(Event, 'event_sub_topic_id') == view_kwargs['event_sub_topic_id']
            )

        if view_kwargs.get('discount_code_id') and 'GET' in request.method:
            event_id = get_id(view_kwargs)['id']
            if not has_access('is_coorganizer', event_id=event_id):
                raise ForbiddenError({'source': ''}, 'Coorganizer access is required')
            query_ = self.session.query(Event).filter(
                getattr(Event, 'discount_code_id') == view_kwargs['discount_code_id']
            )

        if view_kwargs.get('group_id') and 'GET' in request.method:
            group = safe_query(Group, 'id', view_kwargs.get('group_id'), 'group_id')
            query_ = self.session.query(Event).filter(
                getattr(Event, 'group_id') == view_kwargs['group_id']
            )

        return query_

    def before_post(self, args, kwargs, data=None):
        """
        before post method to verify if the event location is provided before publishing the event
        and checks that the user is verified
        :param args:
        :param kwargs:
        :param data:
        :return:
        """
        user = User.query.filter_by(id=kwargs['user_id']).first()
        validate_event(user, data)
        if data['state'] != Event.State.DRAFT:
            validate_date(None, data)

    def after_create_object(self, event, data, view_kwargs):
        """
        after create method to save roles for users and add the user as an accepted role(owner and organizer)
        :param event:
        :param data:
        :param view_kwargs:
        :return:
        """
        user = User.query.filter_by(id=view_kwargs['user_id']).first()
        role = Role.query.filter_by(name=Role.OWNER).first()
        uer = UsersEventsRoles(user=user, event=event, role=role)
        save_to_db(uer, 'Event Saved')
        role_invite = RoleInvite(
            email=user.email,
            role_name=role.title_name,
            event=event,
            role=role,
            status='accepted',
        )
        save_to_db(role_invite, 'Owner Role Invite Added')

        # create custom forms for compulsory fields of attendee form.
        create_custom_forms_for_attendees(event)

        if event.state == Event.State.PUBLISHED and event.schedule_published_on:
            start_export_tasks(event)

        if data.get('original_image_url'):
            start_image_resizing_tasks(event, data['original_image_url'])

    # This permission decorator ensures, you are logged in to create an event
    # and have filter ?withRole to get events associated with logged in user
    decorators = (
        api.has_permission(
            'create_event',
        ),
    )
    schema = EventSchema
    data_layer = {
        'session': db.session,
        'model': Event,
        'methods': {'after_create_object': after_create_object, 'query': query},
    }


def set_event_id(model, identifier, kwargs, attr='event_id', column_name='id'):
    if kwargs.get('id'):  # ID already set
        return
    if kwargs.get(identifier) is None:
        return
    item = safe_query_kwargs(model, kwargs, identifier, column_name=column_name)
    kwargs['id'] = getattr(item, attr, None)


def get_id(view_kwargs):
    """
    method to get the resource id for fetching details
    :param view_kwargs:
    :return:
    """
    set_event_id(Event, 'identifier', view_kwargs, attr='id', column_name='identifier')

    lookup_list = [
        (Sponsor, 'sponsor_id'),
        (UserFavouriteEvent, 'user_favourite_event_id'),
        (EventCopyright, 'copyright_id'),
        (Track, 'track_id'),
        (SessionType, 'session_type_id'),
        (FaqType, 'faq_type_id'),
        (EventInvoice, 'event_invoice_id'),
        (DiscountCode, 'discount_code_id'),
        (Session, 'session_id'),
        (SocialLink, 'social_link_id'),
        (Tax, 'tax_id'),
        (StripeAuthorization, 'stripe_authorization_id'),
        (DiscountCode, 'discount_code_id'),
        (SpeakersCall, 'speakers_call_id'),
        (Ticket, 'ticket_id'),
        (TicketTag, 'ticket_tag_id'),
        (RoleInvite, 'role_invite_id'),
        (SpeakerInvite, 'speaker_invite_id'),
        (UsersEventsRoles, 'users_events_role_id'),
        (AccessCode, 'access_code_id'),
        (Speaker, 'speaker_id'),
        (EmailNotification, 'email_notification_id'),
        (Microlocation, 'microlocation_id'),
        (TicketHolder, 'attendee_id'),
        (CustomForms, 'custom_form_id'),
        (Faq, 'faq_id'),
        (Feedback, 'feedback_id'),
        (VideoStream, 'video_stream_id'),
        (Exhibitor, 'exhibitor_id'),
    ]
    for model, identifier in lookup_list:
        set_event_id(model, identifier, view_kwargs)

    set_event_id(
        EventInvoice, 'event_invoice_identifier', view_kwargs, column_name='identifier'
    )
    set_event_id(Order, 'order_identifier', view_kwargs, column_name='identifier')

    return view_kwargs


class EventDetail(ResourceDetail):
    """
    EventDetail class for EventSchema
    """

    def before_get(self, args, kwargs):
        """
        method for assigning schema based on access
        :param args:
        :param kwargs:
        :return:
        """
        kwargs = get_id(kwargs)
        if is_logged_in() and has_access('is_coorganizer', event_id=kwargs['id']):
            self.schema = EventSchema
        else:
            self.schema = EventSchemaPublic

    def before_get_object(self, view_kwargs):
        """
        before get method to get the resource id for fetching details
        :param view_kwargs:
        :return:
        """
        get_id(view_kwargs)

    def after_get_object(self, event, view_kwargs):
        if event and event.state == Event.State.DRAFT:
            if not is_logged_in() or not has_access('is_coorganizer', event_id=event.id):
                raise ObjectNotFound({'parameter': '{id}'}, "Event: not found")

    def before_patch(self, args, kwargs, data=None):
        """
        before patch method to verify if the event location is provided before publishing the event and checks that
        the user is verified
        :param args:
        :param kwargs:
        :param data:
        :return:
        """
        user = User.query.filter_by(id=current_user.id).one()
        validate_event(user, data)

    def before_update_object(self, event, data, view_kwargs):
        """
        method to save image urls before updating event object
        :param event:
        :param data:
        :param view_kwargs:
        :return:
        """
        g.event_name = event.name

        is_date_updated = (
            data.get('starts_at') != event.starts_at
            or data.get('ends_at') != event.ends_at
        )
        is_draft_published = (
            event.state == Event.State.DRAFT
            and data.get('state') == Event.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 data.get('is_document_enabled'):
            d = data.get('document_links')
            if d:
                for document in d:
                    if not document.get('name') or not document.get('link'):
                        raise UnprocessableEntityError(
                            {'pointer': '/'},
                            "Enter required fields link and name",
                        )

        if has_access('is_admin') and data.get('deleted_at') != event.deleted_at:
            if len(event.orders) != 0 and not has_access('is_super_admin'):
                raise ForbiddenError(
                    {'source': ''}, "Event associated with orders cannot be deleted"
                )
            event.deleted_at = data.get('deleted_at')

        if (
            data.get('original_image_url')
            and data['original_image_url'] != event.original_image_url
        ):
            start_image_resizing_tasks(event, data['original_image_url'])
        if data.get('group') != event.group_id:
            if event.is_announced:
                event.is_announced = False
                save_to_db(event)

    def after_update_object(self, event, data, view_kwargs):
        if event.name != g.event_name:
            from .helpers.tasks import rename_chat_room

            rename_chat_room.delay(event.id)

        if event.state == Event.State.PUBLISHED and event.schedule_published_on:
            start_export_tasks(event)
        else:
            clear_export_urls(event)

    decorators = (
        api.has_permission(
            'is_coorganizer',
            methods="PATCH,DELETE",
            fetch="id",
            fetch_as="event_id",
            model=Event,
        ),
    )
    schema = EventSchema
    data_layer = {
        'session': db.session,
        'model': Event,
        'methods': {
            'before_update_object': before_update_object,
            'before_get_object': before_get_object,
            'after_get_object': after_get_object,
            'after_update_object': after_update_object,
            'before_patch': before_patch,
        },
    }


class EventRelationship(ResourceRelationship):
    """
    Event Relationship
    """

    def before_get_object(self, view_kwargs):
        if view_kwargs.get('identifier'):
            event = safe_query_kwargs(Event, view_kwargs, 'identifier', 'identifier')
            view_kwargs['id'] = event.id

    decorators = (
        api.has_permission(
            'is_coorganizer', fetch="id", fetch_as="event_id", model=Event
        ),
    )
    schema = EventSchema
    data_layer = {
        'session': db.session,
        'model': Event,
        'methods': {'before_get_object': before_get_object},
    }


class EventCopySchema(Schema):
    """
    API Schema for EventCopy
    """

    class Meta:
        """
        Meta class for EventCopySchema
        """

        type_ = 'event-copy'
        inflect = dasherize
        self_view = 'v1.event_copy'
        self_view_kwargs = {'identifier': '<id>'}

    id = fields.Str(dump_only=True)
    identifier = fields.Str(dump_only=True)


class EventCopyResource(ResourceList):
    """
    ResourceList class for EventCopy
    """

    schema = EventCopySchema
    methods = [
        'POST',
    ]
    data_layer = {'class': EventCopyLayer, 'session': db.Session}


def start_export_tasks(event):
    event_id = str(event.id)
    # XCAL
    from .helpers.tasks import export_xcal_task

    task_xcal = export_xcal_task.delay(event_id, temp=False)
    create_export_job(task_xcal.id, event_id)

    # ICAL
    from .helpers.tasks import export_ical_task

    task_ical = export_ical_task.delay(event_id, temp=False)
    create_export_job(task_ical.id, event_id)

    # PENTABARF XML
    from .helpers.tasks import export_pentabarf_task

    task_pentabarf = export_pentabarf_task.delay(event_id, temp=False)
    create_export_job(task_pentabarf.id, event_id)


def start_image_resizing_tasks(event, original_image_url):
    event_id = str(event.id)
    from .helpers.tasks import resize_event_images_task

    resize_event_images_task.delay(event_id, original_image_url)


def clear_export_urls(event):
    event.ical_url = None
    event.xcal_url = None
    event.pentabarf_url = None
    save_to_db(event)


class UpcomingEventList(EventList):
    """
    List Upcoming Events
    """

    def before_get(self, args, kwargs):
        """
        method for assigning schema based on admin access
        :param args:
        :param kwargs:
        :return:
        """
        super().before_get(args, kwargs)
        self.schema.self_view_many = 'v1.upcoming_event_list'

    def query(self, view_kwargs):
        """
        query method for upcoming events list
        :param view_kwargs:
        :return:
        """
        current_time = datetime.now(pytz.utc)
        query_ = (
            self.session.query(Event)
            .filter(
                Event.starts_at > current_time,
                Event.ends_at > current_time,
                Event.state == Event.State.PUBLISHED,
                Event.privacy == Event.Privacy.PUBLIC,
                or_(
                    Event.is_promoted,
                    and_(
                        Event.is_demoted == False,
                        Event.original_image_url != None,
                        Event.logo_url != None,
                        Event.event_type_id != None,
                        Event.event_topic_id != None,
                        Event.event_sub_topic_id != None,
                        Event.tickets.any(
                            and_(
                                Ticket.deleted_at == None,
                                Ticket.is_hidden == False,
                                Ticket.sales_ends_at > current_time,
                                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',
                                    )
                                )
                                .count()
                                < Ticket.quantity,
                            )
                        ),
                        Event.social_link.any(SocialLink.name == "twitter"),
                    ),
                ),
            )
            .order_by(Event.starts_at)
        )
        return query_

    data_layer = {
        'session': db.session,
        'model': Event,
        'methods': {'query': query},
    }