Compare commits

..

1 Commits

Author SHA1 Message Date
dependabot[bot]
b6b5897728 Update django-oauth-toolkit requirement from ==2.3.* to ==3.1.*
Updates the requirements on [django-oauth-toolkit](https://github.com/django-oauth/django-oauth-toolkit) to permit the latest version.
- [Release notes](https://github.com/django-oauth/django-oauth-toolkit/releases)
- [Changelog](https://github.com/django-oauth/django-oauth-toolkit/blob/master/CHANGELOG.md)
- [Commits](https://github.com/django-oauth/django-oauth-toolkit/compare/2.3.0...3.1.0)

---
updated-dependencies:
- dependency-name: django-oauth-toolkit
  dependency-version: 3.1.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-03 18:19:07 +00:00
42 changed files with 228 additions and 465 deletions

View File

@@ -48,7 +48,7 @@ dependencies = [
"django-libsass==0.9",
"django-localflavor==5.0",
"django-markup",
"django-oauth-toolkit==2.3.*",
"django-oauth-toolkit==3.1.*",
"django-otp==1.6.*",
"django-phonenumber-field==7.3.*",
"django-redis==6.0.*",
@@ -91,7 +91,7 @@ dependencies = [
"redis==6.4.*",
"reportlab==4.4.*",
"requests==2.32.*",
"sentry-sdk==2.40.*",
"sentry-sdk==2.38.*",
"sepaxml==2.6.*",
"stripe==7.9.*",
"text-unidecode==1.*",

View File

@@ -764,13 +764,7 @@ class EventOrderViewSet(OrderViewSetMixin, viewsets.ModelViewSet):
) and not order.invoices.last()
invoice = None
if gen_invoice:
try:
invoice = generate_invoice(order, trigger_pdf=True)
except Exception as e:
logger.exception("Could not generate invoice.")
order.log_action("pretix.event.order.invoice.failed", data={
"exception": str(e)
})
invoice = generate_invoice(order, trigger_pdf=True)
# Refresh serializer only after running signals
prefetch_related_objects([order], self._positions_prefetch(request))

View File

@@ -439,12 +439,8 @@ def register_default_webhook_events(sender, **kwargs):
def notify_webhooks(logentry_ids: list):
if not isinstance(logentry_ids, list):
logentry_ids = [logentry_ids]
qs = LogEntry.all.select_related(
'event', 'event__organizer', 'organizer'
).order_by(
'action_type', 'organizer_id', 'event_id',
).filter(id__in=logentry_ids)
_org, _at, _ev, webhooks = None, None, None, None
qs = LogEntry.all.select_related('event', 'event__organizer', 'organizer').filter(id__in=logentry_ids)
_org, _at, webhooks = None, None, None
for logentry in qs:
if not logentry.organizer:
break # We need to know the organizer
@@ -454,7 +450,7 @@ def notify_webhooks(logentry_ids: list):
if not notification_type:
break # Ignore, no webhooks for this event type
if _org != logentry.organizer or _at != logentry.action_type or _ev != logentry.event_id or webhooks is None:
if _org != logentry.organizer or _at != logentry.action_type or webhooks is None:
_org = logentry.organizer
_at = logentry.action_type

View File

@@ -391,7 +391,7 @@ class OutboundSyncProvider:
def sync_order(self, order):
if not self.should_sync_order(order):
logger.debug("Skipping order %r", order)
return {}
return
logger.debug("Syncing order %r", order)
positions = list(

View File

@@ -105,18 +105,6 @@ class BaseExporter:
"""
return False
@property
def repeatable_read(self) -> bool:
"""
If ``True``, this exporter will be run in a REPEATABLE READ transaction. This ensures consistent results for
all queries performed by the exporter, but creates a performance burden on the database server. We recommend to
disable this for exporters that take very long to run and do not rely on this behavior, such as export of lists
to CSV files.
Defaults to ``True`` for now, but default may change in future versions.
"""
return True
@property
def identifier(self) -> str:
"""

View File

@@ -125,7 +125,6 @@ class InvoiceExporter(InvoiceExporterMixin, BaseExporter):
identifier = 'invoices'
verbose_name = _('All invoices')
description = _('Download all invoices created by the system as a ZIP file of PDF files.')
repeatable_read = False
def render(self, form_data: dict, output_file=None):
qs = self.invoices_queryset(form_data).filter(shredded=False)
@@ -181,7 +180,6 @@ class InvoiceDataExporter(InvoiceExporterMixin, MultiSheetListExporter):
'includes two sheets, one with a line for every invoice, and one with a line for every position of '
'every invoice.')
featured = True
repeatable_read = False
@property
def additional_form_fields(self):

View File

@@ -90,7 +90,6 @@ class OrderListExporter(MultiSheetListExporter):
'with a line for every order, one with a line for every order position, and one with '
'a line for every additional fee charged in an order.')
featured = True
repeatable_read = False
@cached_property
def providers(self):
@@ -843,7 +842,6 @@ class TransactionListExporter(ListExporter):
description = gettext_lazy('Download a spreadsheet of all substantial changes to orders, i.e. all changes to '
'products, prices or tax rates. The information is only accurate for changes made with '
'pretix versions released after October 2021.')
repeatable_read = False
@cached_property
def providers(self):
@@ -1022,7 +1020,6 @@ class PaymentListExporter(ListExporter):
category = pgettext_lazy('export_category', 'Order data')
description = gettext_lazy('Download a spreadsheet of all payments or refunds of every order.')
featured = True
repeatable_read = False
@property
def additional_form_fields(self):
@@ -1162,7 +1159,7 @@ class QuotaListExporter(ListExporter):
yield headers
quotas = list(self.event.quotas.select_related('subevent'))
qa = QuotaAvailability(full_results=True, allow_repeatable_read=True)
qa = QuotaAvailability(full_results=True)
qa.queue(*quotas)
qa.compute()
@@ -1203,7 +1200,6 @@ class GiftcardTransactionListExporter(OrganizerLevelExportMixin, ListExporter):
organizer_required_permission = 'can_manage_gift_cards'
category = pgettext_lazy('export_category', 'Gift cards')
description = gettext_lazy('Download a spreadsheet of all gift card transactions.')
repeatable_read = False
@property
def additional_form_fields(self):
@@ -1262,7 +1258,6 @@ class GiftcardRedemptionListExporter(ListExporter):
verbose_name = gettext_lazy('Gift card redemptions')
category = pgettext_lazy('export_category', 'Order data')
description = gettext_lazy('Download a spreadsheet of all payments or refunds that involve gift cards.')
repeatable_read = False
def iterate_list(self, form_data):
payments = OrderPayment.objects.filter(

View File

@@ -34,7 +34,6 @@ class ReusableMediaExporter(OrganizerLevelExportMixin, ListExporter):
verbose_name = _('Reusable media')
category = pgettext_lazy('export_category', 'Reusable media')
description = _('Download a spread sheet with the data of all reusable medias on your account.')
repeatable_read = False
def iterate_list(self, form_data):
media = ReusableMedium.objects.filter(

View File

@@ -41,7 +41,6 @@ class WaitingListExporter(ListExporter):
verbose_name = _('Waiting list')
category = pgettext_lazy('export_category', 'Waiting list')
description = _('Download a spread sheet with all your waiting list data.')
repeatable_read = False
# map selected status to label and queryset-filter
status_filters = [

View File

@@ -1840,10 +1840,6 @@ class OrderPayment(models.Model):
))
return False
if locked_instance.state == OrderPayment.PAYMENT_STATE_CANCELED:
# Never send mails when the payment was already canceled intentionally
send_mail = False
if isinstance(info, str):
locked_instance.info = info
elif info:
@@ -1859,10 +1855,6 @@ class OrderPayment(models.Model):
'data': log_data,
}, user=user, auth=auth)
if self.order.status in (Order.STATUS_PAID, Order.STATUS_CANCELED, Order.STATUS_EXPIRED):
# No reason to send mail, as the payment is no longer really expected
send_mail = False
if send_mail:
with language(self.order.locale, self.order.event.settings.region):
email_subject = self.order.event.settings.mail_subject_order_payment_failed
@@ -1969,20 +1961,14 @@ class OrderPayment(models.Model):
self.order.invoice_dirty
)
if gen_invoice:
try:
if invoices:
last_i = self.order.invoices.filter(is_cancellation=False).last()
if not last_i.canceled:
generate_cancellation(last_i)
invoice = generate_invoice(
self.order,
trigger_pdf=not send_mail or not self.order.event.settings.invoice_email_attachment
)
except Exception as e:
logger.exception("Could not generate invoice.")
self.order.log_action("pretix.event.order.invoice.failed", data={
"exception": str(e)
})
if invoices:
last_i = self.order.invoices.filter(is_cancellation=False).last()
if not last_i.canceled:
generate_cancellation(last_i)
invoice = generate_invoice(
self.order,
trigger_pdf=not send_mail or not self.order.event.settings.invoice_email_attachment
)
transmit_invoice_task = invoice_transmission_separately(invoice)
transmit_invoice_mail = not transmit_invoice_task and self.order.event.settings.invoice_email_attachment and self.order.email

View File

@@ -49,7 +49,7 @@ from pretix.base.signals import (
periodic_task, register_data_exporters, register_multievent_data_exporters,
)
from pretix.celery_app import app
from pretix.helpers import OF_SELF, repeatable_reads_transaction
from pretix.helpers import OF_SELF
from pretix.helpers.urls import build_absolute_uri
logger = logging.getLogger(__name__)
@@ -80,12 +80,7 @@ def export(self, event: Event, fileid: str, provider: str, form_data: Dict[str,
continue
ex = response(event, event.organizer, set_progress)
if ex.identifier == provider:
if ex.repeatable_read:
with repeatable_reads_transaction():
d = ex.render(form_data)
else:
d = ex.render(form_data)
d = ex.render(form_data)
if d is None:
raise ExportError(
gettext('Your export did not contain any data.')
@@ -156,11 +151,7 @@ def multiexport(self, organizer: Organizer, user: User, device: int, token: int,
gettext('You do not have sufficient permission to perform this export.')
)
if ex.repeatable_read:
with repeatable_reads_transaction():
d = ex.render(form_data)
else:
d = ex.render(form_data)
d = ex.render(form_data)
if d is None:
raise ExportError(
gettext('Your export did not contain any data.')
@@ -218,11 +209,7 @@ def _run_scheduled_export(schedule, context: Union[Event, Organizer], exporter,
try:
if not exporter:
raise ExportError("Export type not found.")
if exporter.repeatable_read:
with repeatable_reads_transaction():
d = exporter.render(schedule.export_form_data)
else:
d = exporter.render(schedule.export_form_data)
d = exporter.render(schedule.export_form_data)
if d is None:
raise ExportEmptyError(
gettext('Your export did not contain any data.')

View File

@@ -671,7 +671,6 @@ def send_invoices_to_organizer(sender, **kwargs):
event=i.event,
invoices=[i],
auto_email=True,
plain_text_only=True,
)
i.sent_to_organizer = True
else:

View File

@@ -19,7 +19,6 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
import logging
from decimal import Decimal
from typing import List
@@ -34,8 +33,8 @@ from pretix.base.modelimport import DataImportError, ImportColumn, parse_csv
from pretix.base.modelimport_orders import get_order_import_columns
from pretix.base.modelimport_vouchers import get_voucher_import_columns
from pretix.base.models import (
CachedFile, Event, InvoiceAddress, LogEntry, Order, OrderPayment,
OrderPosition, User, Voucher,
CachedFile, Event, InvoiceAddress, Order, OrderPayment, OrderPosition,
User, Voucher,
)
from pretix.base.models.orders import Transaction
from pretix.base.services.invoices import generate_invoice, invoice_qualified
@@ -44,8 +43,6 @@ from pretix.base.services.tasks import ProfiledEventTask
from pretix.base.signals import order_paid, order_placed
from pretix.celery_app import app
logger = logging.getLogger(__name__)
def _validate(cf: CachedFile, charset: str, cols: List[ImportColumn], settings: dict):
try:
@@ -178,7 +175,6 @@ def import_orders(event: Event, fileid: str, settings: dict, locale: str, user,
raise DataImportError(_('The seat you selected has already been taken. Please select a different seat.'))
save_transactions = []
save_logentries = []
for o in orders:
o.total = sum([c.price for c in o._positions]) # currently no support for fees
if o.total == Decimal('0.00'):
@@ -215,15 +211,13 @@ def import_orders(event: Event, fileid: str, settings: dict, locale: str, user,
o._address.save()
for c in cols:
c.save(o)
save_logentries.append(o.log_action(
o.log_action(
'pretix.event.order.placed',
user=user,
data={'source': 'import'},
save=False,
))
data={'source': 'import'}
)
save_transactions += o.create_transactions(is_new=True, fees=[], positions=o._positions, save=False)
Transaction.objects.bulk_create(save_transactions)
LogEntry.bulk_create_and_postprocess(save_logentries)
for o in orders:
with language(o.locale, event.settings.region):
@@ -236,13 +230,7 @@ def import_orders(event: Event, fileid: str, settings: dict, locale: str, user,
(event.settings.get('invoice_generate') == 'paid' and o.status == Order.STATUS_PAID)
) and not o.invoices.last()
if gen_invoice:
try:
generate_invoice(o, trigger_pdf=True)
except Exception as e:
logger.exception("Could not generate invoice.")
o.log_action("pretix.event.order.invoice.failed", data={
"exception": str(e)
})
generate_invoice(o, trigger_pdf=True)
except DataImportError:
raise ValidationError(_('We were not able to process your request completely as the server was too busy. '
'Please try again.'))
@@ -298,16 +286,13 @@ def import_vouchers(event: Event, fileid: str, settings: dict, locale: str, user
raise DataImportError(
_('The seat you selected has already been taken. Please select a different seat.'))
save_logentries = []
for v in vouchers:
v.save()
save_logentries.append(v.log_action(
v.log_action(
'pretix.voucher.added',
user=user,
data={'source': 'import'},
save=False,
))
data={'source': 'import'}
)
for c in cols:
c.save(v)
LogEntry.bulk_create_and_postprocess(save_logentries)
cf.delete()

View File

@@ -41,11 +41,7 @@ def notify(logentry_ids: list):
if not isinstance(logentry_ids, list):
logentry_ids = [logentry_ids]
qs = LogEntry.all.select_related(
'event', 'event__organizer'
).order_by(
'action_type', 'event_id',
).filter(id__in=logentry_ids)
qs = LogEntry.all.select_related('event', 'event__organizer').filter(id__in=logentry_ids)
_event, _at, notify_specific, notify_global = None, None, None, None
for logentry in qs:

View File

@@ -264,13 +264,7 @@ def reactivate_order(order: Order, force: bool=False, user: User=None, auth=None
num_invoices = order.invoices.filter(is_cancellation=False).count()
if num_invoices > 0 and order.invoices.filter(is_cancellation=True).count() >= num_invoices and invoice_qualified(order):
try:
generate_invoice(order)
except Exception as e:
logger.exception("Could not generate invoice.")
order.log_action("pretix.event.order.invoice.failed", data={
"exception": str(e)
})
generate_invoice(order)
def extend_order(order: Order, new_date: datetime, force: bool=False, valid_if_pending: bool=None, user: User=None, auth=None):
@@ -318,13 +312,7 @@ def extend_order(order: Order, new_date: datetime, force: bool=False, valid_if_p
if was_expired:
num_invoices = order.invoices.filter(is_cancellation=False).count()
if num_invoices > 0 and order.invoices.filter(is_cancellation=True).count() >= num_invoices and invoice_qualified(order):
try:
generate_invoice(order)
except Exception as e:
logger.exception("Could not generate invoice.")
order.log_action("pretix.event.order.invoice.failed", data={
"exception": str(e)
})
generate_invoice(order)
order.create_transactions()
with transaction.atomic():
@@ -409,19 +397,13 @@ def approve_order(order, user=None, send_mail: bool=True, auth=None, force=False
if order.event.settings.get('invoice_generate') == 'True' and invoice_qualified(order):
if not invoice:
try:
invoice = generate_invoice(
order,
# send_mail will trigger PDF generation later
trigger_pdf=not transmit_invoice_mail
)
if transmit_invoice_task:
transmit_invoice.apply_async(args=(order.event_id, invoice.pk, False))
except Exception as e:
logger.exception("Could not generate invoice.")
order.log_action("pretix.event.order.invoice.failed", data={
"exception": str(e)
})
invoice = generate_invoice(
order,
# send_mail will trigger PDF generation later
trigger_pdf=not transmit_invoice_mail
)
if transmit_invoice_task:
transmit_invoice.apply_async(args=(order.event_id, invoice.pk, False))
if send_mail:
with language(order.locale, order.event.settings.region):
@@ -626,13 +608,7 @@ def _cancel_order(order, user=None, send_mail: bool=True, api_token=None, device
order.save(update_fields=['status', 'cancellation_date', 'total'])
if cancel_invoice and i:
try:
invoices.append(generate_invoice(order))
except Exception as e:
logger.exception("Could not generate invoice.")
order.log_action("pretix.event.order.invoice.failed", data={
"exception": str(e)
})
invoices.append(generate_invoice(order))
else:
order.status = Order.STATUS_CANCELED
order.cancellation_date = now()
@@ -1330,19 +1306,13 @@ def _perform_order(event: Event, payment_requests: List[dict], position_ids: Lis
)
)
if invoice_required:
try:
invoice = generate_invoice(
order,
# send_mail will trigger PDF generation later
trigger_pdf=not transmit_invoice_mail
)
if transmit_invoice_task:
transmit_invoice.apply_async(args=(event.pk, invoice.pk, False))
except Exception as e:
logger.exception("Could not generate invoice.")
order.log_action("pretix.event.order.invoice.failed", data={
"exception": str(e)
})
invoice = generate_invoice(
order,
# send_mail will trigger PDF generation later
trigger_pdf=not transmit_invoice_mail
)
if transmit_invoice_task:
transmit_invoice.apply_async(args=(event.pk, invoice.pk, False))
if order.email:
if order.require_approval:
@@ -2731,13 +2701,7 @@ class OrderChangeManager:
)
if split_order.total != Decimal('0.00') and self.order.invoices.filter(is_cancellation=False).last():
try:
generate_invoice(split_order)
except Exception as e:
logger.exception("Could not generate invoice.")
split_order.log_action("pretix.event.order.invoice.failed", data={
"exception": str(e)
})
generate_invoice(split_order)
order_split.send(sender=self.order.event, original=self.order, split_order=split_order)
return split_order
@@ -2848,27 +2812,15 @@ class OrderChangeManager:
if order_now_qualified:
if invoice_should_be_generated_now:
try:
if i and not i.canceled:
self._invoices.append(generate_cancellation(i))
self._invoices.append(generate_invoice(self.order))
except Exception as e:
logger.exception("Could not generate invoice.")
self.order.log_action("pretix.event.order.invoice.failed", data={
"exception": str(e)
})
if i and not i.canceled:
self._invoices.append(generate_cancellation(i))
self._invoices.append(generate_invoice(self.order))
elif invoice_should_be_generated_later:
self.order.invoice_dirty = True
self.order.save(update_fields=["invoice_dirty"])
else:
try:
if i and not i.canceled:
self._invoices.append(generate_cancellation(i))
except Exception as e:
logger.exception("Could not generate invoice.")
self.order.log_action("pretix.event.order.invoice.failed", data={
"exception": str(e)
})
if i and not i.canceled:
self._invoices.append(generate_cancellation(i))
def _check_complete_cancel(self):
current = self.order.positions.count()
@@ -3294,14 +3246,8 @@ def change_payment_provider(order: Order, payment_provider, amount=None, new_pay
has_active_invoice = i and not i.canceled
if has_active_invoice and order.total != oldtotal:
try:
generate_cancellation(i)
generate_invoice(order)
except Exception as e:
logger.exception("Could not generate invoice.")
order.log_action("pretix.event.order.invoice.failed", data={
"exception": str(e)
})
generate_cancellation(i)
generate_invoice(order)
new_invoice_created = True
elif (not has_active_invoice or order.invoice_dirty) and invoice_qualified(order):
@@ -3309,19 +3255,13 @@ def change_payment_provider(order: Order, payment_provider, amount=None, new_pay
order.event.settings.get('invoice_generate') == 'paid' and
new_payment.payment_provider.requires_invoice_immediately
):
try:
if has_active_invoice:
generate_cancellation(i)
i = generate_invoice(order)
new_invoice_created = True
order.log_action('pretix.event.order.invoice.generated', data={
'invoice': i.pk
})
except Exception as e:
logger.exception("Could not generate invoice.")
order.log_action("pretix.event.order.invoice.failed", data={
"exception": str(e)
})
if has_active_invoice:
generate_cancellation(i)
i = generate_invoice(order)
new_invoice_created = True
order.log_action('pretix.event.order.invoice.generated', data={
'invoice': i.pk
})
order.create_transactions()
return old_fee, new_fee, fee, new_payment, new_invoice_created

View File

@@ -26,7 +26,7 @@ from itertools import zip_longest
import django_redis
from django.conf import settings
from django.db import connection, models
from django.db import models
from django.db.models import (
Case, Count, F, Func, Max, OuterRef, Q, Subquery, Sum, Value, When,
prefetch_related_objects,
@@ -64,8 +64,7 @@ class QuotaAvailability:
* count_cart (dict mapping quotas to ints)
"""
def __init__(self, count_waitinglist=True, ignore_closed=False, full_results=False, early_out=True,
allow_repeatable_read=False):
def __init__(self, count_waitinglist=True, ignore_closed=False, full_results=False, early_out=True):
"""
Initialize a new quota availability calculator
@@ -87,8 +86,6 @@ class QuotaAvailability:
keep the database-level quota cache up to date so backend overviews render quickly. If you
do not care about keeping the cache up to date, you can set this to ``False`` for further
performance improvements.
:param allow_repeatable_read: Allow to run this even in REPEATABLE READ mode, generally not advised.
"""
self._queue = []
self._count_waitinglist = count_waitinglist
@@ -98,7 +95,6 @@ class QuotaAvailability:
self._var_to_quotas = defaultdict(set)
self._early_out = early_out
self._quota_objects = {}
self._allow_repeatable_read = allow_repeatable_read
self.results = {}
self.count_paid_orders = defaultdict(int)
self.count_pending_orders = defaultdict(int)
@@ -123,10 +119,6 @@ class QuotaAvailability:
Compute the queued quotas. If ``allow_cache`` is set, results may also be taken from a cache that might
be a few minutes outdated. In this case, you may not rely on the results in the ``count_*`` properties.
"""
if not self._allow_repeatable_read and getattr(connection, "tx_in_repeatable_read", False):
raise ValueError("You cannot compute quotas in REPEATABLE READ mode unless you explicitly opted in to "
"do so.")
now_dt = now_dt or now()
quota_ids_set = {q.id for q in self._queue}
if not quota_ids_set:

View File

@@ -455,7 +455,7 @@ class OrderDataSyncSuccessLogEntryType(OrderDataSyncLogEntryType):
links.append(", ".join(
prov.get_external_link_html(logentry.event, obj['external_link_href'], obj['external_link_display_name'])
for obj in objs
if obj and obj.get('external_link_href') and obj.get('external_link_display_name')
if obj and 'external_link_href' in obj and 'external_link_display_name' in obj
))
return mark_safe(escape(super().display(logentry, data)) + "".join("<p>" + link + "</p>" for link in links))
@@ -522,7 +522,6 @@ def pretixcontrol_orderposition_blocked_display(sender: Event, orderposition, bl
'pretix.event.order.customer.changed': _('The customer account has been changed.'),
'pretix.event.order.locale.changed': _('The order locale has been changed.'),
'pretix.event.order.invoice.generated': _('The invoice has been generated.'),
'pretix.event.order.invoice.failed': _('The invoice could not be generated.'),
'pretix.event.order.invoice.regenerated': _('The invoice has been regenerated.'),
'pretix.event.order.invoice.reissued': _('The invoice has been reissued.'),
'pretix.event.order.invoice.sent': _('The invoice {full_invoice_no} has been sent.'),

View File

@@ -78,8 +78,8 @@ class ControlSyncJob(OrderView):
prov, meta = datasync_providers.get(active_in=self.request.event, identifier=provider)
if self.request.POST.get("queue_sync") == "true":
prov.enqueue_order(self.order, 'user', immediate=True)
messages.success(self.request, _('The sync job has been set to run as soon as possible.'))
prov.enqueue_order(self.order, 'user')
messages.success(self.request, _('The sync job has been enqueued and will run in the next minutes.'))
elif self.request.POST.get("cancel_job"):
with transaction.atomic():
try:

View File

@@ -21,8 +21,7 @@
#
import contextlib
from django.conf import settings
from django.core.exceptions import FieldDoesNotExist, ImproperlyConfigured
from django.core.exceptions import FieldDoesNotExist
from django.db import connection, transaction
from django.db.models import (
Aggregate, Expression, F, Field, Lookup, OrderBy, Value,
@@ -63,43 +62,6 @@ def casual_reads():
yield
@contextlib.contextmanager
def repeatable_reads_transaction():
"""
pretix, and Django, operate in the transaction isolation level READ COMMITTED by default. This is not a strong level
of isolation, but we NEED to use it: Otherwise e.g. our quota logic breaks, because we need to be able to get the
*current* number of tickets sold at any time in a transaction, not the number of tickets sold *before* our transaction
started.
However, this isolation mode has drawbacks, for example during reporting. When a user retrieves a report from the
system, it should return numbers that are consistent with each other. However, if the report makes multiple SQL
queries in READ COMMITTED mode, the results might be different for each query, causing numbers to be inconsistent
with each other.
This context manager creates a transaction that is running in REPEATABLE READ mode to avoid this problem.
**You should only make read-only queries during this transaction and not rely on quota calculations.**
"""
is_under_test = 'tests.testdummy' in settings.INSTALLED_APPS
try:
with transaction.atomic(durable=not is_under_test):
if not is_under_test:
# We're not running this in tests, where we can basically not use this since the test runner does its
# own transaction logic for efficiency
with connection.cursor() as cursor:
if 'postgresql' in settings.DATABASES['default']['ENGINE']:
cursor.execute('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;')
elif 'sqlite' in settings.DATABASES['default']['ENGINE']:
pass # noop
else:
raise ImproperlyConfigured("Cannot set transaction isolation mode on this database backend")
connection.tx_in_repeatable_read = True
yield
finally:
connection.tx_in_repeatable_read = False
class GroupConcat(Aggregate):
function = 'group_concat'
template = '%(function)s(%(distinct)s%(field)s, "%(separator)s")'

View File

@@ -76,11 +76,11 @@ def daterange(df, dt, as_html=False):
return format_html(base_format, _date(df, "j F"), until, _date(dt, "j F Y"))
elif lng.startswith("en"):
if df.year == dt.year and df.month == dt.month and df.day == dt.day:
return format_html(base_format, _date(df, "D, N j, Y"))
return format_html(base_format, _date(df, "D, N jS, Y"))
elif df.year == dt.year and df.month == dt.month:
return format_html(base_format, _date(df, "N j"), until, _date(dt, "j, Y"))
return format_html(base_format, _date(df, "N jS"), until, _date(dt, "jS, Y"))
elif df.year == dt.year:
return format_html(base_format, _date(df, "N j"), until, _date(dt, "N j, Y"))
return format_html(base_format, _date(df, "N jS"), until, _date(dt, "N jS, Y"))
elif lng.startswith("es"):
if df.year == dt.year and df.month == dt.month and df.day == dt.day:
return format_html(base_format, _date(df, "DATE_FORMAT"))

View File

@@ -38,5 +38,5 @@ SHORT_DATE_FORMAT = 'Y-m-d'
SHORT_DATETIME_FORMAT = 'Y-m-d H:i'
TIME_FORMAT = 'H:i'
WEEK_FORMAT = '\\W W, o'
WEEK_DAY_FORMAT = 'D, M j'
WEEK_DAY_FORMAT = 'D, M jS'
SHORT_MONTH_DAY_FORMAT = 'd.m.'

View File

@@ -38,7 +38,7 @@ SHORT_DATE_FORMAT = "Y-m-d"
SHORT_DATETIME_FORMAT = 'Y-m-d P'
TIME_FORMAT = 'P'
WEEK_FORMAT = '\\W W, o'
WEEK_DAY_FORMAT = 'D, M j'
WEEK_DAY_FORMAT = 'D, M jS'
SHORT_MONTH_DAY_FORMAT = 'm/d'
DATE_INPUT_FORMATS = [

View File

@@ -25,7 +25,7 @@ SHORT_DATE_FORMAT = 'm/d/Y'
SHORT_DATETIME_FORMAT = 'm/d/Y P'
TIME_FORMAT = 'P'
WEEK_FORMAT = '\\W W, o'
WEEK_DAY_FORMAT = 'D, M j'
WEEK_DAY_FORMAT = 'D, M jS'
SHORT_MONTH_DAY_FORMAT = 'm/d'
DATE_INPUT_FORMATS = [

View File

@@ -5,8 +5,8 @@ msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-26 11:16+0000\n"
"PO-Revision-Date: 2025-10-03 01:00+0000\n"
"Last-Translator: Mira <weller@rami.io>\n"
"PO-Revision-Date: 2025-09-26 13:02+0000\n"
"Last-Translator: Raphael Michel <michel@rami.io>\n"
"Language-Team: German <https://translate.pretix.eu/projects/pretix/pretix/de/"
">\n"
"Language: de\n"
@@ -28214,7 +28214,7 @@ msgstr ""
#: pretix/control/views/modelimport.py:174
msgid "The import was successful."
msgstr "Der Import war erfolgreich."
msgstr "Die Import war erfolgreich."
#: pretix/control/views/modelimport.py:186
msgid "We've been unable to parse the uploaded file as a CSV file."

View File

@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-26 11:16+0000\n"
"PO-Revision-Date: 2025-10-03 01:00+0000\n"
"Last-Translator: Mira <weller@rami.io>\n"
"PO-Revision-Date: 2025-09-26 13:02+0000\n"
"Last-Translator: Raphael Michel <michel@rami.io>\n"
"Language-Team: German (informal) <https://translate.pretix.eu/projects/"
"pretix/pretix/de_Informal/>\n"
"Language: de_Informal\n"
@@ -28172,7 +28172,7 @@ msgstr ""
#: pretix/control/views/modelimport.py:174
msgid "The import was successful."
msgstr "Der Import war erfolgreich."
msgstr "Die Import war erfolgreich."
#: pretix/control/views/modelimport.py:186
msgid "We've been unable to parse the uploaded file as a CSV file."

View File

@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-26 11:16+0000\n"
"PO-Revision-Date: 2025-10-04 10:10+0000\n"
"Last-Translator: Sebastian Bożek <sebastian@log-mar.pl>\n"
"PO-Revision-Date: 2025-05-04 16:00+0000\n"
"Last-Translator: Pekka Sarkola <pekka.sarkola@gispo.fi>\n"
"Language-Team: Finnish <https://translate.pretix.eu/projects/pretix/pretix/"
"fi/>\n"
"Language: fi\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.13.3\n"
"X-Generator: Weblate 5.11.1\n"
#: pretix/_base_settings.py:87
msgid "English"
@@ -113,7 +113,7 @@ msgstr "norja (kirjakieli)"
#: pretix/_base_settings.py:110
msgid "Polish"
msgstr "Puola"
msgstr "puola"
#: pretix/_base_settings.py:111
msgid "Portuguese (Portugal)"

View File

@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-08-19 16:35+0000\n"
"PO-Revision-Date: 2025-10-03 20:00+0000\n"
"Last-Translator: Yasunobu YesNo Kawaguchi <kawaguti@gmail.com>\n"
"PO-Revision-Date: 2025-09-30 16:00+0000\n"
"Last-Translator: Hijiri Umemoto <hijiri@umemoto.org>\n"
"Language-Team: Japanese <https://translate.pretix.eu/projects/pretix/pretix/"
"ja/>\n"
"Language: ja\n"
@@ -30760,7 +30760,7 @@ msgstr "このイベントのメール設定でチケットの添付が無効に
#: pretix/plugins/sendmail/forms.py:234 pretix/plugins/sendmail/forms.py:386
#: pretix/plugins/sendmail/views.py:267
msgid "payment pending but already confirmed"
msgstr "支払い保留中だが確認済み"
msgstr "支払い保留中ですが、すでに確認済み"
#: pretix/plugins/sendmail/forms.py:235 pretix/plugins/sendmail/forms.py:388
#: pretix/plugins/sendmail/views.py:268

View File

@@ -7,7 +7,7 @@ msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-26 11:16+0000\n"
"PO-Revision-Date: 2025-10-04 19:00+0000\n"
"PO-Revision-Date: 2025-09-30 01:00+0000\n"
"Last-Translator: Jan Van Haver <jan.van.haver@gmail.com>\n"
"Language-Team: Dutch <https://translate.pretix.eu/projects/pretix/pretix/nl/>"
"\n"
@@ -808,8 +808,6 @@ msgid ""
"Field \"{field_name}\" does not exist. Please check your {provider_name} "
"settings."
msgstr ""
"Het veld \"{field_name}\" bestaat niet. Controleer uw {provider_name} "
"instellingen."
#: pretix/base/datasync/datasync.py:262
#, python-brace-format
@@ -817,8 +815,6 @@ msgid ""
"Field \"{field_name}\" requires {required_input}, but only got "
"{available_inputs}. Please check your {provider_name} settings."
msgstr ""
"Het veld \"{field_name}\" vereist {required_input}, maar heeft alleen "
"{available_inputs}. Controleer uw {provider_name} instellingen."
#: pretix/base/datasync/datasync.py:273
#, python-brace-format
@@ -826,16 +822,18 @@ msgid ""
"Please update value mapping for field \"{field_name}\" - option \"{val}\" "
"not assigned"
msgstr ""
"Werk de mapping van de waarden bij voor veld \"{field_name}\" - optie "
"\"{val}\" is niet toegewezen"
#: pretix/base/datasync/sourcefields.py:128
#, fuzzy
#| msgid "Order positions"
msgid "Order position details"
msgstr "Details bestelde producten"
msgstr "Bestelde producten"
#: pretix/base/datasync/sourcefields.py:129
#, fuzzy
#| msgid "Attendee email"
msgid "Attendee details"
msgstr "Details van aanwezige"
msgstr "E-mailadres van aanwezige"
#: pretix/base/datasync/sourcefields.py:130 pretix/base/exporters/answers.py:66
#: pretix/base/models/items.py:1767 pretix/control/navigation.py:172
@@ -845,8 +843,10 @@ msgid "Questions"
msgstr "Vragen"
#: pretix/base/datasync/sourcefields.py:131
#, fuzzy
#| msgid "Product data"
msgid "Product details"
msgstr "Productdetails"
msgstr "Productgegevens"
#: pretix/base/datasync/sourcefields.py:132
#: pretix/control/templates/pretixcontrol/event/settings.html:280
@@ -1038,12 +1038,16 @@ msgid "Product ID"
msgstr "Product ID"
#: pretix/base/datasync/sourcefields.py:419
#, fuzzy
#| msgid "Non-admission product"
msgid "Product is admission product"
msgstr "Product is een toegangsbewijs"
msgstr "Geen toegangsbewijs"
#: pretix/base/datasync/sourcefields.py:428
#, fuzzy
#| msgid "Event short name"
msgid "Event short form"
msgstr "Kort formulier evenement"
msgstr "Korte naam evenement"
#: pretix/base/datasync/sourcefields.py:437 pretix/base/exporters/events.py:57
#: pretix/base/exporters/orderlist.py:262
@@ -1086,8 +1090,10 @@ msgid "Order code and position number"
msgstr "Bestelcode en plaatsnummer"
#: pretix/base/datasync/sourcefields.py:482
#, fuzzy
#| msgid "Ticket page"
msgid "Ticket price"
msgstr "Ticketprijs"
msgstr "Ticketpagina"
#: pretix/base/datasync/sourcefields.py:491 pretix/base/notifications.py:204
#: pretix/control/forms/filter.py:216 pretix/control/forms/modelimport.py:85
@@ -1095,16 +1101,22 @@ msgid "Order status"
msgstr "Bestelstatus"
#: pretix/base/datasync/sourcefields.py:500
#, fuzzy
#| msgid "Device status"
msgid "Ticket status"
msgstr "Ticketstatus"
msgstr "Apparaatstatus"
#: pretix/base/datasync/sourcefields.py:509
#, fuzzy
#| msgid "Purchase date and time"
msgid "Order date and time"
msgstr "Besteldatum en -tijd"
msgstr "Aankoopdatum en -tijd"
#: pretix/base/datasync/sourcefields.py:518
#, fuzzy
#| msgid "Printing date and time"
msgid "Payment date and time"
msgstr "Betaaldatum en -tijd"
msgstr "Printdatum en -tijd"
#: pretix/base/datasync/sourcefields.py:527
#: pretix/base/exporters/orderlist.py:271
@@ -1115,17 +1127,23 @@ msgid "Order locale"
msgstr "Taal van bestelling"
#: pretix/base/datasync/sourcefields.py:536
#, fuzzy
#| msgid "Order position"
msgid "Order position ID"
msgstr "ID besteld product"
msgstr "Besteld product"
#: pretix/base/datasync/sourcefields.py:545
#: pretix/base/exporters/orderlist.py:291
#, fuzzy
#| msgid "Order time"
msgid "Order link"
msgstr "Bestellink"
msgstr "Besteltijd"
#: pretix/base/datasync/sourcefields.py:560
#, fuzzy
#| msgid "Ticket block"
msgid "Ticket link"
msgstr "Ticketlink"
msgstr "Ticketblok"
#: pretix/base/datasync/sourcefields.py:578
#, fuzzy, python-brace-format

View File

@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-26 11:16+0000\n"
"PO-Revision-Date: 2025-10-04 19:00+0000\n"
"Last-Translator: Sebastian Bożek <sebastian@log-mar.pl>\n"
"PO-Revision-Date: 2025-06-02 23:00+0000\n"
"Last-Translator: Anarion Dunedain <anarion80@gmail.com>\n"
"Language-Team: Polish <https://translate.pretix.eu/projects/pretix/pretix/pl/"
">\n"
"Language: pl\n"
@@ -18,7 +18,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
"|| n%100>=20) ? 1 : 2;\n"
"X-Generator: Weblate 5.13.3\n"
"X-Generator: Weblate 5.11.4\n"
#: pretix/_base_settings.py:87
msgid "English"
@@ -90,7 +90,7 @@ msgstr "Grecki"
#: pretix/_base_settings.py:104
msgid "Hebrew"
msgstr "Hebrajski"
msgstr ""
#: pretix/_base_settings.py:105
msgid "Indonesian"
@@ -146,7 +146,7 @@ msgstr "Hiszpański"
#: pretix/_base_settings.py:118
msgid "Spanish (Latin America)"
msgstr "Hiszpański (Ameryka Łacińska)"
msgstr ""
#: pretix/_base_settings.py:119
msgid "Turkish"
@@ -2328,7 +2328,7 @@ msgstr ""
#: pretix/base/exporters/waitinglist.py:115 pretix/control/forms/event.py:1671
#: pretix/control/forms/organizer.py:116
msgid "Event slug"
msgstr "Fragment adresu URL, który pojawia się po nazwie domeny."
msgstr "Kod wydarzenia"
#: pretix/base/exporters/orderlist.py:262
#: pretix/base/exporters/orderlist.py:452
@@ -32680,7 +32680,7 @@ msgid ""
"banks. Please keep your online banking account and login information "
"available."
msgstr ""
"Przelewy24 to metoda płatności online dostępna dla klientów Polskich banków. "
"Przelewy24 to metoda płatności online dostępna dla klientów polskich banków. "
"Prosimy o zachowanie danych logowania i konta bankowego."
#: pretix/plugins/stripe/payment.py:1768

View File

@@ -476,7 +476,6 @@ class CSVCheckinList(CheckInListMixin, ListExporter):
category = pgettext_lazy('export_category', 'Check-in')
description = gettext_lazy("Download a spreadsheet with all attendees that are included in a check-in list.")
featured = True
repeatable_read = False
@property
def additional_form_fields(self):
@@ -674,7 +673,6 @@ class CSVCheckinCodeList(CheckInListMixin, ListExporter):
category = pgettext_lazy('export_category', 'Check-in')
description = gettext_lazy("Download a spreadsheet with all valid check-in barcodes e.g. for import into a "
"different system. Does not included blocked codes or personal data.")
repeatable_read = False
@property
def additional_form_fields(self):
@@ -745,7 +743,6 @@ class CheckinLogList(ListExporter):
category = pgettext_lazy('export_category', 'Check-in')
description = gettext_lazy("Download a spreadsheet with one line for every scan that happened at your check-in "
"stations.")
repeatable_read = False
@property
def additional_form_fields(self):

View File

@@ -661,7 +661,6 @@ class OrderTaxListReport(MultiSheetListExporter):
verbose_name = gettext_lazy('Tax split list')
category = pgettext_lazy('export_category', 'Order data')
description = gettext_lazy("Download a spreadsheet with the tax amounts included in each order.")
repeatable_read = False
@property
def sheets(self):

View File

@@ -36,7 +36,6 @@ import copy
import hmac
import inspect
import json
import logging
import mimetypes
import os
import re
@@ -99,8 +98,6 @@ from pretix.presale.views import (
from pretix.presale.views.event import get_grouped_items
from pretix.presale.views.robots import NoSearchIndexViewMixin
logger = logging.getLogger(__name__)
class OrderDetailMixin(NoSearchIndexViewMixin):
@@ -737,18 +734,11 @@ class OrderInvoiceCreate(EventViewMixin, OrderDetailMixin, View):
elif self.order.invoices.exists():
messages.error(self.request, _('An invoice for this order already exists.'))
else:
try:
i = generate_invoice(self.order)
self.order.log_action('pretix.event.order.invoice.generated', data={
'invoice': i.pk
})
messages.success(self.request, _('The invoice has been generated.'))
except Exception as e:
logger.exception("Could not generate invoice.")
self.order.log_action("pretix.event.order.invoice.failed", data={
"exception": str(e)
})
messages.error(self.request, _('Invoice generation has failed, please reach out to the organizer.'))
i = generate_invoice(self.order)
self.order.log_action('pretix.event.order.invoice.generated', data={
'invoice': i.pk
})
messages.success(self.request, _('The invoice has been generated.'))
return redirect(self.get_order_url())
@@ -817,37 +807,24 @@ class OrderModify(EventViewMixin, OrderDetailMixin, OrderQuestionsViewMixin, Tem
elif self.order.invoices.exists():
messages.error(self.request, _('An invoice for this order already exists.'))
else:
try:
i = generate_invoice(self.order)
self.order.log_action('pretix.event.order.invoice.generated', data={
'invoice': i.pk
})
messages.success(self.request, _('The invoice has been generated.'))
except Exception as e:
logger.exception("Could not generate invoice.")
self.order.log_action("pretix.event.order.invoice.failed", data={
"exception": str(e)
})
messages.error(self.request, _('Invoice generation has failed, please reach out to the organizer.'))
i = generate_invoice(self.order)
self.order.log_action('pretix.event.order.invoice.generated', data={
'invoice': i.pk
})
messages.success(self.request, _('The invoice has been generated.'))
elif self.request.event.settings.invoice_reissue_after_modify:
if self.invoice_form.changed_data:
try:
inv = self.order.invoices.last()
if inv and not inv.canceled and not inv.shredded:
c = generate_cancellation(inv)
if self.order.status != Order.STATUS_CANCELED:
inv = generate_invoice(self.order)
else:
inv = c
self.order.log_action('pretix.event.order.invoice.reissued', data={
'invoice': inv.pk
})
messages.success(self.request, _('The invoice has been reissued.'))
except Exception as e:
self.order.log_action("pretix.event.order.invoice.failed", data={
"exception": str(e)
inv = self.order.invoices.last()
if inv and not inv.canceled and not inv.shredded:
c = generate_cancellation(inv)
if self.order.status != Order.STATUS_CANCELED:
inv = generate_invoice(self.order)
else:
inv = c
self.order.log_action('pretix.event.order.invoice.reissued', data={
'invoice': inv.pk
})
logger.exception("Could not generate invoice.")
messages.success(self.request, _('The invoice has been reissued.'))
invalidate_cache.apply_async(kwargs={'event': self.request.event.pk, 'order': self.order.pk})
CachedTicket.objects.filter(order_position__order=self.order).delete()

View File

@@ -330,11 +330,9 @@ var ajaxErrDialog = {
$("#ajaxerr .links").html("<a class='btn btn-default ajaxerr-close'>"
+ gettext("Close message") + "</a>");
$("body").addClass("ajaxerr has-modal-dialog");
$("#ajaxerr").prop("hidden", false);
},
hide: function () {
"use strict";
$("body").removeClass("ajaxerr has-modal-dialog");
$("#ajaxerr").prop("hidden", true);
},
};

View File

@@ -60,7 +60,7 @@ var i18nToString = function (i18nstring) {
$(document).ajaxError(function (event, jqXHR, settings, thrownError) {
waitingDialog.hide();
var c = $(jqXHR.responseText).filter('.container');
if (jqXHR.responseText && jqXHR.responseText.indexOf("<!-- pretix-login-marker -->") !== -1) {
if (jqXHR.responseText.indexOf("<!-- pretix-login-marker -->") !== -1) {
location.href = '/control/login?next=' + encodeURIComponent(location.pathname + location.search + location.hash)
} else if (c.length > 0) {
ajaxErrDialog.show(c.first().html());
@@ -485,7 +485,6 @@ var form_handlers = function (el) {
theme: "bootstrap",
language: $("body").attr("data-select2-locale"),
data: JSON.parse($(this.getAttribute('data-select2-src')).text()),
width: '100%',
}).val(selectedValue).trigger('change');
});

View File

@@ -63,6 +63,10 @@ td > .form-group > .checkbox {
@include box-shadow(none);
}
div[data-formset-body], div[data-formset-form], div[data-nested-formset-form], div[data-nested-formset-body], details[data-formset-form] {
width: 100%;
}
.form-plugins .panel-title {
line-height: 34px;
}

View File

@@ -414,7 +414,7 @@ def test_2f1r_discount_cross_selling_eventseries_mixed(eventseries):
Regular Ticket 42.00 42.00 Date1
''',
recommendations=f''' Price Discounted Price Max Count Prefix
Tickets (Date1 - Wed, Jan. 1, 2020 10:00) Reduced Ticket 23.00 11.50 1 {prefix_date1}
Tickets (Date1 - Wed, Jan. 1st, 2020 10:00) Reduced Ticket 23.00 11.50 1 {prefix_date1}
'''
)
check_cart_behaviour(
@@ -424,8 +424,8 @@ def test_2f1r_discount_cross_selling_eventseries_mixed(eventseries):
Regular Ticket 42.00 42.00 Date2
''',
recommendations=f''' Price Discounted Price Max Count Prefix
Tickets (Date1 - Wed, Jan. 1, 2020 10:00) Reduced Ticket 23.00 11.50 1 {prefix_date1}
Tickets (Date2 - Wed, Jan. 1, 2020 11:00) Reduced Ticket 23.00 11.50 1 {prefix_date2}
Tickets (Date1 - Wed, Jan. 1st, 2020 10:00) Reduced Ticket 23.00 11.50 1 {prefix_date1}
Tickets (Date2 - Wed, Jan. 1st, 2020 11:00) Reduced Ticket 23.00 11.50 1 {prefix_date2}
'''
)
check_cart_behaviour(
@@ -460,8 +460,8 @@ def test_2f1r_discount_cross_selling_eventseries_mixed(eventseries):
Regular Ticket 42.00 42.00 Date2
''',
recommendations=f''' Price Discounted Price Max Count Prefix
Tickets (Date1 - Wed, Jan. 1, 2020 10:00) Reduced Ticket 23.00 11.50 2 {prefix_date1}
Tickets (Date2 - Wed, Jan. 1, 2020 11:00) Reduced Ticket 23.00 11.50 2 {prefix_date2}
Tickets (Date1 - Wed, Jan. 1st, 2020 10:00) Reduced Ticket 23.00 11.50 2 {prefix_date1}
Tickets (Date2 - Wed, Jan. 1st, 2020 11:00) Reduced Ticket 23.00 11.50 2 {prefix_date2}
'''
)
check_cart_behaviour(
@@ -475,7 +475,7 @@ def test_2f1r_discount_cross_selling_eventseries_mixed(eventseries):
Reduced Ticket 23.00 11.50 Date1
''',
recommendations=f''' Price Discounted Price Max Count Prefix
Tickets (Date1 - Wed, Jan. 1, 2020 10:00) Reduced Ticket 23.00 11.50 1 {prefix_date1}
Tickets (Date1 - Wed, Jan. 1st, 2020 10:00) Reduced Ticket 23.00 11.50 1 {prefix_date1}
'''
)
check_cart_behaviour(
@@ -490,7 +490,7 @@ def test_2f1r_discount_cross_selling_eventseries_mixed(eventseries):
Reduced Ticket 23.00 11.50 Date1
''',
recommendations=f''' Price Discounted Price Max Count Prefix
Tickets (Date1 - Wed, Jan. 1, 2020 10:00) Reduced Ticket 23.00 11.50 1 {prefix_date1}
Tickets (Date1 - Wed, Jan. 1st, 2020 10:00) Reduced Ticket 23.00 11.50 1 {prefix_date1}
'''
)
check_cart_behaviour(
@@ -506,7 +506,7 @@ def test_2f1r_discount_cross_selling_eventseries_mixed(eventseries):
Reduced Ticket 23.00 11.50 Date1
''',
recommendations=f''' Price Discounted Price Max Count Prefix
Tickets (Date1 - Wed, Jan. 1, 2020 10:00) Reduced Ticket 23.00 11.50 2 {prefix_date1}
Tickets (Date1 - Wed, Jan. 1st, 2020 10:00) Reduced Ticket 23.00 11.50 2 {prefix_date1}
'''
)
@@ -531,8 +531,8 @@ def test_2f1r_discount_cross_selling_eventseries_same(eventseries):
Regular Ticket 42.00 42.00 Date2
''',
recommendations=f''' Price Discounted Price Max Count Prefix
Tickets (Date1 - Wed, Jan. 1, 2020 10:00) Reduced Ticket 23.00 11.50 1 {prefix_date1}
Tickets (Date2 - Wed, Jan. 1, 2020 11:00) Reduced Ticket 23.00 11.50 1 {prefix_date2}
Tickets (Date1 - Wed, Jan. 1st, 2020 10:00) Reduced Ticket 23.00 11.50 1 {prefix_date1}
Tickets (Date2 - Wed, Jan. 1st, 2020 11:00) Reduced Ticket 23.00 11.50 1 {prefix_date2}
'''
)
check_cart_behaviour(
@@ -546,8 +546,8 @@ def test_2f1r_discount_cross_selling_eventseries_same(eventseries):
Regular Ticket 42.00 42.00 Date2
''',
recommendations=f''' Price Discounted Price Max Count Prefix
Tickets (Date1 - Wed, Jan. 1, 2020 10:00) Reduced Ticket 23.00 11.50 1 {prefix_date1}
Tickets (Date2 - Wed, Jan. 1, 2020 11:00) Reduced Ticket 23.00 11.50 2 {prefix_date2}
Tickets (Date1 - Wed, Jan. 1st, 2020 10:00) Reduced Ticket 23.00 11.50 1 {prefix_date1}
Tickets (Date2 - Wed, Jan. 1st, 2020 11:00) Reduced Ticket 23.00 11.50 2 {prefix_date2}
'''
)
check_cart_behaviour(
@@ -561,7 +561,7 @@ def test_2f1r_discount_cross_selling_eventseries_same(eventseries):
Reduced Ticket 23.00 11.50 Date1
''',
recommendations=f''' Price Discounted Price Max Count Prefix
Tickets (Date1 - Wed, Jan. 1, 2020 10:00) Reduced Ticket 23.00 11.50 1 {prefix_date1}
Tickets (Date1 - Wed, Jan. 1st, 2020 10:00) Reduced Ticket 23.00 11.50 1 {prefix_date1}
'''
)
check_cart_behaviour(
@@ -589,7 +589,7 @@ def test_2f1r_discount_cross_selling_eventseries_same(eventseries):
Reduced Ticket 23.00 11.50 Date1
''',
recommendations=f''' Price Discounted Price Max Count Prefix
Tickets (Date2 - Wed, Jan. 1, 2020 11:00) Reduced Ticket 23.00 11.50 1 {prefix_date2}
Tickets (Date2 - Wed, Jan. 1st, 2020 11:00) Reduced Ticket 23.00 11.50 1 {prefix_date2}
'''
)

View File

@@ -327,26 +327,6 @@ def test_enqueue_order_twice(event):
SimpleOrderSync.enqueue_order(order, 'testcase_2nd')
class DoNothingSync(SimpleOrderSync):
def should_sync_order(self, order):
return False
@pytest.mark.django_db
def test_should_not_sync(event):
_register_with_fake_plugin_name(datasync_providers, DoNothingSync, 'testplugin')
DoNothingSync.fake_api_client = FakeSyncAPI()
for order in event.orders.order_by("code").all():
DoNothingSync.enqueue_order(order, 'testcase')
sync_all()
assert DoNothingSync.fake_api_client.fake_database == {}
StaticMappingWithAssociations = namedtuple('StaticMappingWithAssociations', (
'id', 'pretix_model', 'external_object_type', 'pretix_id_field', 'external_id_field', 'property_mappings', 'association_mappings'
))

View File

@@ -64,7 +64,6 @@ from pretix.base.models.items import (
from pretix.base.reldate import RelativeDate, RelativeDateWrapper
from pretix.base.services.orders import OrderError, cancel_order, perform_order
from pretix.base.services.quotas import QuotaAvailability
from pretix.helpers import repeatable_reads_transaction
from pretix.testutils.scope import classscope
@@ -100,29 +99,6 @@ class BaseQuotaTestCase(TestCase):
self.var3 = ItemVariation.objects.create(item=self.item3, value='Fancy')
@pytest.mark.django_db(transaction=True)
@scopes_disabled()
def test_verify_repeatable_read_check():
if 'sqlite' in settings.DATABASES['default']['ENGINE']:
pytest.skip('Not supported on SQLite')
o = Organizer.objects.create(name='Dummy', slug='dummy')
event = Event.objects.create(
organizer=o, name='Dummy', slug='dummy',
date_from=now(), plugins='tests.testdummy'
)
quota = Quota.objects.create(name="Test", size=2, event=event)
with repeatable_reads_transaction():
with pytest.raises(ValueError):
qa = QuotaAvailability(full_results=True)
qa.queue(quota)
qa.compute()
qa = QuotaAvailability(full_results=True, allow_repeatable_read=True)
qa.queue(quota)
qa.compute()
@pytest.mark.usefixtures("fakeredis_client")
class QuotaTestCase(BaseQuotaTestCase):
@classscope(attr='o')
@@ -2489,44 +2465,44 @@ class EventTest(TestCase):
(
datetime.datetime(2025, 3, 9, 21, 0, 0, tzinfo=tz),
datetime.datetime(2025, 3, 9, 22, 0, 0, tzinfo=tz),
'Sun, March 9, 2025',
'<time datetime="2025-03-09">Sun, March 9, 2025</time>',
'Sun, March 9, 2025 20:0021:00',
'<time datetime="2025-03-09">Sun, March 9, 2025</time> '
'Sun, March 9th, 2025',
'<time datetime="2025-03-09">Sun, March 9th, 2025</time>',
'Sun, March 9th, 2025 20:0021:00',
'<time datetime="2025-03-09">Sun, March 9th, 2025</time> '
'<time datetime="2025-03-09T21:00:00+01:00" data-timezone="UTC" data-time-short>20:0021:00</time>'
),
(
datetime.datetime(2025, 3, 9, 21, 0, 0, tzinfo=tz),
datetime.datetime(2025, 3, 10, 3, 0, 0, tzinfo=tz),
'March 9 10, 2025',
'<time datetime="2025-03-09">March 9</time> '
'March 9th 10th, 2025',
'<time datetime="2025-03-09">March 9th</time> '
'<span aria-hidden="true"></span><span class="sr-only"> until </span> '
'<time datetime="2025-03-10">10, 2025</time>',
'March 9 10, 2025 20:0002:00',
'<time datetime="2025-03-09">March 9</time> '
'<time datetime="2025-03-10">10th, 2025</time>',
'March 9th 10th, 2025 20:0002:00',
'<time datetime="2025-03-09">March 9th</time> '
'<span aria-hidden="true"></span><span class="sr-only"> until </span> '
'<time datetime="2025-03-10">10, 2025</time> '
'<time datetime="2025-03-10">10th, 2025</time> '
'<time datetime="2025-03-09T21:00:00+01:00" data-timezone="UTC" data-time-short>20:0002:00</time>'
),
(
datetime.datetime(2025, 3, 9, 21, 0, 0, tzinfo=tz),
datetime.datetime(2025, 3, 12, 14, 0, 0, tzinfo=tz),
'March 9 12, 2025',
'<time datetime="2025-03-09">March 9</time> '
'March 9th 12th, 2025',
'<time datetime="2025-03-09">March 9th</time> '
'<span aria-hidden="true"></span><span class="sr-only"> until </span> '
'<time datetime="2025-03-12">12, 2025</time>',
'March 9 12, 2025',
'<time datetime="2025-03-09">March 9</time> '
'<time datetime="2025-03-12">12th, 2025</time>',
'March 9th 12th, 2025',
'<time datetime="2025-03-09">March 9th</time> '
'<span aria-hidden="true"></span><span class="sr-only"> until </span> '
'<time datetime="2025-03-12">12, 2025</time>',
'<time datetime="2025-03-12">12th, 2025</time>',
),
(
datetime.datetime(2025, 3, 9, 21, 0, 0, tzinfo=tz),
None,
'Sun, March 9, 2025',
'<time datetime="2025-03-09">Sun, March 9, 2025</time>',
'Sun, March 9, 2025 20:00',
'<time datetime="2025-03-09">Sun, March 9, 2025</time> '
'Sun, March 9th, 2025',
'<time datetime="2025-03-09">Sun, March 9th, 2025</time>',
'Sun, March 9th, 2025 20:00',
'<time datetime="2025-03-09">Sun, March 9th, 2025</time> '
'<time datetime="2025-03-09T21:00:00+01:00" data-timezone="UTC" data-time-short>20:00</time>'
),
)

View File

@@ -48,10 +48,10 @@ ref_date = date(2023, 3, 28)
(ref_date, 'days_tomorrow', date(2023, 3, 29), date(2023, 3, 29), None),
(ref_date, 'days_next7', date(2023, 3, 29), date(2023, 4, 4), None),
(ref_date, 'days_next14', date(2023, 3, 29), date(2023, 4, 11), None),
(ref_date, 'week_this', date(2023, 3, 27), date(2023, 4, 2), 'W 13, 2023 - March 27 April 2, 2023'),
(ref_date, 'week_to_date', date(2023, 3, 27), date(2023, 3, 28), 'W 13, 2023 - March 27 28, 2023'),
(ref_date, 'week_previous', date(2023, 3, 20), date(2023, 3, 26), 'W 12, 2023 - March 20 26, 2023'),
(ref_date, 'week_next', date(2023, 4, 3), date(2023, 4, 9), 'W 14, 2023 - April 3 9, 2023'),
(ref_date, 'week_this', date(2023, 3, 27), date(2023, 4, 2), 'W 13, 2023 - March 27th April 2nd, 2023'),
(ref_date, 'week_to_date', date(2023, 3, 27), date(2023, 3, 28), 'W 13, 2023 - March 27th 28th, 2023'),
(ref_date, 'week_previous', date(2023, 3, 20), date(2023, 3, 26), 'W 12, 2023 - March 20th 26th, 2023'),
(ref_date, 'week_next', date(2023, 4, 3), date(2023, 4, 9), 'W 14, 2023 - April 3rd 9th, 2023'),
(ref_date, 'month_this', date(2023, 3, 1), date(2023, 3, 31), 'March 2023'),
(ref_date, 'month_to_date', date(2023, 3, 1), date(2023, 3, 28), 'March 2023'),
(ref_date, 'month_previous', date(2023, 2, 1), date(2023, 2, 28), 'February 2023'),

View File

@@ -50,8 +50,8 @@ def test_same_day_german():
def test_same_day_english():
with translation.override('en'):
df = date(2003, 2, 1)
assert daterange(df, df) == "Sat, Feb. 1, 2003"
assert daterange(df, df, as_html=True) == '<time datetime="2003-02-01">Sat, Feb. 1, 2003</time>'
assert daterange(df, df) == "Sat, Feb. 1st, 2003"
assert daterange(df, df, as_html=True) == '<time datetime="2003-02-01">Sat, Feb. 1st, 2003</time>'
def test_same_day_spanish():
@@ -82,10 +82,10 @@ def test_same_month_english():
with translation.override('en'):
df = date(2003, 2, 1)
dt = date(2003, 2, 3)
assert daterange(df, dt) == "Feb. 1 3, 2003"
assert daterange(df, dt, as_html=True) == '<time datetime="2003-02-01">Feb. 1</time> ' \
assert daterange(df, dt) == "Feb. 1st 3rd, 2003"
assert daterange(df, dt, as_html=True) == '<time datetime="2003-02-01">Feb. 1st</time> ' \
'<span aria-hidden="true"></span><span class="sr-only"> until </span> ' \
'<time datetime="2003-02-03">3, 2003</time>'
'<time datetime="2003-02-03">3rd, 2003</time>'
def test_same_month_spanish():
@@ -112,10 +112,10 @@ def test_same_year_english():
with translation.override('en'):
df = date(2003, 2, 1)
dt = date(2003, 4, 3)
assert daterange(df, dt) == "Feb. 1 April 3, 2003"
assert daterange(df, dt, as_html=True) == '<time datetime="2003-02-01">Feb. 1</time> ' \
assert daterange(df, dt) == "Feb. 1st April 3rd, 2003"
assert daterange(df, dt, as_html=True) == '<time datetime="2003-02-01">Feb. 1st</time> ' \
'<span aria-hidden="true"></span><span class="sr-only"> until </span> ' \
'<time datetime="2003-04-03">April 3, 2003</time>'
'<time datetime="2003-04-03">April 3rd, 2003</time>'
def test_same_year_spanish():

View File

@@ -1621,7 +1621,7 @@ class EventLocaleTest(EventTestMixin, SoupTest):
'/%s/%s/' % (self.orga.slug, self.event.slug)
)
self.assertEqual(response.status_code, 200)
self.assertIn('Thu, Dec. 26,', response.rendered_content)
self.assertIn('Thu, Dec. 26th,', response.rendered_content)
self.assertIn('14:00', response.rendered_content)
def test_english_region_US(self):
@@ -1631,7 +1631,7 @@ class EventLocaleTest(EventTestMixin, SoupTest):
'/%s/%s/' % (self.orga.slug, self.event.slug)
)
self.assertEqual(response.status_code, 200)
self.assertIn('Thu, Dec. 26,', response.rendered_content)
self.assertIn('Thu, Dec. 26th,', response.rendered_content)
self.assertIn('2 p.m.', response.rendered_content)
def test_german_region_US(self):

View File

@@ -169,7 +169,7 @@ class WidgetCartTest(CartTestMixin, TestCase):
"target_url": "http://example.com/ccc/30c3/",
"subevent": None,
"name": "30C3",
"date_range": f"{self.event.date_from.strftime('%a')}, Dec. 26, {self.event.date_from.year} 00:00",
"date_range": f"{self.event.date_from.strftime('%a')}, Dec. 26th, {self.event.date_from.year} 00:00",
"frontpage_text": "",
"location": "",
"currency": "EUR",
@@ -375,7 +375,7 @@ class WidgetCartTest(CartTestMixin, TestCase):
"target_url": "http://example.com/ccc/30c3/",
"subevent": None,
"name": "30C3",
"date_range": f"{self.event.date_from.strftime('%a')}, Dec. 26, {self.event.date_from.year} 00:00",
"date_range": f"{self.event.date_from.strftime('%a')}, Dec. 26th, {self.event.date_from.year} 00:00",
"frontpage_text": "",
"location": "",
"currency": "EUR",
@@ -435,7 +435,7 @@ class WidgetCartTest(CartTestMixin, TestCase):
"target_url": "http://example.com/ccc/30c3/",
"subevent": None,
"name": "30C3",
"date_range": f"{self.event.date_from.strftime('%a')}, Dec. 26, {self.event.date_from.year} 00:00",
"date_range": f"{self.event.date_from.strftime('%a')}, Dec. 26th, {self.event.date_from.year} 00:00",
"frontpage_text": "",
"location": "",
"currency": "EUR",
@@ -520,7 +520,7 @@ class WidgetCartTest(CartTestMixin, TestCase):
"target_url": "http://example.com/ccc/30c3/",
"subevent": None,
"name": "30C3",
"date_range": f"{self.event.date_from.strftime('%a')}, Dec. 26, {self.event.date_from.year} 00:00",
"date_range": f"{self.event.date_from.strftime('%a')}, Dec. 26th, {self.event.date_from.year} 00:00",
"frontpage_text": "",
"location": "",
"currency": "EUR",
@@ -627,9 +627,9 @@ class WidgetCartTest(CartTestMixin, TestCase):
'poweredby': '<a href="https://pretix.eu" target="_blank" rel="noopener">ticketing powered by pretix</a>',
'has_more_events': False,
'events': [
{'name': 'Present', 'date_range': 'Tue, Jan. 1, 2019 11:00', 'availability': {'color': 'none', 'text': 'More info', 'reason': 'unknown'},
{'name': 'Present', 'date_range': 'Tue, Jan. 1st, 2019 11:00', 'availability': {'color': 'none', 'text': 'More info', 'reason': 'unknown'},
'event_url': 'http://example.com/ccc/30c3/', 'subevent': se1.pk, 'location': ''},
{'name': 'Future', 'date_range': 'Fri, Jan. 4, 2019 11:00', 'availability': {'color': 'none', 'text': 'More info', 'reason': 'unknown'},
{'name': 'Future', 'date_range': 'Fri, Jan. 4th, 2019 11:00', 'availability': {'color': 'none', 'text': 'More info', 'reason': 'unknown'},
'event_url': 'http://example.com/ccc/30c3/', 'subevent': se2.pk, 'location': ''}
]
}
@@ -659,14 +659,14 @@ class WidgetCartTest(CartTestMixin, TestCase):
[
None,
{'day': 1, 'date': '2019-01-01', 'events': [
{'name': 'Present', 'time': '11:00', 'continued': False, 'date_range': 'Tue, Jan. 1, 2019 11:00',
{'name': 'Present', 'time': '11:00', 'continued': False, 'date_range': 'Tue, Jan. 1st, 2019 11:00',
'location': '',
'availability': {'color': 'none', 'text': 'More info', 'reason': 'unknown'},
'event_url': 'http://example.com/ccc/30c3/', 'subevent': se1.pk}]},
{'day': 2, 'date': '2019-01-02', 'events': []},
{'day': 3, 'date': '2019-01-03', 'events': []},
{'day': 4, 'date': '2019-01-04', 'events': [
{'name': 'Future', 'time': '11:00', 'continued': False, 'date_range': 'Fri, Jan. 4, 2019 11:00',
{'name': 'Future', 'time': '11:00', 'continued': False, 'date_range': 'Fri, Jan. 4th, 2019 11:00',
'location': '',
'availability': {'color': 'none', 'text': 'More info', 'reason': 'unknown'},
'event_url': 'http://example.com/ccc/30c3/', 'subevent': se2.pk}]},
@@ -732,21 +732,21 @@ class WidgetCartTest(CartTestMixin, TestCase):
'week': [2019, 1],
'poweredby': '<a href="https://pretix.eu" target="_blank" rel="noopener">ticketing powered by pretix</a>',
'days': [
{'day_formatted': 'Mon, Dec 31', 'date': '2018-12-31', 'events': [], 'today': False},
{'day_formatted': 'Tue, Jan 1', 'date': '2019-01-01', 'events': [
{'name': 'Present', 'time': '11:00', 'continued': False, 'date_range': 'Tue, Jan. 1, 2019 11:00',
{'day_formatted': 'Mon, Dec 31st', 'date': '2018-12-31', 'events': [], 'today': False},
{'day_formatted': 'Tue, Jan 1st', 'date': '2019-01-01', 'events': [
{'name': 'Present', 'time': '11:00', 'continued': False, 'date_range': 'Tue, Jan. 1st, 2019 11:00',
'location': '',
'availability': {'color': 'none', 'text': 'More info', 'reason': 'unknown'},
'event_url': 'http://example.com/ccc/30c3/', 'subevent': se1.pk}], 'today': True},
{'day_formatted': 'Wed, Jan 2', 'date': '2019-01-02', 'events': [], 'today': False},
{'day_formatted': 'Thu, Jan 3', 'date': '2019-01-03', 'events': [], 'today': False},
{'day_formatted': 'Fri, Jan 4', 'date': '2019-01-04', 'events': [
{'name': 'Future', 'time': '11:00', 'continued': False, 'date_range': 'Fri, Jan. 4, 2019 11:00',
{'day_formatted': 'Wed, Jan 2nd', 'date': '2019-01-02', 'events': [], 'today': False},
{'day_formatted': 'Thu, Jan 3rd', 'date': '2019-01-03', 'events': [], 'today': False},
{'day_formatted': 'Fri, Jan 4th', 'date': '2019-01-04', 'events': [
{'name': 'Future', 'time': '11:00', 'continued': False, 'date_range': 'Fri, Jan. 4th, 2019 11:00',
'location': '',
'availability': {'color': 'none', 'text': 'More info', 'reason': 'unknown'},
'event_url': 'http://example.com/ccc/30c3/', 'subevent': se2.pk}], 'today': False},
{'day_formatted': 'Sat, Jan 5', 'date': '2019-01-05', 'events': [], 'today': False},
{'day_formatted': 'Sun, Jan 6', 'date': '2019-01-06', 'events': [], 'today': False}
{'day_formatted': 'Sat, Jan 5th', 'date': '2019-01-05', 'events': [], 'today': False},
{'day_formatted': 'Sun, Jan 6th', 'date': '2019-01-06', 'events': [], 'today': False}
],
}
@@ -773,17 +773,17 @@ class WidgetCartTest(CartTestMixin, TestCase):
'poweredby': '<a href="https://pretix.eu" target="_blank" rel="noopener">ticketing powered by pretix</a>',
'events': [
{'availability': {'color': 'none', 'text': 'Event series'},
'date_range': 'Jan. 1 4, 2019',
'date_range': 'Jan. 1st 4th, 2019',
'event_url': 'http://example.com/ccc/30c3/',
'location': '',
'name': '30C3'},
{'availability': {'color': 'none', 'text': 'More info', 'reason': 'unknown'},
'date_range': 'Tue, Jan. 1, 2019 10:00',
'date_range': 'Tue, Jan. 1st, 2019 10:00',
'location': '',
'event_url': 'http://example.com/ccc/present/',
'name': 'Present'},
{'availability': {'color': 'none', 'text': 'More info', 'reason': 'unknown'},
'date_range': 'Fri, Jan. 4, 2019 10:00',
'date_range': 'Fri, Jan. 4th, 2019 10:00',
'location': '',
'event_url': 'http://example.com/ccc/future/',
'name': 'Future'}
@@ -884,7 +884,7 @@ class WidgetCartTest(CartTestMixin, TestCase):
'day': 1,
'events': [{'availability': {'color': 'none', 'text': 'More info', 'reason': 'unknown'},
'continued': False,
'date_range': 'Tue, Jan. 1, 2019 10:00',
'date_range': 'Tue, Jan. 1st, 2019 10:00',
'event_url': 'http://example.com/ccc/present/',
'name': 'Present',
'location': '',
@@ -892,7 +892,7 @@ class WidgetCartTest(CartTestMixin, TestCase):
'time': '10:00'},
{'availability': {'color': 'none', 'text': 'More info', 'reason': 'unknown'},
'continued': False,
'date_range': 'Tue, Jan. 1, 2019 11:00',
'date_range': 'Tue, Jan. 1st, 2019 11:00',
'event_url': 'http://example.com/ccc/30c3/',
'name': 'Present',
'location': '',
@@ -904,7 +904,7 @@ class WidgetCartTest(CartTestMixin, TestCase):
'day': 4,
'events': [{'availability': {'color': 'none', 'text': 'More info', 'reason': 'unknown'},
'continued': False,
'date_range': 'Fri, Jan. 4, 2019 10:00',
'date_range': 'Fri, Jan. 4th, 2019 10:00',
'event_url': 'http://example.com/ccc/future/',
'name': 'Future',
'location': '',
@@ -912,7 +912,7 @@ class WidgetCartTest(CartTestMixin, TestCase):
'time': '10:00'},
{'availability': {'color': 'none', 'text': 'More info', 'reason': 'unknown'},
'continued': False,
'date_range': 'Fri, Jan. 4, 2019 11:00',
'date_range': 'Fri, Jan. 4th, 2019 11:00',
'event_url': 'http://example.com/ccc/30c3/',
'name': 'Future',
'location': '',