mirror of
https://github.com/pretix/pretix.git
synced 2026-03-17 14:52:28 +00:00
Compare commits
4 Commits
flaky-test
...
hotfix2021
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cedd0c877e | ||
|
|
c9cebd820a | ||
|
|
e2a40aaa3a | ||
|
|
584ff16f58 |
@@ -1 +1 @@
|
||||
__version__ = "3.17.0.dev0"
|
||||
__version__ = "3.16.0"
|
||||
|
||||
@@ -596,7 +596,6 @@ class EventSettingsSerializer(SettingsSerializer):
|
||||
'checkout_email_helptext',
|
||||
'presale_has_ended_text',
|
||||
'voucher_explanation_text',
|
||||
'checkout_success_text',
|
||||
'banner_text',
|
||||
'banner_text_bottom',
|
||||
'show_dates_on_frontpage',
|
||||
@@ -657,7 +656,6 @@ class EventSettingsSerializer(SettingsSerializer):
|
||||
'mail_from',
|
||||
'mail_from_name',
|
||||
'mail_attach_ical',
|
||||
'mail_attach_tickets',
|
||||
'invoice_address_asked',
|
||||
'invoice_address_required',
|
||||
'invoice_address_vatid',
|
||||
|
||||
@@ -736,14 +736,7 @@ with scopes_disabled():
|
||||
|
||||
def search_qs(self, queryset, name, value):
|
||||
return queryset.filter(
|
||||
Q(secret__istartswith=value)
|
||||
| Q(attendee_name_cached__icontains=value)
|
||||
| Q(addon_to__attendee_name_cached__icontains=value)
|
||||
| Q(attendee_email__icontains=value)
|
||||
| Q(addon_to__attendee_email__icontains=value)
|
||||
| Q(order__code__istartswith=value)
|
||||
| Q(order__invoice_address__name_cached__icontains=value)
|
||||
| Q(order__email__icontains=value)
|
||||
Q(attendee_name_cached__icontains=value)
|
||||
)
|
||||
|
||||
def has_checkin_qs(self, queryset, name, value):
|
||||
|
||||
@@ -13,7 +13,6 @@ from django.utils.functional import cached_property
|
||||
from django.utils.translation import gettext, gettext_lazy as _, pgettext
|
||||
|
||||
from pretix.base.models import Invoice, InvoiceLine, OrderPayment
|
||||
from ..services.export import ExportError
|
||||
|
||||
from ...control.forms.filter import get_all_payment_providers
|
||||
from ...helpers import GroupConcat
|
||||
@@ -112,8 +111,6 @@ class InvoiceExporter(InvoiceExporterMixin, BaseExporter):
|
||||
if not i.file:
|
||||
invoice_pdf_task.apply(args=(i.pk,))
|
||||
i.refresh_from_db()
|
||||
if not i.file:
|
||||
raise ExportError('Could not generate PDF for invoice {nr}'.format(nr=i.full_invoice_no))
|
||||
i.file.open('rb')
|
||||
zipf.writestr('{}-{}.pdf'.format(i.number, i.order.code), i.file.read())
|
||||
i.file.close()
|
||||
|
||||
@@ -255,10 +255,8 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
invoice_from_top = 17 * mm
|
||||
|
||||
def _draw_invoice_from(self, canvas):
|
||||
p = Paragraph(
|
||||
bleach.clean(self.invoice.full_invoice_from, tags=[]).strip().replace('\n', '<br />\n'),
|
||||
style=self.stylesheet['InvoiceFrom']
|
||||
)
|
||||
p = Paragraph(self.invoice.full_invoice_from.strip().replace('\n', '<br />\n'), style=self.stylesheet[
|
||||
'InvoiceFrom'])
|
||||
p.wrapOn(canvas, self.invoice_from_width, self.invoice_from_height)
|
||||
p_size = p.wrap(self.invoice_from_width, self.invoice_from_height)
|
||||
p.drawOn(canvas, self.invoice_from_left, self.pagesize[1] - p_size[1] - self.invoice_from_top)
|
||||
@@ -363,7 +361,6 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
def _draw_event(self, canvas):
|
||||
def shorten(txt):
|
||||
txt = str(txt)
|
||||
txt = bleach.clean(txt, tags=[]).strip()
|
||||
p = Paragraph(txt.strip().replace('\n', '<br />\n'), style=self.stylesheet['Normal'])
|
||||
p_size = p.wrap(self.event_width, self.event_height)
|
||||
|
||||
@@ -444,18 +441,13 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
story = []
|
||||
if self.invoice.custom_field:
|
||||
story.append(Paragraph(
|
||||
'{}: {}'.format(
|
||||
bleach.clean(self.invoice.event.settings.invoice_address_custom_field, tags=[]).strip().replace('\n', '<br />\n'),
|
||||
bleach.clean(self.invoice.custom_field, tags=[]).strip().replace('\n', '<br />\n'),
|
||||
),
|
||||
'{}: {}'.format(self.invoice.event.settings.invoice_address_custom_field, self.invoice.custom_field),
|
||||
self.stylesheet['Normal']
|
||||
))
|
||||
|
||||
if self.invoice.internal_reference:
|
||||
story.append(Paragraph(
|
||||
pgettext('invoice', 'Customer reference: {reference}').format(
|
||||
reference=bleach.clean(self.invoice.internal_reference, tags=[]).strip().replace('\n', '<br />\n'),
|
||||
),
|
||||
pgettext('invoice', 'Customer reference: {reference}').format(reference=self.invoice.internal_reference),
|
||||
self.stylesheet['Normal']
|
||||
))
|
||||
|
||||
@@ -474,10 +466,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
))
|
||||
|
||||
if self.invoice.introductory_text:
|
||||
story.append(Paragraph(
|
||||
bleach.clean(self.invoice.introductory_text, tags=[]).strip().replace('\n', '<br />\n'),
|
||||
self.stylesheet['Normal']
|
||||
))
|
||||
story.append(Paragraph(self.invoice.introductory_text, self.stylesheet['Normal']))
|
||||
story.append(Spacer(1, 10 * mm))
|
||||
|
||||
return story
|
||||
@@ -577,16 +566,10 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
story.append(Spacer(1, 15 * mm))
|
||||
|
||||
if self.invoice.payment_provider_text:
|
||||
story.append(Paragraph(
|
||||
bleach.clean(self.invoice.payment_provider_text, tags=[]).strip().replace('\n', '<br />\n'),
|
||||
self.stylesheet['Normal']
|
||||
))
|
||||
story.append(Paragraph(self.invoice.payment_provider_text, self.stylesheet['Normal']))
|
||||
|
||||
if self.invoice.additional_text:
|
||||
story.append(Paragraph(
|
||||
bleach.clean(self.invoice.additional_text, tags=[]).strip().replace('\n', '<br />\n'),
|
||||
self.stylesheet['Normal']
|
||||
))
|
||||
story.append(Paragraph(self.invoice.additional_text, self.stylesheet['Normal']))
|
||||
story.append(Spacer(1, 15 * mm))
|
||||
|
||||
tstyledata = [
|
||||
@@ -718,10 +701,7 @@ class Modern1Renderer(ClassicInvoiceRenderer):
|
||||
def _draw_invoice_from(self, canvas):
|
||||
if not self.invoice.invoice_from:
|
||||
return
|
||||
c = [
|
||||
bleach.clean(l, tags=[]).strip().replace('\n', '<br />\n')
|
||||
for l in self.invoice.address_invoice_from.strip().split('\n')
|
||||
]
|
||||
c = self.invoice.address_invoice_from.strip().split('\n')
|
||||
p = Paragraph(' · '.join(c), style=self.stylesheet['Sender'])
|
||||
p.wrapOn(canvas, self.invoice_to_width, 15.7 * mm)
|
||||
p.drawOn(canvas, self.invoice_to_left, self.pagesize[1] - self.invoice_to_top + 2 * mm)
|
||||
|
||||
@@ -291,8 +291,6 @@ def mail_send_task(self, *args, to: List[str], subject: str, body: str, html: st
|
||||
order = None
|
||||
else:
|
||||
with language(order.locale, event.settings.region):
|
||||
if not event.settings.mail_attach_tickets:
|
||||
attach_tickets = False
|
||||
if position:
|
||||
try:
|
||||
position = order.positions.get(pk=position)
|
||||
|
||||
@@ -10,6 +10,7 @@ from django.db.models import (
|
||||
)
|
||||
from django.dispatch import receiver
|
||||
from django.utils.timezone import now
|
||||
from django_redis import get_redis_connection
|
||||
from django_scopes import scopes_disabled
|
||||
|
||||
from pretix.base.models import (
|
||||
@@ -89,7 +90,7 @@ class QuotaAvailability:
|
||||
def queue(self, *quota):
|
||||
self._queue += quota
|
||||
|
||||
def compute(self, now_dt=None):
|
||||
def compute(self, now_dt=None, dbcache=True):
|
||||
now_dt = now_dt or now()
|
||||
quotas = list(set(self._queue))
|
||||
quotas_original = list(self._queue)
|
||||
@@ -106,18 +107,18 @@ class QuotaAvailability:
|
||||
|
||||
self._close(quotas)
|
||||
try:
|
||||
self._write_cache(quotas, now_dt)
|
||||
self._write_cache(quotas, now_dt, dbcache)
|
||||
except OperationalError as e:
|
||||
# Ignore deadlocks when multiple threads try to write to the cache
|
||||
if 'deadlock' not in str(e).lower():
|
||||
raise e
|
||||
|
||||
def _write_cache(self, quotas, now_dt):
|
||||
def _write_cache(self, quotas, now_dt, dbcache):
|
||||
# We used to also delete item_quota_cache:* from the event cache here, but as the cache
|
||||
# gets more complex, this does not seem worth it. The cache is only present for up to
|
||||
# 5 seconds to prevent high peaks, and a 5-second delay in availability is usually
|
||||
# tolerable
|
||||
update = []
|
||||
update = defaultdict(list)
|
||||
for q in quotas:
|
||||
rewrite_cache = self._count_waitinglist and (
|
||||
not q.cache_is_hot(now_dt) or self.results[q][0] > q.cached_availability_state
|
||||
@@ -129,12 +130,21 @@ class QuotaAvailability:
|
||||
q.cached_availability_time = now_dt
|
||||
if q in self.count_paid_orders:
|
||||
q.cached_availability_paid_orders = self.count_paid_orders[q]
|
||||
update.append(q)
|
||||
update[q.event_id].append(q)
|
||||
|
||||
if update:
|
||||
Quota.objects.using('default').bulk_update(update, [
|
||||
'cached_availability_state', 'cached_availability_number', 'cached_availability_time',
|
||||
'cached_availability_paid_orders'
|
||||
], batch_size=50)
|
||||
# if dbcache:
|
||||
# Quota.objects.using('default').bulk_update(sum((quotas for event, quotas in update.items()), []), [
|
||||
# 'cached_availability_state', 'cached_availability_number', 'cached_availability_time',
|
||||
# 'cached_availability_paid_orders'
|
||||
# ], batch_size=50)
|
||||
|
||||
if settings.HAS_REDIS:
|
||||
rc = get_redis_connection("redis")
|
||||
for eventid, quotas in update.items():
|
||||
rc.hmset(f'quotas:{eventid}:availability', {
|
||||
str(q.id): ",".join([str(i) for i in self.results[q]]) for q in quotas
|
||||
})
|
||||
|
||||
def _close(self, quotas):
|
||||
for q in quotas:
|
||||
|
||||
@@ -13,7 +13,6 @@ from django.core.validators import (
|
||||
MaxValueValidator, MinValueValidator, RegexValidator,
|
||||
)
|
||||
from django.db.models import Model
|
||||
from django.utils.text import format_lazy
|
||||
from django.utils.translation import (
|
||||
gettext_lazy as _, gettext_noop, pgettext, pgettext_lazy,
|
||||
)
|
||||
@@ -1323,19 +1322,6 @@ DEFAULTS = {
|
||||
'default': 'classic',
|
||||
'type': str
|
||||
},
|
||||
'mail_attach_tickets': {
|
||||
'default': 'True',
|
||||
'type': bool,
|
||||
'form_class': forms.BooleanField,
|
||||
'serializer_class': serializers.BooleanField,
|
||||
'form_kwargs': dict(
|
||||
label=_("Attach ticket files"),
|
||||
help_text=format_lazy(
|
||||
_("Tickets will never be attached if they're larger than {size} to avoid email delivery problems."),
|
||||
size='4 MB'
|
||||
),
|
||||
)
|
||||
},
|
||||
'mail_attach_ical': {
|
||||
'default': 'False',
|
||||
'type': bool,
|
||||
@@ -1997,19 +1983,6 @@ Your {event} team"""))
|
||||
"why you need information from them.")
|
||||
)
|
||||
},
|
||||
'checkout_success_text': {
|
||||
'default': '',
|
||||
'type': LazyI18nString,
|
||||
'serializer_class': I18nField,
|
||||
'form_class': I18nFormField,
|
||||
'form_kwargs': dict(
|
||||
label=_("Additional success message"),
|
||||
help_text=_("This message will be shown after an order has been created successfully. It will be shown in additional "
|
||||
"to the default text."),
|
||||
widget_kwargs={'attrs': {'rows': '2'}},
|
||||
widget=I18nTextarea
|
||||
)
|
||||
},
|
||||
'checkout_phone_helptext': {
|
||||
'default': '',
|
||||
'type': LazyI18nString,
|
||||
|
||||
@@ -435,7 +435,6 @@ class EventSettingsForm(SettingsForm):
|
||||
'checkout_email_helptext',
|
||||
'presale_has_ended_text',
|
||||
'voucher_explanation_text',
|
||||
'checkout_success_text',
|
||||
'show_dates_on_frontpage',
|
||||
'show_date_to',
|
||||
'show_times',
|
||||
@@ -787,7 +786,6 @@ class MailSettingsForm(SettingsForm):
|
||||
'mail_from',
|
||||
'mail_from_name',
|
||||
'mail_attach_ical',
|
||||
'mail_attach_tickets',
|
||||
]
|
||||
|
||||
mail_sales_channel_placed_paid = forms.MultipleChoiceField(
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
{% bootstrap_field form.mail_from_name layout="control" %}
|
||||
{% bootstrap_field form.mail_text_signature layout="control" %}
|
||||
{% bootstrap_field form.mail_bcc layout="control" %}
|
||||
{% bootstrap_field form.mail_attach_tickets layout="control" %}
|
||||
{% bootstrap_field form.mail_attach_ical layout="control" %}
|
||||
{% bootstrap_field form.mail_sales_channel_placed_paid layout="control" %}
|
||||
</fieldset>
|
||||
|
||||
@@ -188,7 +188,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% bootstrap_field sform.checkout_success_text layout="control" %}
|
||||
{% bootstrap_field sform.checkout_email_helptext layout="control" %}
|
||||
{% bootstrap_field sform.checkout_phone_helptext layout="control" %}
|
||||
{% bootstrap_field sform.banner_text layout="control" %}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
{% load expiresformat %}
|
||||
{% load eventurl %}
|
||||
{% load phone_format %}
|
||||
{% load rich_text %}
|
||||
{% block title %}
|
||||
{% if "thanks" in request.GET or "paid" in request.GET %}
|
||||
{% trans "Thank you!" %}
|
||||
@@ -45,9 +44,6 @@
|
||||
{% else %}
|
||||
<p>{% trans "We successfully received your payment. See below for details." %}</p>
|
||||
{% endif %}
|
||||
{% if request.event.settings.checkout_success_text %}
|
||||
{{ request.event.settings.checkout_success_text|rich_text }}
|
||||
{% endif %}
|
||||
<p class="iframe-hidden">{% blocktrans trimmed %}
|
||||
Please bookmark or save the link to this exact page if you want to access your order later. We also sent you an email containing the link to the address you specified.
|
||||
{% endblocktrans %}</p>
|
||||
|
||||
@@ -14,6 +14,7 @@ from django.utils.timezone import get_current_timezone, now
|
||||
from django.views import View
|
||||
from django.views.decorators.cache import cache_page
|
||||
from django.views.generic import ListView, TemplateView
|
||||
from django_redis import get_redis_connection
|
||||
from pytz import UTC
|
||||
|
||||
from pretix.base.i18n import language
|
||||
@@ -366,35 +367,61 @@ def add_subevents_for_days(qs, before, after, ebd, timezones, event=None, cart_n
|
||||
'date_from'
|
||||
)
|
||||
quotas_to_compute = []
|
||||
quotas_by_event = defaultdict(list)
|
||||
for se in qs:
|
||||
if se.presale_is_running:
|
||||
quotas_to_compute += [
|
||||
q for q in se.active_quotas
|
||||
if not q.cache_is_hot(now() + timedelta(seconds=5))
|
||||
]
|
||||
quotas_by_event[se.event_id] += [
|
||||
q for q in se.active_quotas
|
||||
if not q.cache_is_hot(now() + timedelta(seconds=5))
|
||||
]
|
||||
|
||||
name = None
|
||||
qcache = {}
|
||||
if quotas_to_compute:
|
||||
qa = QuotaAvailability()
|
||||
qa.queue(*quotas_to_compute)
|
||||
qa.compute()
|
||||
for se in qs:
|
||||
if settings.HAS_REDIS:
|
||||
rc = get_redis_connection("redis")
|
||||
for ev, quotas in quotas_by_event.items():
|
||||
d = rc.hmget(f'quotas:{ev}:availability', [str(q.pk) for q in quotas])
|
||||
for redisval, q in zip(d, quotas):
|
||||
if redisval is not None and b',' in redisval:
|
||||
parts = redisval.decode().strip().split(',')
|
||||
if parts[0].isdigit() and parts[1] == "None":
|
||||
qcache[q] = (int(parts[0]), None)
|
||||
quotas_to_compute.remove(q)
|
||||
else:
|
||||
try:
|
||||
qcache[q] = tuple(int(rv) for rv in parts)
|
||||
quotas_to_compute.remove(q)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
if quotas_to_compute:
|
||||
se._quota_cache = qa.results
|
||||
qa = QuotaAvailability()
|
||||
qa.queue(*quotas_to_compute)
|
||||
qa.compute(dbcache=False)
|
||||
qcache.update(qa.results)
|
||||
|
||||
for se in qs:
|
||||
if qcache:
|
||||
se._quota_cache = qcache
|
||||
kwargs = {'subevent': se.pk}
|
||||
if cart_namespace:
|
||||
kwargs['cart_namespace'] = cart_namespace
|
||||
|
||||
settings = event.settings if event else se.event.settings
|
||||
timezones.add(settings.timezones)
|
||||
tz = pytz.timezone(settings.timezone)
|
||||
s = event.settings if event else se.event.settings
|
||||
timezones.add(s.timezones)
|
||||
tz = pytz.timezone(s.timezone)
|
||||
datetime_from = se.date_from.astimezone(tz)
|
||||
date_from = datetime_from.date()
|
||||
if name is None:
|
||||
name = str(se.name)
|
||||
elif str(se.name) != name:
|
||||
ebd['_subevents_different_names'] = True
|
||||
if se.event.settings.show_date_to and se.date_to:
|
||||
if s.show_date_to and se.date_to:
|
||||
datetime_to = se.date_to.astimezone(tz)
|
||||
date_to = se.date_to.astimezone(tz).date()
|
||||
d = max(date_from, before.date())
|
||||
@@ -402,13 +429,13 @@ def add_subevents_for_days(qs, before, after, ebd, timezones, event=None, cart_n
|
||||
first = d == date_from
|
||||
ebd[d].append({
|
||||
'continued': not first,
|
||||
'timezone': settings.timezone,
|
||||
'time': datetime_from.time().replace(tzinfo=None) if first and settings.show_times else None,
|
||||
'timezone': s.timezone,
|
||||
'time': datetime_from.time().replace(tzinfo=None) if first and s.show_times else None,
|
||||
'time_end': (
|
||||
datetime_to.time().replace(tzinfo=None)
|
||||
if (date_to == date_from or (
|
||||
date_to == date_from + timedelta(days=1) and datetime_to.time() < datetime_from.time()
|
||||
)) and settings.show_times
|
||||
)) and s.show_times
|
||||
else None
|
||||
),
|
||||
'event': se,
|
||||
@@ -420,9 +447,9 @@ def add_subevents_for_days(qs, before, after, ebd, timezones, event=None, cart_n
|
||||
ebd[date_from].append({
|
||||
'event': se,
|
||||
'continued': False,
|
||||
'time': datetime_from.time().replace(tzinfo=None) if se.event.settings.show_times else None,
|
||||
'time': datetime_from.time().replace(tzinfo=None) if s.show_times else None,
|
||||
'url': eventreverse(se.event, 'presale:event.index', kwargs=kwargs),
|
||||
'timezone': se.event.settings.timezone,
|
||||
'timezone': s.timezone,
|
||||
})
|
||||
|
||||
|
||||
|
||||
@@ -918,10 +918,12 @@ class SubEventsTest(SoupTest):
|
||||
assert doc.select(".alert-success")
|
||||
with scopes_disabled():
|
||||
for se in [self.subevent1, self.subevent2]:
|
||||
q = se.quotas.get(name='Q1')
|
||||
q = se.quotas.first()
|
||||
assert q.name == 'Q1'
|
||||
assert q.size == 50
|
||||
assert list(q.items.all()) == [self.ticket]
|
||||
q = se.quotas.get(name='Q2')
|
||||
q = se.quotas.last()
|
||||
assert q.name == 'Q2'
|
||||
assert q.size == 25
|
||||
assert list(q.items.all()) == [self.ticket]
|
||||
|
||||
@@ -929,8 +931,6 @@ class SubEventsTest(SoupTest):
|
||||
'__ALL': 'on',
|
||||
}, follow=True)
|
||||
fields = extract_form_fields(doc)
|
||||
assert fields['quotas-0-name'] == 'Q1'
|
||||
assert fields['quotas-1-name'] == 'Q2'
|
||||
fields.update({
|
||||
'_bulk': ['__quotas'],
|
||||
'quotas-0-size': '25',
|
||||
|
||||
Reference in New Issue
Block a user