forked from CGM_Public/pretix_original
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f2cd9a2002 | ||
|
|
874b38db17 | ||
|
|
0f58e1c396 | ||
|
|
36e0afc09e | ||
|
|
7164124a70 | ||
|
|
887d8832c0 | ||
|
|
beb144f9a0 | ||
|
|
6d1dea7922 |
@@ -21,15 +21,15 @@ class IdempotencyMiddleware:
|
||||
if not request.path.startswith('/api/'):
|
||||
return self.get_response(request)
|
||||
|
||||
if not request.META.get('HTTP_X_IDEMPOTENCY_KEY'):
|
||||
if not request.headers.get('X-Idempotency-Key'):
|
||||
return self.get_response(request)
|
||||
|
||||
auth_hash_parts = '{}:{}'.format(
|
||||
request.META.get('HTTP_AUTHORIZATION', ''),
|
||||
request.headers.get('Authorization', ''),
|
||||
request.COOKIES.get(settings.SESSION_COOKIE_NAME, '')
|
||||
)
|
||||
auth_hash = sha1(auth_hash_parts.encode()).hexdigest()
|
||||
idempotency_key = request.META.get('HTTP_X_IDEMPOTENCY_KEY', '')
|
||||
idempotency_key = request.headers.get('X-Idempotency-Key', '')
|
||||
|
||||
with transaction.atomic():
|
||||
call, created = ApiCall.objects.select_for_update().get_or_create(
|
||||
|
||||
@@ -77,6 +77,9 @@ class WebHook(models.Model):
|
||||
all_events = models.BooleanField(default=True, verbose_name=_("All events (including newly created ones)"))
|
||||
limit_events = models.ManyToManyField('pretixbase.Event', verbose_name=_("Limit to events"), blank=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ('id',)
|
||||
|
||||
@property
|
||||
def action_types(self):
|
||||
return [
|
||||
|
||||
@@ -23,4 +23,4 @@ def cleanup_webhook_logs(sender, **kwargs):
|
||||
|
||||
@receiver(periodic_task)
|
||||
def cleanup_api_logs(sender, **kwargs):
|
||||
ApiCall.objects.filter(datetime__lte=now() - timedelta(hours=24)).delete()
|
||||
ApiCall.objects.filter(created__lte=now() - timedelta(hours=24)).delete()
|
||||
|
||||
@@ -31,10 +31,10 @@ class RichOrderingFilter(OrderingFilter):
|
||||
class ConditionalListView:
|
||||
|
||||
def list(self, request, **kwargs):
|
||||
if_modified_since = request.META.get('HTTP_IF_MODIFIED_SINCE')
|
||||
if_modified_since = request.headers.get('If-Modified-Since')
|
||||
if if_modified_since:
|
||||
if_modified_since = parse_http_date_safe(if_modified_since)
|
||||
if_unmodified_since = request.META.get('HTTP_IF_UNMODIFIED_SINCE')
|
||||
if_unmodified_since = request.headers.get('If-Unmodified-Since')
|
||||
if if_unmodified_since:
|
||||
if_unmodified_since = parse_http_date_safe(if_unmodified_since)
|
||||
if not hasattr(request, 'event'):
|
||||
|
||||
@@ -7,7 +7,7 @@ from django.utils.functional import cached_property
|
||||
from django.utils.timezone import now
|
||||
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.decorators import detail_route
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.fields import DateTimeField
|
||||
from rest_framework.response import Response
|
||||
|
||||
@@ -77,7 +77,7 @@ class CheckinListViewSet(viewsets.ModelViewSet):
|
||||
)
|
||||
super().perform_destroy(instance)
|
||||
|
||||
@detail_route(methods=['GET'])
|
||||
@action(detail=True, methods=['GET'])
|
||||
def status(self, *args, **kwargs):
|
||||
clist = self.get_object()
|
||||
cqs = Checkin.objects.filter(
|
||||
@@ -242,7 +242,7 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
|
||||
return qs
|
||||
|
||||
@detail_route(methods=['POST'])
|
||||
@action(detail=True, methods=['POST'])
|
||||
def redeem(self, *args, **kwargs):
|
||||
force = bool(self.request.data.get('force', False))
|
||||
ignore_unpaid = bool(self.request.data.get('ignore_unpaid', False))
|
||||
|
||||
@@ -4,7 +4,7 @@ from django.shortcuts import get_object_or_404
|
||||
from django.utils.functional import cached_property
|
||||
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.decorators import detail_route
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
from rest_framework.filters import OrderingFilter
|
||||
from rest_framework.response import Response
|
||||
@@ -499,7 +499,7 @@ class QuotaViewSet(ConditionalListView, viewsets.ModelViewSet):
|
||||
)
|
||||
super().perform_destroy(instance)
|
||||
|
||||
@detail_route(methods=['get'])
|
||||
@action(detail=True, methods=['get'])
|
||||
def availability(self, request, *args, **kwargs):
|
||||
quota = self.get_object()
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ from django.utils.timezone import make_aware, now
|
||||
from django.utils.translation import ugettext as _
|
||||
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
|
||||
from rest_framework import mixins, serializers, status, viewsets
|
||||
from rest_framework.decorators import detail_route
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.exceptions import (
|
||||
APIException, NotFound, PermissionDenied, ValidationError,
|
||||
)
|
||||
@@ -127,7 +127,7 @@ class OrderViewSet(viewsets.ModelViewSet):
|
||||
serializer = self.get_serializer(queryset, many=True)
|
||||
return Response(serializer.data, headers={'X-Page-Generated': date})
|
||||
|
||||
@detail_route(url_name='download', url_path='download/(?P<output>[^/]+)')
|
||||
@action(detail=True, url_name='download', url_path='download/(?P<output>[^/]+)')
|
||||
def download(self, request, output, **kwargs):
|
||||
provider = self._get_output_provider(output)
|
||||
order = self.get_object()
|
||||
@@ -149,7 +149,7 @@ class OrderViewSet(viewsets.ModelViewSet):
|
||||
)
|
||||
return resp
|
||||
|
||||
@detail_route(methods=['POST'])
|
||||
@action(detail=True, methods=['POST'])
|
||||
def mark_paid(self, request, **kwargs):
|
||||
order = self.get_object()
|
||||
|
||||
@@ -190,7 +190,7 @@ class OrderViewSet(viewsets.ModelViewSet):
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
@detail_route(methods=['POST'])
|
||||
@action(detail=True, methods=['POST'])
|
||||
def mark_canceled(self, request, **kwargs):
|
||||
send_mail = request.data.get('send_email', True)
|
||||
cancellation_fee = request.data.get('cancellation_fee', None)
|
||||
@@ -224,7 +224,7 @@ class OrderViewSet(viewsets.ModelViewSet):
|
||||
)
|
||||
return self.retrieve(request, [], **kwargs)
|
||||
|
||||
@detail_route(methods=['POST'])
|
||||
@action(detail=True, methods=['POST'])
|
||||
def approve(self, request, **kwargs):
|
||||
send_mail = request.data.get('send_email', True)
|
||||
|
||||
@@ -242,7 +242,7 @@ class OrderViewSet(viewsets.ModelViewSet):
|
||||
return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST)
|
||||
return self.retrieve(request, [], **kwargs)
|
||||
|
||||
@detail_route(methods=['POST'])
|
||||
@action(detail=True, methods=['POST'])
|
||||
def deny(self, request, **kwargs):
|
||||
send_mail = request.data.get('send_email', True)
|
||||
comment = request.data.get('comment', '')
|
||||
@@ -260,7 +260,7 @@ class OrderViewSet(viewsets.ModelViewSet):
|
||||
return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST)
|
||||
return self.retrieve(request, [], **kwargs)
|
||||
|
||||
@detail_route(methods=['POST'])
|
||||
@action(detail=True, methods=['POST'])
|
||||
def mark_pending(self, request, **kwargs):
|
||||
order = self.get_object()
|
||||
|
||||
@@ -279,7 +279,7 @@ class OrderViewSet(viewsets.ModelViewSet):
|
||||
)
|
||||
return self.retrieve(request, [], **kwargs)
|
||||
|
||||
@detail_route(methods=['POST'])
|
||||
@action(detail=True, methods=['POST'])
|
||||
def mark_expired(self, request, **kwargs):
|
||||
order = self.get_object()
|
||||
|
||||
@@ -296,7 +296,7 @@ class OrderViewSet(viewsets.ModelViewSet):
|
||||
)
|
||||
return self.retrieve(request, [], **kwargs)
|
||||
|
||||
@detail_route(methods=['POST'])
|
||||
@action(detail=True, methods=['POST'])
|
||||
def mark_refunded(self, request, **kwargs):
|
||||
order = self.get_object()
|
||||
|
||||
@@ -313,7 +313,7 @@ class OrderViewSet(viewsets.ModelViewSet):
|
||||
)
|
||||
return self.retrieve(request, [], **kwargs)
|
||||
|
||||
@detail_route(methods=['POST'])
|
||||
@action(detail=True, methods=['POST'])
|
||||
def create_invoice(self, request, **kwargs):
|
||||
order = self.get_object()
|
||||
has_inv = order.invoices.exists() and not (
|
||||
@@ -345,7 +345,7 @@ class OrderViewSet(viewsets.ModelViewSet):
|
||||
status=status.HTTP_201_CREATED
|
||||
)
|
||||
|
||||
@detail_route(methods=['POST'])
|
||||
@action(detail=True, methods=['POST'])
|
||||
def resend_link(self, request, **kwargs):
|
||||
order = self.get_object()
|
||||
if not order.email:
|
||||
@@ -359,7 +359,7 @@ class OrderViewSet(viewsets.ModelViewSet):
|
||||
status=status.HTTP_204_NO_CONTENT
|
||||
)
|
||||
|
||||
@detail_route(methods=['POST'])
|
||||
@action(detail=True, methods=['POST'])
|
||||
@transaction.atomic
|
||||
def regenerate_secrets(self, request, **kwargs):
|
||||
order = self.get_object()
|
||||
@@ -377,7 +377,7 @@ class OrderViewSet(viewsets.ModelViewSet):
|
||||
)
|
||||
return self.retrieve(request, [], **kwargs)
|
||||
|
||||
@detail_route(methods=['POST'])
|
||||
@action(detail=True, methods=['POST'])
|
||||
def extend(self, request, **kwargs):
|
||||
new_date = request.data.get('expires', None)
|
||||
force = request.data.get('force', False)
|
||||
@@ -619,7 +619,7 @@ class OrderPositionViewSet(mixins.DestroyModelMixin, viewsets.ReadOnlyModelViewS
|
||||
return prov
|
||||
raise NotFound('Unknown output provider.')
|
||||
|
||||
@detail_route(url_name='download', url_path='download/(?P<output>[^/]+)')
|
||||
@action(detail=True, url_name='download', url_path='download/(?P<output>[^/]+)')
|
||||
def download(self, request, output, **kwargs):
|
||||
provider = self._get_output_provider(output)
|
||||
pos = self.get_object()
|
||||
@@ -670,7 +670,7 @@ class PaymentViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
order = get_object_or_404(Order, code=self.kwargs['order'], event=self.request.event)
|
||||
return order.payments.all()
|
||||
|
||||
@detail_route(methods=['POST'])
|
||||
@action(detail=True, methods=['POST'])
|
||||
def confirm(self, request, **kwargs):
|
||||
payment = self.get_object()
|
||||
force = request.data.get('force', False)
|
||||
@@ -691,7 +691,7 @@ class PaymentViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
pass
|
||||
return self.retrieve(request, [], **kwargs)
|
||||
|
||||
@detail_route(methods=['POST'])
|
||||
@action(detail=True, methods=['POST'])
|
||||
def refund(self, request, **kwargs):
|
||||
payment = self.get_object()
|
||||
amount = serializers.DecimalField(max_digits=10, decimal_places=2).to_internal_value(
|
||||
@@ -756,7 +756,7 @@ class PaymentViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
payment.order.save(update_fields=['status', 'expires'])
|
||||
return Response(OrderRefundSerializer(r).data, status=status.HTTP_200_OK)
|
||||
|
||||
@detail_route(methods=['POST'])
|
||||
@action(detail=True, methods=['POST'])
|
||||
def cancel(self, request, **kwargs):
|
||||
payment = self.get_object()
|
||||
|
||||
@@ -784,7 +784,7 @@ class RefundViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
|
||||
order = get_object_or_404(Order, code=self.kwargs['order'], event=self.request.event)
|
||||
return order.refunds.all()
|
||||
|
||||
@detail_route(methods=['POST'])
|
||||
@action(detail=True, methods=['POST'])
|
||||
def cancel(self, request, **kwargs):
|
||||
refund = self.get_object()
|
||||
|
||||
@@ -801,7 +801,7 @@ class RefundViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
|
||||
}, user=self.request.user if self.request.user.is_authenticated else None, auth=self.request.auth)
|
||||
return self.retrieve(request, [], **kwargs)
|
||||
|
||||
@detail_route(methods=['POST'])
|
||||
@action(detail=True, methods=['POST'])
|
||||
def process(self, request, **kwargs):
|
||||
refund = self.get_object()
|
||||
|
||||
@@ -826,7 +826,7 @@ class RefundViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
|
||||
refund.order.save(update_fields=['status', 'expires'])
|
||||
return self.retrieve(request, [], **kwargs)
|
||||
|
||||
@detail_route(methods=['POST'])
|
||||
@action(detail=True, methods=['POST'])
|
||||
def done(self, request, **kwargs):
|
||||
refund = self.get_object()
|
||||
|
||||
@@ -916,7 +916,7 @@ class InvoiceViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
nr=Concat('prefix', 'invoice_no')
|
||||
)
|
||||
|
||||
@detail_route()
|
||||
@action(detail=True, )
|
||||
def download(self, request, **kwargs):
|
||||
invoice = self.get_object()
|
||||
|
||||
@@ -934,7 +934,7 @@ class InvoiceViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
resp['Content-Disposition'] = 'attachment; filename="{}.pdf"'.format(invoice.number)
|
||||
return resp
|
||||
|
||||
@detail_route(methods=['POST'])
|
||||
@action(detail=True, methods=['POST'])
|
||||
def regenerate(self, request, **kwarts):
|
||||
inv = self.get_object()
|
||||
if inv.canceled:
|
||||
@@ -953,7 +953,7 @@ class InvoiceViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
)
|
||||
return Response(status=204)
|
||||
|
||||
@detail_route(methods=['POST'])
|
||||
@action(detail=True, methods=['POST'])
|
||||
def reissue(self, request, **kwarts):
|
||||
inv = self.get_object()
|
||||
if inv.canceled:
|
||||
|
||||
@@ -7,7 +7,7 @@ from django_filters.rest_framework import (
|
||||
BooleanFilter, DjangoFilterBackend, FilterSet,
|
||||
)
|
||||
from rest_framework import status, viewsets
|
||||
from rest_framework.decorators import list_route
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
from rest_framework.filters import OrderingFilter
|
||||
from rest_framework.response import Response
|
||||
@@ -116,7 +116,7 @@ class VoucherViewSet(viewsets.ModelViewSet):
|
||||
instance.cartposition_set.all().delete()
|
||||
super().perform_destroy(instance)
|
||||
|
||||
@list_route(methods=['POST'])
|
||||
@action(detail=False, methods=['POST'])
|
||||
def batch_create(self, request, *args, **kwargs):
|
||||
if any(self._predict_quota_check(d, None) for d in request.data):
|
||||
lockfn = request.event.lock
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import django_filters
|
||||
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.decorators import detail_route
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.exceptions import PermissionDenied, ValidationError
|
||||
from rest_framework.filters import OrderingFilter
|
||||
from rest_framework.response import Response
|
||||
@@ -69,7 +69,7 @@ class WaitingListViewSet(viewsets.ModelViewSet):
|
||||
)
|
||||
super().perform_destroy(instance)
|
||||
|
||||
@detail_route(methods=['POST'])
|
||||
@action(detail=True, methods=['POST'])
|
||||
def send_voucher(self, *args, **kwargs):
|
||||
try:
|
||||
self.get_object().send_voucher(
|
||||
|
||||
@@ -97,7 +97,7 @@ def get_language_from_event(request: HttpRequest) -> str:
|
||||
|
||||
|
||||
def get_language_from_browser(request: HttpRequest) -> str:
|
||||
accept = request.META.get('HTTP_ACCEPT_LANGUAGE', '')
|
||||
accept = request.headers.get('Accept-Language', '')
|
||||
for accept_lang, unused in parse_accept_lang_header(accept):
|
||||
if accept_lang == '*':
|
||||
break
|
||||
|
||||
@@ -111,6 +111,7 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
|
||||
class Meta:
|
||||
verbose_name = _("User")
|
||||
verbose_name_plural = _("Users")
|
||||
ordering = ('email',)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.email = self.email.lower()
|
||||
|
||||
@@ -37,7 +37,7 @@ class MultiStringField(TextField):
|
||||
def get_prep_lookup(self, lookup_type, value): # NOQA
|
||||
raise TypeError('Lookups on multi strings are currently not supported.')
|
||||
|
||||
def from_db_value(self, value, expression, connection, context):
|
||||
def from_db_value(self, value, expression, connection):
|
||||
if value:
|
||||
return [v for v in value.split(DELIMITER) if v]
|
||||
else:
|
||||
|
||||
@@ -1159,7 +1159,7 @@ class OrderPayment(models.Model):
|
||||
self.order.log_action('pretix.event.order.overpaid', {}, user=user, auth=auth)
|
||||
order_paid.send(self.order.event, order=self.order)
|
||||
|
||||
def confirm(self, count_waitinglist=True, send_mail=True, force=False, user=None, auth=None, mail_text=''):
|
||||
def confirm(self, count_waitinglist=True, send_mail=True, force=False, user=None, auth=None, mail_text='', lock=True):
|
||||
"""
|
||||
Marks the payment as complete. If possible, this also marks the order as paid if no further
|
||||
payment is required
|
||||
@@ -1218,7 +1218,7 @@ class OrderPayment(models.Model):
|
||||
if payment_sum - refund_sum < self.order.total:
|
||||
return
|
||||
|
||||
if self.order.status == Order.STATUS_PENDING and self.order.expires > now() + timedelta(hours=12):
|
||||
if (self.order.status == Order.STATUS_PENDING and self.order.expires > now() + timedelta(hours=12)) or not lock:
|
||||
# Performance optimization. In this case, there's really no reason to lock everything and an atomic
|
||||
# database transaction is more than enough.
|
||||
with transaction.atomic():
|
||||
|
||||
@@ -118,6 +118,9 @@ class TaxRule(LoggedModel):
|
||||
)
|
||||
custom_rules = models.TextField(blank=True, null=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ('event', 'rate', 'id')
|
||||
|
||||
def allow_delete(self):
|
||||
from pretix.base.models.orders import OrderFee, OrderPosition
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ def build_invoice(invoice: Invoice) -> Invoice:
|
||||
positions = list(
|
||||
invoice.order.positions.select_related('addon_to', 'item', 'tax_rule', 'subevent', 'variation').annotate(
|
||||
addon_c=Count('addons')
|
||||
)
|
||||
).order_by('positionid', 'id')
|
||||
)
|
||||
|
||||
reverse_charge = False
|
||||
|
||||
@@ -219,14 +219,23 @@ def mail_send_task(self, *args, to: List[str], subject: str, body: str, html: st
|
||||
args.append((name, content, ct.type))
|
||||
attach_size += len(content)
|
||||
|
||||
if attach_tickets < 4 * 1024 * 1024:
|
||||
if attach_size < 4 * 1024 * 1024:
|
||||
# Do not attach more than 4MB, it will bounce way to often.
|
||||
|
||||
for a in args:
|
||||
try:
|
||||
email.attach(*a)
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
order.log_action(
|
||||
'pretix.event.order.email.error',
|
||||
data={
|
||||
'subject': 'Attachments skipped',
|
||||
'message': 'Attachment have not been send because {} bytes are likely too large to arrive.'.format(attach_size),
|
||||
'recipient': '',
|
||||
'invoices': [],
|
||||
}
|
||||
)
|
||||
|
||||
email = email_filter.send_chained(event, 'message', message=email, order=order)
|
||||
|
||||
|
||||
@@ -567,6 +567,7 @@ def _create_order(event: Event, email: str, positions: List[CartPosition], now_d
|
||||
meta_info: dict=None, sales_channel: str='web'):
|
||||
fees, pf = _get_fees(positions, payment_provider, address, meta_info, event)
|
||||
total = sum([c.price for c in positions]) + sum([c.value for c in fees])
|
||||
p = None
|
||||
|
||||
with transaction.atomic():
|
||||
order = Order(
|
||||
@@ -600,7 +601,7 @@ def _create_order(event: Event, email: str, positions: List[CartPosition], now_d
|
||||
fee.save()
|
||||
|
||||
if payment_provider and not order.require_approval:
|
||||
order.payments.create(
|
||||
p = order.payments.create(
|
||||
state=OrderPayment.PAYMENT_STATE_CREATED,
|
||||
provider=payment_provider.identifier,
|
||||
amount=total,
|
||||
@@ -616,7 +617,7 @@ def _create_order(event: Event, email: str, positions: List[CartPosition], now_d
|
||||
order.log_action('pretix.event.order.consent', data={'msg': msg})
|
||||
|
||||
order_placed.send(event, order=order)
|
||||
return order
|
||||
return order, p
|
||||
|
||||
|
||||
def _perform_order(event: str, payment_provider: str, position_ids: List[str],
|
||||
@@ -648,8 +649,12 @@ def _perform_order(event: str, payment_provider: str, position_ids: List[str],
|
||||
if len(position_ids) != len(positions):
|
||||
raise OrderError(error_messages['internal'])
|
||||
_check_positions(event, now_dt, positions, address=addr)
|
||||
order = _create_order(event, email, positions, now_dt, pprov,
|
||||
locale=locale, address=addr, meta_info=meta_info, sales_channel=sales_channel)
|
||||
order, payment = _create_order(event, email, positions, now_dt, pprov,
|
||||
locale=locale, address=addr, meta_info=meta_info, sales_channel=sales_channel)
|
||||
|
||||
free_order_flow = payment and payment_provider == 'free' and order.total == Decimal('0.00')
|
||||
if free_order_flow:
|
||||
payment.confirm(send_mail=False, lock=False)
|
||||
|
||||
invoice = order.invoices.last() # Might be generated by plugin already
|
||||
if event.settings.get('invoice_generate') == 'True' and invoice_qualified(order):
|
||||
@@ -664,7 +669,7 @@ def _perform_order(event: str, payment_provider: str, position_ids: List[str],
|
||||
if order.require_approval:
|
||||
email_template = event.settings.mail_text_order_placed_require_approval
|
||||
log_entry = 'pretix.event.order.email.order_placed_require_approval'
|
||||
elif payment_provider == 'free':
|
||||
elif free_order_flow:
|
||||
email_template = event.settings.mail_text_order_free
|
||||
log_entry = 'pretix.event.order.email.order_free'
|
||||
else:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "error.html" %}
|
||||
{% load i18n %}
|
||||
{% load staticfiles %}
|
||||
{% load static %}
|
||||
{% block title %}{% trans "Bad Request" %}{% endblock %}
|
||||
{% block content %}
|
||||
<i class="fa fa-frown-o fa-fw big-icon"></i>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "error.html" %}
|
||||
{% load i18n %}
|
||||
{% load staticfiles %}
|
||||
{% load static %}
|
||||
{% block title %}{% trans "Permission denied" %}{% endblock %}
|
||||
{% block content %}
|
||||
<i class="fa fa-fw fa-lock big-icon"></i>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "error.html" %}
|
||||
{% load i18n %}
|
||||
{% load staticfiles %}
|
||||
{% load static %}
|
||||
{% block title %}{% trans "Not found" %}{% endblock %}
|
||||
{% block content %}
|
||||
<i class="fa fa-meh-o fa-fw big-icon"></i>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "error.html" %}
|
||||
{% load i18n %}
|
||||
{% load staticfiles %}
|
||||
{% load static %}
|
||||
{% block title %}{% trans "Internal Server Error" %}{% endblock %}
|
||||
{% block content %}
|
||||
<i class="fa fa-bolt big-icon fa-fw"></i>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "error.html" %}
|
||||
{% load i18n %}
|
||||
{% load staticfiles %}
|
||||
{% load static %}
|
||||
{% block title %}{% trans "Verification failed" %}{% endblock %}
|
||||
{% block content %}
|
||||
<i class="fa fa-frown-o big-icon fa-fw"></i>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% load compress %}
|
||||
{% load i18n %}
|
||||
{% load staticfiles %}
|
||||
{% load static %}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
|
||||
@@ -2,7 +2,7 @@ from django.utils import timezone
|
||||
from django.utils.translation.trans_real import DjangoTranslation
|
||||
from django.views.decorators.cache import cache_page
|
||||
from django.views.decorators.http import etag
|
||||
from django.views.i18n import JavaScriptCatalog, render_javascript_catalog
|
||||
from django.views.i18n import JavaScriptCatalog
|
||||
|
||||
# Yes, we want to regenerate this every time the module has been imported to
|
||||
# refresh the cache at least at every code deployment
|
||||
@@ -21,4 +21,5 @@ js_info_dict = {
|
||||
def js_catalog(request, lang):
|
||||
c = JavaScriptCatalog()
|
||||
c.translation = DjangoTranslation(lang, domain='djangojs')
|
||||
return render_javascript_catalog(c.get_catalog(), c.get_plural())
|
||||
context = c.get_context_data()
|
||||
return c.render_to_response(context)
|
||||
|
||||
@@ -20,10 +20,10 @@ def serve_metrics(request):
|
||||
return unauthed_response()
|
||||
|
||||
# check if the user is properly authorized:
|
||||
if "HTTP_AUTHORIZATION" not in request.META:
|
||||
if "Authorization" not in request.headers:
|
||||
return unauthed_response()
|
||||
|
||||
method, credentials = request.META["HTTP_AUTHORIZATION"].split(" ", 1)
|
||||
method, credentials = request.headers["Authorization"].split(" ", 1)
|
||||
if method.lower() != "basic":
|
||||
return unauthed_response()
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% load compress %}
|
||||
{% load i18n %}
|
||||
{% load staticfiles %}
|
||||
{% load static %}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "pretixcontrol/organizers/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load staticfiles %}
|
||||
{% load static %}
|
||||
{% load bootstrap3 %}
|
||||
{% block inner %}
|
||||
<h1>{% trans "Connect to device:" %} {{ device.name }}</h1>
|
||||
|
||||
@@ -49,7 +49,9 @@ class ItemList(ListView):
|
||||
event=self.request.event
|
||||
).annotate(
|
||||
var_count=Count('variations')
|
||||
).prefetch_related("category")
|
||||
).prefetch_related("category").order_by(
|
||||
'category__position', 'category', 'position'
|
||||
)
|
||||
|
||||
|
||||
def item_move(request, item, up=True):
|
||||
|
||||
@@ -13,7 +13,7 @@ class SessionReauthRequired(Exception):
|
||||
|
||||
|
||||
def get_user_agent_hash(request):
|
||||
return hashlib.sha256(request.META['HTTP_USER_AGENT'].encode()).hexdigest()
|
||||
return hashlib.sha256(request.headers['User-Agent'].encode()).hexdigest()
|
||||
|
||||
|
||||
def assert_session_valid(request):
|
||||
@@ -26,7 +26,7 @@ def assert_session_valid(request):
|
||||
if time.time() - last_used > settings.PRETIX_SESSION_TIMEOUT_RELATIVE:
|
||||
raise SessionReauthRequired()
|
||||
|
||||
if 'HTTP_USER_AGENT' in request.META:
|
||||
if 'User-Agent' in request.headers:
|
||||
if 'pinned_user_agent' in request.session:
|
||||
if request.session.get('pinned_user_agent') != get_user_agent_hash(request):
|
||||
raise SessionInvalid()
|
||||
|
||||
@@ -23,10 +23,10 @@ LOCAL_HOST_NAMES = ('testserver', 'localhost')
|
||||
class MultiDomainMiddleware(MiddlewareMixin):
|
||||
def process_request(self, request):
|
||||
# We try three options, in order of decreasing preference.
|
||||
if settings.USE_X_FORWARDED_HOST and ('HTTP_X_FORWARDED_HOST' in request.META):
|
||||
host = request.META['HTTP_X_FORWARDED_HOST']
|
||||
elif 'HTTP_HOST' in request.META:
|
||||
host = request.META['HTTP_HOST']
|
||||
if settings.USE_X_FORWARDED_HOST and ('X-Forwarded-Host' in request.headers):
|
||||
host = request.headers['X-Forwarded-Host']
|
||||
elif 'Host' in request.headers:
|
||||
host = request.headers['Host']
|
||||
else:
|
||||
# Reconstruct the host using the algorithm from PEP 333.
|
||||
host = request.META['SERVER_NAME']
|
||||
|
||||
@@ -52,3 +52,6 @@ class BadgeItem(models.Model):
|
||||
on_delete=models.CASCADE)
|
||||
layout = models.ForeignKey('BadgeLayout', on_delete=models.CASCADE, related_name='item_assignments',
|
||||
null=True, blank=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ('id',)
|
||||
|
||||
@@ -28,8 +28,7 @@ def parse(data, hint):
|
||||
resrow['amount'] = re.sub('[^0-9,+.-]', '', resrow['amount'])
|
||||
if hint.get('date') is not None:
|
||||
resrow['date'] = row[int(hint.get('date'))].strip()
|
||||
if len(resrow['amount']) == 0 or 'amount' not in resrow \
|
||||
or len(resrow['reference']) == 0 or resrow['date'] == '':
|
||||
if len(resrow['amount']) == 0 or 'amount' not in resrow or resrow['date'] == '':
|
||||
# This is probably a headline or something other special.
|
||||
continue
|
||||
if resrow['reference'] or resrow['payer']:
|
||||
|
||||
@@ -21,6 +21,9 @@ class BankImportJob(models.Model):
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
state = models.CharField(max_length=32, choices=STATES, default=STATE_PENDING)
|
||||
|
||||
class Meta:
|
||||
ordering = ('id',)
|
||||
|
||||
@property
|
||||
def owner_kwargs(self):
|
||||
if self.event:
|
||||
@@ -76,3 +79,4 @@ class BankTransaction(models.Model):
|
||||
|
||||
class Meta:
|
||||
unique_together = ('event', 'organizer', 'checksum')
|
||||
ordering = ('date', 'id')
|
||||
|
||||
@@ -73,3 +73,4 @@ class TicketLayoutItem(models.Model):
|
||||
|
||||
class Meta:
|
||||
unique_together = (('item', 'layout', 'sales_channel'),)
|
||||
ordering = ("id",)
|
||||
|
||||
@@ -14,7 +14,7 @@ from django.utils.translation import (
|
||||
from django.views.generic.base import TemplateResponseMixin
|
||||
|
||||
from pretix.base.models import Order
|
||||
from pretix.base.models.orders import InvoiceAddress
|
||||
from pretix.base.models.orders import InvoiceAddress, OrderPayment
|
||||
from pretix.base.services.cart import (
|
||||
get_fees, set_cart_addons, update_tax_rates,
|
||||
)
|
||||
@@ -721,7 +721,7 @@ class ConfirmStep(CartMixin, AsyncAction, TemplateFlowStep):
|
||||
return self.get_step_url(self.request)
|
||||
|
||||
def get_order_url(self, order):
|
||||
payment = order.payments.first()
|
||||
payment = order.payments.filter(state__in=[OrderPayment.PAYMENT_STATE_CREATED, OrderPayment.PAYMENT_STATE_PENDING]).first()
|
||||
if not payment:
|
||||
return eventreverse(self.request.event, 'presale:event.order', kwargs={
|
||||
'order': order.code,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{% extends "pretixpresale/event/checkout_base.html" %}
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% load rich_text %}
|
||||
{% block inner %}
|
||||
<p>
|
||||
{% trans "For some of the products in your cart, you can choose additional options before you continue." %}
|
||||
@@ -30,6 +31,9 @@
|
||||
{% for c in form.categories %}
|
||||
<fieldset>
|
||||
<legend>{{ c.category.name }}</legend>
|
||||
{% if c.category.description %}
|
||||
{{ c.category.description|rich_text }}
|
||||
{% endif %}
|
||||
<p>
|
||||
{% if c.min_count == c.max_count %}
|
||||
{% blocktrans trimmed count min_count=c.min_count %}
|
||||
|
||||
@@ -107,13 +107,13 @@
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if can_download and download_buttons and order.count_positions %}
|
||||
<div class="alert alert-info">
|
||||
<div class="alert alert-info info-download">
|
||||
{% blocktrans trimmed %}
|
||||
You can download your tickets using the buttons below. Please have your ticket ready when entering the event.
|
||||
{% endblocktrans %}
|
||||
</div>
|
||||
{% if cart.positions|length > 1 and can_download_multi %}
|
||||
<p>
|
||||
<p class="info-download">
|
||||
{% trans "Download all tickets at once:" %}
|
||||
{% for b in download_buttons %}
|
||||
{% if b.multi %}
|
||||
@@ -131,7 +131,7 @@
|
||||
{% endif %}
|
||||
{% elif not download_buttons and ticket_download_date %}
|
||||
{% if order.status == 'p' %}
|
||||
<div class="alert alert-info">
|
||||
<div class="alert alert-info info-download">
|
||||
{% blocktrans trimmed with date=ticket_download_date|date:"SHORT_DATE_FORMAT" %}
|
||||
You will be able to download your tickets here starting on {{ date }}.
|
||||
{% endblocktrans %}
|
||||
@@ -249,7 +249,7 @@
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
{% if order.cancel_allowed %}
|
||||
<div class="panel panel-primary cart">
|
||||
<div class="panel panel-primary panel-cancellation">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
{% trans "Cancellation" context "action" %}
|
||||
|
||||
@@ -11,7 +11,7 @@ from .robots import NoSearchIndexViewMixin
|
||||
class LocaleSet(NoSearchIndexViewMixin, View):
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
url = request.GET.get('next', request.META.get('HTTP_REFERER', '/'))
|
||||
url = request.GET.get('next', request.headers.get('Referer', '/'))
|
||||
url = url if is_safe_url(url, allowed_hosts=[request.get_host()]) else '/'
|
||||
resp = HttpResponseRedirect(url)
|
||||
|
||||
|
||||
65
src/pretix/static/pretixpresale/scss/_print.scss
Normal file
65
src/pretix/static/pretixpresale/scss/_print.scss
Normal file
@@ -0,0 +1,65 @@
|
||||
@media print {
|
||||
body {
|
||||
font-size: 12px;
|
||||
}
|
||||
a[href]:after {
|
||||
content: none;
|
||||
}
|
||||
.container {
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
.page-header {
|
||||
margin-top: 0;
|
||||
}
|
||||
.page-header .pull-right,
|
||||
.thank-you,
|
||||
.panel-heading a,
|
||||
.download-desktop,
|
||||
.download-mobile,
|
||||
.info-download,
|
||||
.panel-cancellation,
|
||||
footer {
|
||||
display: none !important;
|
||||
}
|
||||
.cart-row .product {
|
||||
width: 50%;
|
||||
}
|
||||
.cart-row.has-downloads .product {
|
||||
width: 80%;
|
||||
}
|
||||
.cart-row .count {
|
||||
width: 10%;
|
||||
}
|
||||
.cart-row .singleprice {
|
||||
width: 20%;
|
||||
clear: none;
|
||||
}
|
||||
.cart-row .totalprice {
|
||||
width: 20%;
|
||||
}
|
||||
.panel-body .text-right a.btn {
|
||||
display: none;
|
||||
}
|
||||
@media(max-width: $screen-sm-max) {
|
||||
.cart-row .product {
|
||||
width: 50%;
|
||||
}
|
||||
.cart-row.has-downloads .product {
|
||||
width: 80%;
|
||||
}
|
||||
}
|
||||
@media (min-width: $screen-sm-min) {
|
||||
.dl-horizontal dt {
|
||||
width: 100px;
|
||||
}
|
||||
.dl-horizontal dd {
|
||||
margin-left: 120px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 0px) {
|
||||
@include make-grid(sm);
|
||||
@include make-grid(md);
|
||||
@include make-grid(lg);
|
||||
}
|
||||
}
|
||||
@@ -290,3 +290,4 @@ h2 .label {
|
||||
|
||||
@import "_iframe.scss";
|
||||
@import "_a11y.scss";
|
||||
@import "_print.scss";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
django-debug-toolbar==1.9.1
|
||||
sqlparse==0.2.1 # pinned due to difficulties with django-debug-toolbar
|
||||
django-debug-toolbar==1.11
|
||||
sqlparse==0.3.* # pinned due to difficulties with django-debug-toolbar
|
||||
# Testing requirements
|
||||
pycodestyle==2.5.*
|
||||
pyflakes==2.1.*
|
||||
@@ -7,15 +7,16 @@ pep8-naming
|
||||
flake8==3.7.*
|
||||
codecov
|
||||
coverage
|
||||
pytest==3.6.*
|
||||
pytest==4.4.*
|
||||
pytest-django
|
||||
pytest-xdist==1.27.*
|
||||
isort
|
||||
pytest-rerunfailures==4.*
|
||||
pytest-mock==1.6.*
|
||||
pytest-cache
|
||||
pytest-sugar
|
||||
pytest-rerunfailures==7.*
|
||||
pytest-mock==1.10.*
|
||||
responses
|
||||
potypo
|
||||
freezegun
|
||||
|
||||
# Not really required, just nice to have
|
||||
pytest-xdist==1.28.*
|
||||
pytest-cache
|
||||
pytest-sugar
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
# Functional requirements
|
||||
Django>=2.1,<2.2
|
||||
djangorestframework==3.8.*
|
||||
python-dateutil
|
||||
Django==2.2.*
|
||||
djangorestframework==3.9.*
|
||||
python-dateutil==2.8.*
|
||||
pytz
|
||||
django-bootstrap3==10.0.*
|
||||
django-bootstrap3==11.0.*
|
||||
django-formset-js-improved==0.5.0.2
|
||||
django-compressor==2.2.*
|
||||
django-hierarkey==1.0.*,>=1.0.3
|
||||
django-filter==2.0.*
|
||||
django-filter==2.1.*
|
||||
reportlab==3.5.*
|
||||
PyPDF2==1.26.*
|
||||
Pillow==5.*
|
||||
django-libsass
|
||||
libsass
|
||||
django-otp==0.4.*
|
||||
django-otp==0.5.*
|
||||
python-u2flib-server==4.*
|
||||
django-formtools==2.1
|
||||
celery==4.3.*
|
||||
@@ -28,7 +28,7 @@ dj-static
|
||||
csscompressor
|
||||
django-markup
|
||||
markdown<=2.2
|
||||
bleach==2.*
|
||||
bleach==3.1.*
|
||||
sentry-sdk==0.7.*
|
||||
babel
|
||||
django-i18nfield>=1.4.0
|
||||
|
||||
@@ -18,6 +18,9 @@ skip = make_testdata.py,wsgi.py,bootstrap,celery_app.py,pretix/settings.py,tests
|
||||
[tool:pytest]
|
||||
DJANGO_SETTINGS_MODULE=tests.settings
|
||||
addopts =--reruns 3 -rw
|
||||
filterwarnings =
|
||||
ignore:The 'warn' method is deprecated:DeprecationWarning
|
||||
ignore:django.contrib.staticfiles.templatetags.static:DeprecationWarning
|
||||
|
||||
[coverage:run]
|
||||
source = pretix
|
||||
|
||||
27
src/setup.py
27
src/setup.py
@@ -87,21 +87,21 @@ setup(
|
||||
|
||||
keywords='tickets web shop ecommerce',
|
||||
install_requires=[
|
||||
'Django>=2.1,<2.2',
|
||||
'djangorestframework==3.8.*',
|
||||
'python-dateutil==2.4.*',
|
||||
'Django==2.2.*',
|
||||
'djangorestframework==3.9.*',
|
||||
'python-dateutil==2.8.*',
|
||||
'pytz',
|
||||
'django-bootstrap3==10.0.*',
|
||||
'django-bootstrap3==11.0.*',
|
||||
'django-formset-js-improved==0.5.0.2',
|
||||
'django-compressor==2.2.*',
|
||||
'django-hierarkey==1.0.*,>=1.0.2',
|
||||
'django-filter==2.0.*',
|
||||
'django-filter==2.1.*',
|
||||
'reportlab==3.5.*',
|
||||
'Pillow==5.*',
|
||||
'PyPDF2==1.26.*',
|
||||
'django-libsass',
|
||||
'libsass',
|
||||
'django-otp==0.4.*',
|
||||
'django-otp==0.5.*',
|
||||
'python-u2flib-server==4.*',
|
||||
'django-formtools==2.1',
|
||||
'celery==4.3.*',
|
||||
@@ -116,7 +116,7 @@ setup(
|
||||
'csscompressor',
|
||||
'django-markup',
|
||||
'markdown<=2.2',
|
||||
'bleach==2.*',
|
||||
'bleach==3.1.*',
|
||||
'sentry-sdk==0.7.*',
|
||||
'babel',
|
||||
'paypalrestsdk==1.13.*',
|
||||
@@ -144,21 +144,22 @@ setup(
|
||||
],
|
||||
extras_require={
|
||||
'dev': [
|
||||
'django-debug-toolbar==1.9.1',
|
||||
'sqlparse==0.2.1',
|
||||
'django-debug-toolbar==1.11',
|
||||
'sqlparse==0.3.*',
|
||||
'pycodestyle==2.5.*',
|
||||
'pyflakes==2.1.*',
|
||||
'flake8==3.7.*',
|
||||
'pep8-naming',
|
||||
'coveralls',
|
||||
'coverage',
|
||||
'pytest==3.6.*',
|
||||
'pytest==4.4.*',
|
||||
'pytest-django',
|
||||
'pytest-xdist==1.27.*',
|
||||
'pytest-xdist==1.28.*',
|
||||
'isort',
|
||||
'pytest-mock==1.6.*',
|
||||
'pytest-rerunfailures',
|
||||
'pytest-mock==1.10.*',
|
||||
'pytest-rerunfailures==7.*',
|
||||
'responses',
|
||||
'potypo',
|
||||
'freezegun',
|
||||
],
|
||||
'memcached': ['pylibmc'],
|
||||
|
||||
@@ -76,8 +76,8 @@ def test_cp_list(token_client, organizer, event, item, taxrule, question):
|
||||
cr = CartPosition.objects.create(
|
||||
event=event, cart_id="aaa", item=item,
|
||||
price=23, attendee_name_parts={'full_name': 'Peter'},
|
||||
datetime=datetime.datetime(2018, 6, 11, 10, 0, 0, 0),
|
||||
expires=datetime.datetime(2018, 6, 11, 10, 0, 0, 0)
|
||||
datetime=datetime.datetime(2018, 6, 11, 10, 0, 0, 0, tzinfo=UTC),
|
||||
expires=datetime.datetime(2018, 6, 11, 10, 0, 0, 0, tzinfo=UTC)
|
||||
)
|
||||
res = dict(TEST_CARTPOSITION_RES)
|
||||
res["id"] = cr.pk
|
||||
@@ -97,8 +97,8 @@ def test_cp_list_api(token_client, organizer, event, item, taxrule, question):
|
||||
cr = CartPosition.objects.create(
|
||||
event=event, cart_id="aaa@api", item=item,
|
||||
price=23, attendee_name_parts={'full_name': 'Peter'},
|
||||
datetime=datetime.datetime(2018, 6, 11, 10, 0, 0, 0),
|
||||
expires=datetime.datetime(2018, 6, 11, 10, 0, 0, 0)
|
||||
datetime=datetime.datetime(2018, 6, 11, 10, 0, 0, 0, tzinfo=UTC),
|
||||
expires=datetime.datetime(2018, 6, 11, 10, 0, 0, 0, tzinfo=UTC)
|
||||
)
|
||||
res = dict(TEST_CARTPOSITION_RES)
|
||||
res["id"] = cr.pk
|
||||
@@ -118,8 +118,8 @@ def test_cp_detail(token_client, organizer, event, item, taxrule, question):
|
||||
cr = CartPosition.objects.create(
|
||||
event=event, cart_id="aaa@api", item=item,
|
||||
price=23, attendee_name_parts={'full_name': 'Peter'},
|
||||
datetime=datetime.datetime(2018, 6, 11, 10, 0, 0, 0),
|
||||
expires=datetime.datetime(2018, 6, 11, 10, 0, 0, 0)
|
||||
datetime=datetime.datetime(2018, 6, 11, 10, 0, 0, 0, tzinfo=UTC),
|
||||
expires=datetime.datetime(2018, 6, 11, 10, 0, 0, 0, tzinfo=UTC)
|
||||
)
|
||||
res = dict(TEST_CARTPOSITION_RES)
|
||||
res["id"] = cr.pk
|
||||
@@ -139,8 +139,8 @@ def test_cp_delete(token_client, organizer, event, item, taxrule, question):
|
||||
cr = CartPosition.objects.create(
|
||||
event=event, cart_id="aaa@api", item=item,
|
||||
price=23, attendee_name_parts={'full_name': 'Peter'},
|
||||
datetime=datetime.datetime(2018, 6, 11, 10, 0, 0, 0),
|
||||
expires=datetime.datetime(2018, 6, 11, 10, 0, 0, 0)
|
||||
datetime=datetime.datetime(2018, 6, 11, 10, 0, 0, 0, tzinfo=UTC),
|
||||
expires=datetime.datetime(2018, 6, 11, 10, 0, 0, 0, tzinfo=UTC)
|
||||
)
|
||||
res = dict(TEST_CARTPOSITION_RES)
|
||||
res["id"] = cr.pk
|
||||
@@ -537,7 +537,7 @@ def test_cartpos_create_answer_validation(token_client, organizer, event, item,
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
assert resp.data == {
|
||||
'answers': [{'non_field_errors': ['Date has wrong format. Use one of these formats instead: YYYY[-MM[-DD]].']}]}
|
||||
'answers': [{'non_field_errors': ['Date has wrong format. Use one of these formats instead: YYYY-MM-DD.']}]}
|
||||
|
||||
question.type = Question.TYPE_DATETIME
|
||||
question.save()
|
||||
|
||||
@@ -76,8 +76,8 @@ def cart_position(event, item, variations):
|
||||
c = CartPosition.objects.create(
|
||||
event=event,
|
||||
item=item,
|
||||
datetime=datetime.now(),
|
||||
expires=datetime.now() + timedelta(days=1),
|
||||
datetime=testtime,
|
||||
expires=testtime + timedelta(days=1),
|
||||
variation=variations[0],
|
||||
price=Decimal("23"),
|
||||
cart_id="z3fsn8jyufm5kpk768q69gkbyr5f4h6w"
|
||||
|
||||
@@ -2181,7 +2181,7 @@ def test_order_create_answer_validation(token_client, organizer, event, item, qu
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
assert resp.data == {'positions': [{'answers': [
|
||||
{'non_field_errors': ['Date has wrong format. Use one of these formats instead: YYYY[-MM[-DD]].']}]}]}
|
||||
{'non_field_errors': ['Date has wrong format. Use one of these formats instead: YYYY-MM-DD.']}]}]}
|
||||
|
||||
question.type = Question.TYPE_DATETIME
|
||||
question.save()
|
||||
|
||||
@@ -1748,10 +1748,11 @@ class CachedFileTestCase(TestCase):
|
||||
cf.filename = "testfile.txt"
|
||||
cf.save()
|
||||
assert default_storage.exists(cf.file.name)
|
||||
n = cf.file.name
|
||||
with default_storage.open(cf.file.name, 'r') as f:
|
||||
assert f.read().strip() == "file_content"
|
||||
cf.delete()
|
||||
assert not default_storage.exists(cf.file.name)
|
||||
assert not default_storage.exists(n)
|
||||
|
||||
|
||||
class CheckinListTestCase(TestCase):
|
||||
|
||||
@@ -41,7 +41,7 @@ def test_expiry_days(event):
|
||||
event.settings.set('payment_term_weekdays', False)
|
||||
order = _create_order(event, email='dummy@example.org', positions=[],
|
||||
now_dt=today, payment_provider=FreeOrderProvider(event),
|
||||
locale='de')
|
||||
locale='de')[0]
|
||||
assert (order.expires - today).days == 5
|
||||
|
||||
|
||||
@@ -52,14 +52,14 @@ def test_expiry_weekdays(event):
|
||||
event.settings.set('payment_term_weekdays', True)
|
||||
order = _create_order(event, email='dummy@example.org', positions=[],
|
||||
now_dt=today, payment_provider=FreeOrderProvider(event),
|
||||
locale='de')
|
||||
locale='de')[0]
|
||||
assert (order.expires - today).days == 6
|
||||
assert order.expires.weekday() == 0
|
||||
|
||||
today = make_aware(datetime(2016, 9, 19, 15, 0, 0, 0))
|
||||
order = _create_order(event, email='dummy@example.org', positions=[],
|
||||
now_dt=today, payment_provider=FreeOrderProvider(event),
|
||||
locale='de')
|
||||
locale='de')[0]
|
||||
assert (order.expires - today).days == 7
|
||||
assert order.expires.weekday() == 0
|
||||
|
||||
@@ -72,12 +72,12 @@ def test_expiry_last(event):
|
||||
event.settings.set('payment_term_last', now() + timedelta(days=3))
|
||||
order = _create_order(event, email='dummy@example.org', positions=[],
|
||||
now_dt=today, payment_provider=FreeOrderProvider(event),
|
||||
locale='de')
|
||||
locale='de')[0]
|
||||
assert (order.expires - today).days == 3
|
||||
event.settings.set('payment_term_last', now() + timedelta(days=7))
|
||||
order = _create_order(event, email='dummy@example.org', positions=[],
|
||||
now_dt=today, payment_provider=FreeOrderProvider(event),
|
||||
locale='de')
|
||||
locale='de')[0]
|
||||
assert (order.expires - today).days == 5
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ def test_expiry_last_relative(event):
|
||||
))
|
||||
order = _create_order(event, email='dummy@example.org', positions=[],
|
||||
now_dt=today, payment_provider=FreeOrderProvider(event),
|
||||
locale='de')
|
||||
locale='de')[0]
|
||||
assert (order.expires - today).days == 3
|
||||
|
||||
|
||||
@@ -124,7 +124,7 @@ def test_expiry_last_relative_subevents(event):
|
||||
))
|
||||
order = _create_order(event, email='dummy@example.org', positions=[cp1, cp2],
|
||||
now_dt=today, payment_provider=FreeOrderProvider(event),
|
||||
locale='de')
|
||||
locale='de')[0]
|
||||
assert (order.expires - today).days == 6
|
||||
|
||||
|
||||
@@ -136,7 +136,7 @@ def test_expiry_dst(event):
|
||||
today = tz.localize(datetime(2016, 10, 29, 12, 0, 0)).astimezone(utc)
|
||||
order = _create_order(event, email='dummy@example.org', positions=[],
|
||||
now_dt=today, payment_provider=FreeOrderProvider(event),
|
||||
locale='de')
|
||||
locale='de')[0]
|
||||
localex = order.expires.astimezone(tz)
|
||||
assert (localex.hour, localex.minute) == (23, 59)
|
||||
|
||||
|
||||
@@ -363,7 +363,7 @@ class EventsTest(SoupTest):
|
||||
|
||||
def test_payment_settings_last_date_payment_after_presale_end(self):
|
||||
tr19 = self.event1.tax_rules.create(rate=Decimal('19.00'))
|
||||
self.event1.presale_end = datetime.datetime.now()
|
||||
self.event1.presale_end = now()
|
||||
self.event1.save(update_fields=['presale_end'])
|
||||
doc = self.post_doc('/control/event/%s/%s/settings/payment' % (self.orga1.slug, self.event1.slug), {
|
||||
'payment_term_days': '2',
|
||||
|
||||
@@ -11,7 +11,7 @@ from pretix.base.models import (
|
||||
Event, Item, Order, OrderFee, OrderPayment, OrderPosition, Organizer,
|
||||
Quota, Team, User,
|
||||
)
|
||||
from pretix.plugins.banktransfer.models import BankImportJob
|
||||
from pretix.plugins.banktransfer.models import BankImportJob, BankTransaction
|
||||
from pretix.plugins.banktransfer.tasks import process_banktransfers
|
||||
|
||||
|
||||
@@ -300,6 +300,19 @@ def test_wrong_event_organizer(env, orga_job):
|
||||
assert env[2].status == Order.STATUS_PENDING
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_keep_unmatched(env, orga_job):
|
||||
process_banktransfers(orga_job, [{
|
||||
'payer': 'Karla Kundin',
|
||||
'reference': 'No useful reference',
|
||||
'date': '2016-01-26',
|
||||
'amount': '23.00'
|
||||
}])
|
||||
job = BankImportJob.objects.last()
|
||||
t = job.transactions.last()
|
||||
assert t.state == BankTransaction.STATE_NOMATCH
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_import_very_long_csv_file(client, env):
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
|
||||
@@ -907,6 +907,24 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
||||
self.assertEqual(OrderPosition.objects.count(), 1)
|
||||
self.assertEqual(OrderPosition.objects.first().price, 42)
|
||||
|
||||
def test_free_order(self):
|
||||
self.ticket.free_price = True
|
||||
self.ticket.save()
|
||||
cr1 = CartPosition.objects.create(
|
||||
event=self.event, cart_id=self.session_key, item=self.ticket,
|
||||
price=0, expires=now() + timedelta(minutes=10)
|
||||
)
|
||||
self._set_session('payment', 'free')
|
||||
|
||||
response = self.client.post('/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug), follow=True)
|
||||
doc = BeautifulSoup(response.rendered_content, "lxml")
|
||||
self.assertEqual(len(doc.select(".thank-you")), 1)
|
||||
self.assertFalse(CartPosition.objects.filter(id=cr1.id).exists())
|
||||
self.assertEqual(Order.objects.count(), 1)
|
||||
self.assertEqual(OrderPosition.objects.count(), 1)
|
||||
self.assertEqual(OrderPosition.objects.first().price, 0)
|
||||
self.assertEqual(Order.objects.first().status, Order.STATUS_PAID)
|
||||
|
||||
def test_confirm_in_time(self):
|
||||
cr1 = CartPosition.objects.create(
|
||||
event=self.event, cart_id=self.session_key, item=self.ticket,
|
||||
@@ -920,6 +938,7 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
||||
self.assertFalse(CartPosition.objects.filter(id=cr1.id).exists())
|
||||
self.assertEqual(Order.objects.count(), 1)
|
||||
self.assertEqual(OrderPosition.objects.count(), 1)
|
||||
self.assertEqual(Order.objects.first().status, Order.STATUS_PENDING)
|
||||
|
||||
def test_subevent_confirm_expired_available(self):
|
||||
self.event.has_subevents = True
|
||||
|
||||
Reference in New Issue
Block a user