Beispiel #1
0
def process_exit_all(sender, **kwargs):
    qs = CheckinList.objects.filter(exit_all_at__lte=now(),
                                    exit_all_at__isnull=False).select_related(
                                        'event', 'event__organizer')
    for cl in qs:
        for p in cl.positions_inside:
            with scope(organizer=cl.event.organizer):
                ci = Checkin.objects.create(position=p,
                                            list=cl,
                                            auto_checked_in=True,
                                            type=Checkin.TYPE_EXIT,
                                            datetime=cl.exit_all_at)
                checkin_created.send(cl.event, checkin=ci)
        d = cl.exit_all_at.astimezone(cl.event.timezone)
        if cl.event.settings.get(
                f'autocheckin_dst_hack_{cl.pk}'
        ):  # move time back if yesterday was DST switch
            d -= timedelta(hours=1)
            cl.event.settings.delete(f'autocheckin_dst_hack_{cl.pk}')
        try:
            cl.exit_all_at = make_aware(
                datetime.combine(d.date() + timedelta(days=1), d.time()),
                cl.event.timezone)
        except pytz.exceptions.NonExistentTimeError:
            cl.event.settings.set(f'autocheckin_dst_hack_{cl.pk}', True)
            d += timedelta(hours=1)
            cl.exit_all_at = make_aware(
                datetime.combine(d.date() + timedelta(days=1), d.time()),
                cl.event.timezone)
            # AmbiguousTimeError shouldn't be possible since d.time() includes fold=0
        cl.save(update_fields=['exit_all_at'])
Beispiel #2
0
    def post(self, request, *args, **kwargs):
        if "can_change_orders" not in request.eventpermset:
            messages.error(request, _('You do not have permission to perform this action.'))
            return redirect(reverse('control:event.orders.checkins', kwargs={
                'event': self.request.event.slug,
                'organizer': self.request.event.organizer.slug
            }) + '?' + request.GET.urlencode())

        positions = self.get_queryset(filter=False).filter(
            pk__in=request.POST.getlist('checkin')
        )

        if request.POST.get('revert') == 'true':
            for op in positions:
                if op.order.status == Order.STATUS_PAID or (self.list.include_pending and op.order.status == Order.STATUS_PENDING):
                    Checkin.objects.filter(position=op, list=self.list).delete()
                    op.order.log_action('pretix.event.checkin.reverted', data={
                        'position': op.id,
                        'positionid': op.positionid,
                        'list': self.list.pk,
                        'web': True
                    }, user=request.user)
                    op.order.touch()

            messages.success(request, _('The selected check-ins have been reverted.'))
        else:
            for op in positions:
                if op.order.status == Order.STATUS_PAID or (self.list.include_pending and op.order.status == Order.STATUS_PENDING):
                    t = Checkin.TYPE_EXIT if request.POST.get('checkout') == 'true' else Checkin.TYPE_ENTRY
                    if self.list.allow_multiple_entries or t != Checkin.TYPE_ENTRY:
                        ci = Checkin.objects.create(position=op, list=self.list, datetime=now(), type=t)
                        created = True
                    else:
                        ci, created = Checkin.objects.get_or_create(position=op, list=self.list, defaults={
                            'datetime': now(),
                        })
                    op.order.log_action('pretix.event.checkin', data={
                        'position': op.id,
                        'positionid': op.positionid,
                        'first': created,
                        'forced': False,
                        'datetime': now(),
                        'type': t,
                        'list': self.list.pk,
                        'web': True
                    }, user=request.user)
                    checkin_created.send(op.order.event, checkin=ci)

            messages.success(request, _('The selected tickets have been marked as checked in.'))

        return redirect(reverse('control:event.orders.checkinlists.show', kwargs={
            'event': self.request.event.slug,
            'organizer': self.request.event.organizer.slug,
            'list': self.list.pk
        }) + '?' + request.GET.urlencode())
Beispiel #3
0
def order_placed(sender, **kwargs):
    order = kwargs['order']
    event = sender

    cls = list(event.checkin_lists.filter(auto_checkin_sales_channels__contains=order.sales_channel).prefetch_related(
        'limit_products'))
    if not cls:
        return
    for op in order.positions.all():
        for cl in cls:
            if cl.all_products or op.item_id in {i.pk for i in cl.limit_products.all()}:
                ci = Checkin.objects.create(position=op, list=cl, auto_checked_in=True)
                checkin_created.send(event, checkin=ci)
Beispiel #4
0
def process_exit_all(sender, **kwargs):
    qs = CheckinList.objects.filter(exit_all_at__lte=now(),
                                    exit_all_at__isnull=False).select_related(
                                        'event', 'event__organizer')
    for cl in qs:
        for p in cl.positions_inside:
            with scope(organizer=cl.event.organizer):
                ci = Checkin.objects.create(position=p,
                                            list=cl,
                                            auto_checked_in=True,
                                            type=Checkin.TYPE_EXIT,
                                            datetime=cl.exit_all_at)
                checkin_created.send(cl.event, checkin=ci)
        cl.exit_all_at = cl.exit_all_at + timedelta(days=1)
        cl.save(update_fields=['exit_all_at'])
Beispiel #5
0
def perform_checkin(op: OrderPosition,
                    clist: CheckinList,
                    given_answers: dict,
                    force=False,
                    ignore_unpaid=False,
                    nonce=None,
                    datetime=None,
                    questions_supported=True,
                    user=None,
                    auth=None,
                    canceled_supported=False,
                    type=Checkin.TYPE_ENTRY,
                    raw_barcode=None,
                    from_revoked_secret=False):
    """
    Create a checkin for this particular order position and check-in list. Fails with CheckInError if the check in is
    not valid at this time.

    :param op: The order position to check in
    :param clist: The order position to check in
    :param given_answers: A dictionary of questions mapped to validated, given answers
    :param force: When set to True, this will succeed even when the position is already checked in or when required
        questions are not filled out.
    :param ignore_unpaid: When set to True, this will succeed even when the order is unpaid.
    :param questions_supported: When set to False, questions are ignored
    :param nonce: A random nonce to prevent race conditions.
    :param datetime: The datetime of the checkin, defaults to now.
    """
    dt = datetime or now()

    if op.canceled or op.order.status not in (Order.STATUS_PAID,
                                              Order.STATUS_PENDING):
        raise CheckInError(_('This order position has been canceled.'),
                           'canceled' if canceled_supported else 'unpaid')

    # Do this outside of transaction so it is saved even if the checkin fails for some other reason
    checkin_questions = list(
        clist.event.questions.filter(ask_during_checkin=True,
                                     items__in=[op.item_id]))
    require_answers = []
    if type != Checkin.TYPE_EXIT and checkin_questions:
        answers = {a.question: a for a in op.answers.all()}
        for q in checkin_questions:
            if q not in given_answers and q not in answers:
                require_answers.append(q)

        _save_answers(op, answers, given_answers)

    with transaction.atomic():
        # Lock order positions
        op = OrderPosition.all.select_for_update().get(pk=op.pk)

        if not clist.all_products and op.item_id not in [
                i.pk for i in clist.limit_products.all()
        ]:
            raise CheckInError(
                _('This order position has an invalid product for this check-in list.'
                  ), 'product')
        elif clist.subevent_id and op.subevent_id != clist.subevent_id:
            raise CheckInError(
                _('This order position has an invalid date for this check-in list.'
                  ), 'product')
        elif op.order.status != Order.STATUS_PAID and not force and not (
                ignore_unpaid and clist.include_pending
                and op.order.status == Order.STATUS_PENDING):
            raise CheckInError(_('This order is not marked as paid.'),
                               'unpaid')

        if type == Checkin.TYPE_ENTRY and clist.rules and not force:
            rule_data = LazyRuleVars(op, clist, dt)
            logic = _get_logic_environment(op.subevent or clist.event)
            if not logic.apply(clist.rules, rule_data):
                reason = _logic_explain(clist.rules, op.subevent
                                        or clist.event, rule_data)
                raise CheckInError(
                    _('Entry not permitted: {explanation}.').format(
                        explanation=reason),
                    'rules',
                    reason=reason)

        if require_answers and not force and questions_supported:
            raise RequiredQuestionsError(
                _('You need to answer questions to complete this check-in.'),
                'incomplete', require_answers)

        device = None
        if isinstance(auth, Device):
            device = auth

        last_cis = list(
            op.checkins.order_by('-datetime').filter(list=clist).only(
                'type', 'nonce'))
        entry_allowed = (type == Checkin.TYPE_EXIT
                         or clist.allow_multiple_entries or not last_cis
                         or all(c.type == Checkin.TYPE_EXIT for c in last_cis)
                         or (clist.allow_entry_after_exit
                             and last_cis[0].type == Checkin.TYPE_EXIT))

        if nonce and (
            (last_cis and last_cis[0].nonce == nonce) or op.checkins.filter(
                type=type, list=clist, device=device, nonce=nonce).exists()):
            return

        if entry_allowed or force:
            ci = Checkin.objects.create(
                position=op,
                type=type,
                list=clist,
                datetime=dt,
                device=device,
                gate=device.gate if device else None,
                nonce=nonce,
                forced=force and (not entry_allowed or from_revoked_secret),
                raw_barcode=raw_barcode,
            )
            op.order.log_action(
                'pretix.event.checkin',
                data={
                    'position': op.id,
                    'positionid': op.positionid,
                    'first': True,
                    'forced': force or op.order.status != Order.STATUS_PAID,
                    'datetime': dt,
                    'type': type,
                    'answers':
                    {k.pk: str(v)
                     for k, v in given_answers.items()},
                    'list': clist.pk
                },
                user=user,
                auth=auth)
            checkin_created.send(op.order.event, checkin=ci)
        else:
            raise CheckInError(
                _('This ticket has already been redeemed.'),
                'already_redeemed',
            )
Beispiel #6
0
def perform_checkin(op: OrderPosition, clist: CheckinList, given_answers: dict, force=False,
                    ignore_unpaid=False, nonce=None, datetime=None, questions_supported=True,
                    user=None, auth=None, canceled_supported=False):
    """
    Create a checkin for this particular order position and check-in list. Fails with CheckInError if the check in is
    not valid at this time.

    :param op: The order position to check in
    :param clist: The order position to check in
    :param given_answers: A dictionary of questions mapped to validated, given answers
    :param force: When set to True, this will succeed even when the position is already checked in or when required
        questions are not filled out.
    :param ignore_unpaid: When set to True, this will succeed even when the order is unpaid.
    :param questions_supported: When set to False, questions are ignored
    :param nonce: A random nonce to prevent race conditions.
    :param datetime: The datetime of the checkin, defaults to now.
    """
    dt = datetime or now()

    # Fetch order position with related objects
    op = OrderPosition.all.select_related(
        'item', 'variation', 'order', 'addon_to'
    ).prefetch_related(
        'item__questions',
        Prefetch(
            'item__questions',
            queryset=Question.objects.filter(ask_during_checkin=True),
            to_attr='checkin_questions'
        ),
        'answers'
    ).get(pk=op.pk)

    if op.canceled or op.order.status not in (Order.STATUS_PAID, Order.STATUS_PENDING):
        raise CheckInError(
            _('This order position has been canceled.'),
            'canceled' if canceled_supported else 'unpaid'
        )

    answers = {a.question: a for a in op.answers.all()}
    require_answers = []
    for q in op.item.checkin_questions:
        if q not in given_answers and q not in answers:
            require_answers.append(q)

    _save_answers(op, answers, given_answers)

    if not clist.all_products and op.item_id not in [i.pk for i in clist.limit_products.all()]:
        raise CheckInError(
            _('This order position has an invalid product for this check-in list.'),
            'product'
        )
    elif op.order.status != Order.STATUS_PAID and not force and not (
        ignore_unpaid and clist.include_pending and op.order.status == Order.STATUS_PENDING
    ):
        raise CheckInError(
            _('This order is not marked as paid.'),
            'unpaid'
        )
    elif require_answers and not force and questions_supported:
        raise RequiredQuestionsError(
            _('You need to answer questions to complete this check-in.'),
            'incomplete',
            require_answers
        )
    else:
        try:
            ci, created = Checkin.objects.get_or_create(position=op, list=clist, defaults={
                'datetime': dt,
                'nonce': nonce,
            })
        except Checkin.MultipleObjectsReturned:
            ci, created = Checkin.objects.filter(position=op, list=clist).last(), False

    if created or (nonce and nonce == ci.nonce):
        if created:
            op.order.log_action('pretix.event.checkin', data={
                'position': op.id,
                'positionid': op.positionid,
                'first': True,
                'forced': op.order.status != Order.STATUS_PAID,
                'datetime': dt,
                'list': clist.pk
            }, user=user, auth=auth)
            checkin_created.send(op.order.event, checkin=ci)
    else:
        if not force:
            raise CheckInError(
                _('This ticket has already been redeemed.'),
                'already_redeemed',
            )
        op.order.log_action('pretix.event.checkin', data={
            'position': op.id,
            'positionid': op.positionid,
            'first': False,
            'forced': force,
            'datetime': dt,
            'list': clist.pk
        }, user=user, auth=auth)