Compare commits

...

27 Commits

Author SHA1 Message Date
Raphael Michel
d9ce40d0ac Update src/pretix/base/email.py
Co-authored-by: Richard Schreiber <wiffbi@gmail.com>
2022-08-11 21:40:45 +02:00
Raphael Michel
d62c7553c2 Add last order modification time as an email placeholder 2022-08-09 17:21:15 +02:00
Raphael Michel
4ce51b81ed Limit length of invoice number counter 2022-08-09 16:01:20 +02:00
Raphael Michel
f63fbaca4d Do not run control context processor for error pages 2022-08-09 09:53:43 +02:00
Raphael Michel
7bab5b16a2 Badges: Add paper size HERMA 40x40 2022-08-08 13:18:30 +02:00
Raphael Michel
bdb0a176da Bring back status code 400 for unknown domains 2022-08-08 10:20:32 +02:00
Raphael Michel
345a05e4ae Improve error message on mismatching host header on new installations 2022-08-08 10:19:32 +02:00
Raphael Michel
3bb590c1ae Ensure /favicon.ico does not raise Resolver404 on custom domains 2022-08-08 10:01:50 +02:00
Richard Schreiber
9a5e07e078 PDF-Editor: fix other/other-i18n input prefill bug (Z#23104692) (#2756) 2022-08-05 15:44:08 +02:00
dependabot[bot]
a563881659 Bump @babel/core from 7.18.6 to 7.18.10 in /src/pretix/static/npm_dir (#2759)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-08-05 10:00:05 +02:00
Raphael Michel
50ebda332a Bump PyPDF to version 2 (#2755) 2022-08-05 09:53:32 +02:00
dependabot[bot]
9ae25caf5c Bump vue and vue-template-compiler in /src/pretix/static/npm_dir (#2749)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-08-05 09:35:37 +02:00
Michael Stapelberg
a27a5c65aa Docker: Set AUTOMIGRATE=skip for subprocesses (#2742) 2022-08-05 09:34:52 +02:00
dependabot[bot]
395dbedbed Bump rollup from 2.75.7 to 2.77.2 in /src/pretix/static/npm_dir (#2751)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-08-05 09:34:21 +02:00
dependabot[bot]
441f32349f Bump @babel/preset-env from 7.18.6 to 7.18.9 in /src/pretix/static/npm_dir (#2750)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-08-05 09:31:30 +02:00
Michael Stapelberg
fd3311ed60 Set default for SITE_URL to localhost:8000 (#2757) 2022-08-05 09:31:22 +02:00
Raphael Michel
6cd8fb5b58 Order import: Add test for country code XK 2022-08-05 09:30:10 +02:00
Raphael Michel
62f1e8d64b Order import: Allow custom country codes (like XK) 2022-08-05 09:29:31 +02:00
Raphael Michel
4eca90e287 Order data exporter: Do not use same colum name twice on same sheet 2022-08-05 09:27:05 +02:00
Raphael Michel
e6c45e40a9 Add a delay to the navigation typeahead to prevent many lookups 2022-08-04 17:59:19 +02:00
Raphael Michel
0bb41cc44e Navigation typeahead: only match order numbers and vouchers at the beginning and only >3 characters 2022-08-04 17:55:25 +02:00
Raphael Michel
6c7f5f4888 Fix performance issue in sendmail_run_rules cronjob 2022-08-04 17:01:04 +02:00
Raphael Michel
c13d873f1f Invalidate tickets when item meta data is changed 2022-08-04 15:41:45 +02:00
Raphael Michel
bddeb35520 Don't crash if an order can't be paid manually
Fixes PRETIXEU-5Q6
2022-08-01 19:12:56 +02:00
Raphael Michel
9ce519bb0b Docs: Fix missing redirect from http to https 2022-08-01 13:07:06 +02:00
Raphael Michel
1f9b0b842f Docs: Clarify information on search= parameter for orders 2022-08-01 13:06:01 +02:00
Raphael Michel
69b482849a Bump to 4.13.0.dev0 2022-07-29 16:10:21 +02:00
34 changed files with 988 additions and 796 deletions

View File

@@ -2,6 +2,7 @@
file=/tmp/supervisor.sock
[supervisord]
environment = AUTOMIGRATE="skip"
logfile=/dev/stdout
logfile_maxbytes=0
loglevel=info

View File

@@ -240,6 +240,9 @@ The following snippet is an example on how to configure a nginx proxy for pretix
listen 80 default_server;
listen [::]:80 ipv6only=on default_server;
server_name pretix.mydomain.com;
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 default_server;

View File

@@ -225,6 +225,9 @@ The following snippet is an example on how to configure a nginx proxy for pretix
listen 80 default_server;
listen [::]:80 ipv6only=on default_server;
server_name pretix.mydomain.com;
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 default_server;

View File

@@ -428,7 +428,7 @@ List of all orders
``last_modified``, and ``status``. Default: ``datetime``
:query string code: Only return orders that match the given order code
:query string status: Only return orders in the given order status (see above)
:query string search: Only return orders matching a given search query
:query string search: Only return orders matching a given search query (matching for names, email addresses, and company names)
:query integer item: Only return orders with a position that contains this item ID. *Warning:* Result will also include orders if they contain mixed items, and it will even return orders where the item is only contained in a canceled position.
:query integer variation: Only return orders with a position that contains this variation ID. *Warning:* Result will also include orders if they contain mixed items and variations, and it will even return orders where the variation is only contained in a canceled position.
:query boolean testmode: Only return orders with ``testmode`` set to ``true`` or ``false``

View File

@@ -19,4 +19,4 @@
# 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/>.
#
__version__ = "4.12.0"
__version__ = "4.13.0.dev0"

View File

@@ -43,6 +43,7 @@ from pretix.base.i18n import (
LazyCurrencyNumber, LazyDate, LazyExpiresDate, LazyNumber,
)
from pretix.base.models import Event
from pretix.base.reldate import RelativeDateWrapper
from pretix.base.settings import PERSON_NAME_SCHEMES
from pretix.base.signals import (
register_html_mail_renderers, register_mail_placeholders,
@@ -469,6 +470,19 @@ def base_placeholders(sender, **kwargs):
}
),
),
SimpleFunctionalMailTextPlaceholder(
'order_modification_deadline_date_and_time', ['order', 'event'],
lambda order, event:
date_format(order.modify_deadline.astimezone(event.timezone), 'SHORT_DATETIME_FORMAT')
if order.modify_deadline
else '',
lambda event: date_format(
event.settings.get(
'last_order_modification_date', as_type=RelativeDateWrapper
).datetime(event).astimezone(event.timezone),
'SHORT_DATETIME_FORMAT'
) if event.settings.get('last_order_modification_date') else '',
),
SimpleFunctionalMailTextPlaceholder(
'event_location', ['event_or_subevent'], lambda event_or_subevent: str(event_or_subevent.location or ''),
lambda event: str(event.location or ''),

View File

@@ -610,7 +610,10 @@ class OrderListExporter(MultiSheetListExporter):
for k, label, w in name_scheme['fields']:
headers.append(_('Invoice address name') + ': ' + str(label))
headers += [
_('Address'), _('ZIP code'), _('City'), _('Country'), pgettext('address', 'State'), _('VAT ID'),
_('Invoice address street'), _('Invoice address ZIP code'), _('Invoice address city'),
_('Invoice address country'),
pgettext('address', 'Invoice address state'),
_('VAT ID'),
]
headers += [
_('Sales channel'), _('Order locale'),

View File

@@ -746,6 +746,19 @@ class Order(LockModel, LoggedModel):
length += 1
iteration = 0
@property
def modify_deadline(self):
modify_deadline = self.event.settings.get('last_order_modification_date', as_type=RelativeDateWrapper)
if self.event.has_subevents and modify_deadline:
dates = [
modify_deadline.datetime(se)
for se in self.event.subevents.filter(id__in=self.positions.values_list('subevent', flat=True))
]
return min(dates) if dates else None
elif modify_deadline:
return modify_deadline.datetime(self.event)
return None
@property
def can_modify_answers(self) -> bool:
"""
@@ -758,16 +771,7 @@ class Order(LockModel, LoggedModel):
if self.status not in (Order.STATUS_PENDING, Order.STATUS_PAID, Order.STATUS_EXPIRED):
return False
modify_deadline = self.event.settings.get('last_order_modification_date', as_type=RelativeDateWrapper)
if self.event.has_subevents and modify_deadline:
dates = [
modify_deadline.datetime(se)
for se in self.event.subevents.filter(id__in=self.positions.values_list('subevent', flat=True))
]
modify_deadline = min(dates) if dates else None
elif modify_deadline:
modify_deadline = modify_deadline.datetime(self.event)
modify_deadline = self.modify_deadline
if modify_deadline is not None and now() > modify_deadline:
return False

View File

@@ -392,7 +392,7 @@ class InvoiceAddressCountry(ImportColumn):
return list(countries)
def clean(self, value, previous_values):
if value and not Country(value).numeric:
if value and not (Country(value).numeric or value in settings.COUNTRIES_OVERRIDE):
raise ValidationError(_("Please enter a valid country code."))
return value
@@ -538,7 +538,7 @@ class AttendeeCountry(ImportColumn):
return list(countries)
def clean(self, value, previous_values):
if value and not Country(value).numeric:
if value and not (Country(value).numeric or value in settings.COUNTRIES_OVERRIDE):
raise ValidationError(_("Please enter a valid country code."))
return value

View File

@@ -57,7 +57,7 @@ from django.utils.html import conditional_escape
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _, pgettext
from i18nfield.strings import LazyI18nString
from PyPDF2 import PdfFileReader
from PyPDF2 import PdfReader
from pytz import timezone
from reportlab.graphics import renderPDF
from reportlab.graphics.barcode.qr import QrCodeWidget
@@ -646,7 +646,7 @@ class Renderer:
self.event = event
if self.background_file:
self.bg_bytes = self.background_file.read()
self.bg_pdf = PdfFileReader(BytesIO(self.bg_bytes), strict=False)
self.bg_pdf = PdfReader(BytesIO(self.bg_bytes), strict=False)
else:
self.bg_bytes = None
self.bg_pdf = None
@@ -861,7 +861,7 @@ class Renderer:
canvas.restoreState()
def draw_page(self, canvas: Canvas, order: Order, op: OrderPosition, show_page=True, only_page=None):
page_count = self.bg_pdf.getNumPages()
page_count = len(self.bg_pdf.pages)
if not only_page and not show_page:
raise ValueError("only_page=None and show_page=False cannot be combined")
@@ -881,7 +881,7 @@ class Renderer:
elif o['type'] == "poweredby":
self._draw_poweredby(canvas, op, o)
if self.bg_pdf:
canvas.setPageSize((self.bg_pdf.getPage(page).mediaBox[2], self.bg_pdf.getPage(page).mediaBox[3]))
canvas.setPageSize((self.bg_pdf.pages[0].mediabox[2], self.bg_pdf.pages[0].mediabox[3]))
if show_page:
canvas.showPage()
@@ -905,17 +905,17 @@ class Renderer:
with open(os.path.join(d, 'out.pdf'), 'rb') as f:
return BytesIO(f.read())
else:
from PyPDF2 import PdfFileReader, PdfFileWriter
from PyPDF2 import PdfReader, PdfWriter
buffer.seek(0)
new_pdf = PdfFileReader(buffer)
output = PdfFileWriter()
new_pdf = PdfReader(buffer)
output = PdfWriter()
for i, page in enumerate(new_pdf.pages):
bg_page = copy.copy(self.bg_pdf.getPage(i))
bg_page.mergePage(page)
output.addPage(bg_page)
bg_page = copy.copy(self.bg_pdf.pages[i])
bg_page.merge_page(page)
output.add_page(bg_page)
output.addMetadata({
output.add_metadata({
'/Title': str(title),
'/Creator': 'pretix',
})

View File

@@ -57,6 +57,7 @@ from django_countries.fields import Country
from hierarkey.models import GlobalSettingsBase, Hierarkey
from i18nfield.forms import I18nFormField, I18nTextarea, I18nTextInput
from i18nfield.strings import LazyI18nString
from phonenumbers import PhoneNumber, parse
from rest_framework import serializers
from pretix.api.serializers.fields import (
@@ -3030,6 +3031,7 @@ settings_hierarkey.add_type(LazyI18nStringList,
settings_hierarkey.add_type(RelativeDateWrapper,
serialize=lambda rdw: rdw.to_string(),
unserialize=lambda s: RelativeDateWrapper.from_string(s))
settings_hierarkey.add_type(PhoneNumber, lambda pn: pn.as_international, lambda s: parse(s))
@settings_hierarkey.set_global(cache_namespace='global')

View File

@@ -0,0 +1,52 @@
{% extends "error.html" %}
{% load i18n %}
{% load static %}
{% block title %}{% trans "Unknown host" %}{% endblock %}
{% block content %}
<i class="fa fa-question-circle-o fa-fw big-icon"></i>
<div class="error-details">
<h1>{% trans "Unknown host" %}</h1>
<p>
{% blocktrans trimmed with host=header_host %}
Your browser told us that you want to access "{{ header_host }}". Unfortunately, we don't have
any content for this domain.
{% endblocktrans %}
</p>
{% if is_fresh_install %}
<p>
{% blocktrans trimmed %}
It looks like this is a fresh installation of pretix. This error message is probably caused due to
the fact that either your configuration includes the wrong site URL or your reverse proxy is sending
the wrong header.
{% endblocktrans %}
</p>
<dl>
<dt>{% trans "Expected host according to configuration" %}</dt>
<dd><code>{{ site_host }}</code></dd>
<dt>{% trans "Received headers" %}</dt>
<dd>
<code>Host: {{ request.headers.Host }}</code>
{% if xfh %}
<br>
<code>X-Forwarded-For: {{ xfh }}</code>
{% if not settings.USE_X_FORWARDED_HOST %}({% trans "ignored" %}){% endif %}
{% endif %}
</dd>
<dt>{% trans "Derived host from headers" %}</dt>
<dd><code>{{ header_host }}</code></dd>
</dl>
{% else %}
<p>
{% blocktrans trimmed %}
If you just configured this as a domain for your ticket shop, you now need to set this up as a "custom domain"
in your organizer account.
{% endblocktrans %}
</p>
{% endif %}
<p class="links">
<a id='goback' href='#'>{% trans "Take a step back" %}</a>
&middot; <a id='reload' href='#'>{% trans "Try again" %}</a>
</p>
<img src="{% static "pretixbase/img/pretix-logo.svg" %}" class="logo"/>
</div>
{% endblock %}

View File

@@ -71,7 +71,7 @@ def _default_context(request):
except Resolver404:
return {}
if not request.path.startswith(get_script_prefix() + 'control'):
if not request.path.startswith(get_script_prefix() + 'control') or not hasattr(request, 'user'):
return {}
ctx = {
'url_name': url.url_name,

View File

@@ -39,7 +39,7 @@ from urllib.parse import urlencode, urlparse
from django import forms
from django.conf import settings
from django.core.exceptions import ValidationError
from django.core.validators import validate_email
from django.core.validators import MaxValueValidator, validate_email
from django.db.models import Prefetch, Q, prefetch_related_objects
from django.forms import (
CheckboxSelectMultiple, formset_factory, inlineformset_factory,
@@ -848,6 +848,7 @@ class InvoiceSettingsForm(SettingsForm):
self.fields['invoice_generate_sales_channels'].choices = (
(c.identifier, c.verbose_name) for c in get_all_sales_channels().values()
)
self.fields['invoice_numbers_counter_length'].validators.append(MaxValueValidator(15))
def clean(self):
data = super().clean()

View File

@@ -1344,22 +1344,36 @@ class ItemUpdateGeneral(ItemDetailMixin, EventPermissionRequiredMixin, MetaDataE
def form_valid(self, form):
self.save_meta()
messages.success(self.request, _('Your changes have been saved.'))
if form.has_changed() or any(f.has_changed() for f in self.plugin_forms):
data = {
k: form.cleaned_data.get(k)
for k in form.changed_data
}
for f in self.plugin_forms:
data.update({
k: (f.cleaned_data.get(k).name
if isinstance(f.cleaned_data.get(k), File)
else f.cleaned_data.get(k))
for k in f.changed_data
})
change_data = {
k: form.cleaned_data.get(k)
for k in form.changed_data
}
for f in self.plugin_forms:
change_data.update({
k: (f.cleaned_data.get(k).name
if isinstance(f.cleaned_data.get(k), File)
else f.cleaned_data.get(k))
for k in f.changed_data
})
meta_changed = {}
for f in self.meta_forms:
meta_changed.update({
k: (f.cleaned_data.get(k).name
if isinstance(f.cleaned_data.get(k), File)
else f.cleaned_data.get(k))
for k in f.changed_data
})
if meta_changed:
change_data['meta_data'] = meta_changed
if change_data:
self.object.log_action(
'pretix.event.item.changed', user=self.request.user, data=data
'pretix.event.item.changed', user=self.request.user, data=change_data
)
invalidate_cache.apply_async(kwargs={'event': self.request.event.pk, 'item': self.object.pk})
for f in self.plugin_forms:
f.save()

View File

@@ -1186,13 +1186,17 @@ class OrderTransition(OrderView):
if ps == Decimal('0.00') and self.order.pending_sum <= Decimal('0.00'):
p = self.order.payments.filter(state=OrderPayment.PAYMENT_STATE_CONFIRMED).last()
if p:
p._mark_order_paid(
user=self.request.user,
send_mail=self.mark_paid_form.cleaned_data['send_email'],
force=self.mark_paid_form.cleaned_data.get('force', False),
payment_refund_sum=self.order.payment_refund_sum,
)
messages.success(self.request, _('The order has been marked as paid.'))
try:
p._mark_order_paid(
user=self.request.user,
send_mail=self.mark_paid_form.cleaned_data['send_email'],
force=self.mark_paid_form.cleaned_data.get('force', False),
payment_refund_sum=self.order.payment_refund_sum,
)
except Quota.QuotaExceededException as e:
messages.error(self.request, str(e))
else:
messages.success(self.request, _('The order has been marked as paid.'))
return redirect(self.get_order_url())
try:

View File

@@ -39,8 +39,8 @@ from django.utils.crypto import get_random_string
from django.utils.timezone import now
from django.utils.translation import gettext as _
from django.views.generic import TemplateView
from PyPDF2 import PdfFileReader, PdfFileWriter
from PyPDF2.utils import PdfReadError
from PyPDF2 import PdfReader, PdfWriter
from PyPDF2.errors import PdfReadError
from reportlab.lib.units import mm
from pretix.base.i18n import language
@@ -153,9 +153,9 @@ class BaseEditorView(EventPermissionRequiredMixin, TemplateView):
def post(self, request, *args, **kwargs):
if "emptybackground" in request.POST:
p = PdfFileWriter()
p = PdfWriter()
try:
p.addBlankPage(
p.add_blank_page(
width=float(request.POST.get('width')) * mm,
height=float(request.POST.get('height')) * mm,
)
@@ -203,7 +203,7 @@ class BaseEditorView(EventPermissionRequiredMixin, TemplateView):
try:
bg_bytes = c.file.read()
PdfFileReader(BytesIO(bg_bytes), strict=False)
PdfReader(BytesIO(bg_bytes), strict=False)
except PdfReadError as e:
return JsonResponse({
"status": "error",

View File

@@ -228,9 +228,9 @@ def nav_context_list(request):
if query:
qs_orga = qs_orga.filter(Q(name__icontains=query) | Q(slug__icontains=query))
if query:
if query and len(query) >= 3:
qs_orders = Order.objects.filter(
code__icontains=query
code__istartswith=query
).select_related('event', 'event__organizer').only('event', 'code', 'pk').order_by()
if not request.user.has_active_staff_session(request.session.session_key):
qs_orders = qs_orders.filter(
@@ -241,7 +241,7 @@ def nav_context_list(request):
)
qs_vouchers = Voucher.objects.filter(
code__icontains=query
code__istartswith=query
).select_related('event', 'event__organizer').only('event', 'code', 'pk').order_by()
if not request.user.has_active_staff_session(request.session.session_key):
qs_vouchers = qs_vouchers.filter(

View File

@@ -43,6 +43,7 @@ from django.core.cache import cache
from django.core.exceptions import DisallowedHost
from django.http.request import split_domain_port
from django.middleware.csrf import CsrfViewMiddleware as BaseCsrfMiddleware
from django.shortcuts import render
from django.urls import set_urlconf
from django.utils.cache import patch_vary_headers
from django.utils.deprecation import MiddlewareMixin
@@ -112,7 +113,15 @@ class MultiDomainMiddleware(MiddlewareMixin):
elif settings.DEBUG or domain in LOCAL_HOST_NAMES:
request.urlconf = "pretix.multidomain.maindomain_urlconf"
else:
raise DisallowedHost("Unknown host: %r" % host)
with scopes_disabled():
is_fresh_install = not Event.objects.exists()
return render(request, '400_hostname.html', {
'header_host': domain,
'site_host': default_domain,
'settings': settings,
'xfh': request.headers.get('X-Forwarded-Host'),
'is_fresh_install': is_fresh_install,
}, status=400)
else:
raise DisallowedHost("Invalid HTTP_HOST header: %r." % host)

View File

@@ -48,6 +48,7 @@ from django.db.models import Exists, OuterRef, Q
from django.db.models.functions import Coalesce
from django.utils.timezone import make_aware
from django.utils.translation import gettext as _, gettext_lazy
from PyPDF2 import Transformation
from reportlab.lib import pagesizes
from reportlab.lib.units import mm
from reportlab.pdfgen import canvas
@@ -153,11 +154,19 @@ OPTIONS = OrderedDict([
'offsets': [95 * mm, 55 * mm],
'pagesize': pagesizes.A4,
}),
('herma_40x40', {
'name': 'HERMA 40 x 40 mm (9642)',
'cols': 4,
'rows': 6,
'margins': [13.5 * mm, 15 * mm, 13.5 * mm, 15 * mm],
'offsets': [46 * mm, 46 * mm],
'pagesize': pagesizes.A4,
}),
])
def render_pdf(event, positions, opt):
from PyPDF2 import PdfFileReader, PdfFileWriter
from PyPDF2 import PdfReader, PdfWriter
Renderer._register_fonts()
renderermap = {
@@ -168,7 +177,7 @@ def render_pdf(event, positions, opt):
default_renderer = _renderer(event, event.badge_layouts.get(default=True))
except BadgeLayout.DoesNotExist:
default_renderer = None
output_pdf_writer = PdfFileWriter()
output_pdf_writer = PdfWriter()
any = False
npp = opt['cols'] * opt['rows']
@@ -189,22 +198,19 @@ def render_pdf(event, positions, opt):
p.showPage()
p.save()
buffer.seek(0)
canvas_pdf_reader = PdfFileReader(buffer)
empty_pdf_page = output_pdf_writer.addBlankPage(
width=opt['pagesize'][0] if opt['pagesize'] else positions[0][1].bg_pdf.getPage(0).mediaBox[2],
height=opt['pagesize'][1] if opt['pagesize'] else positions[0][1].bg_pdf.getPage(0).mediaBox[3],
canvas_pdf_reader = PdfReader(buffer)
empty_pdf_page = output_pdf_writer.add_blank_page(
width=opt['pagesize'][0] if opt['pagesize'] else positions[0][1].bg_pdf.pages[0].mediabox[2],
height=opt['pagesize'][1] if opt['pagesize'] else positions[0][1].bg_pdf.pages[0].mediabox[3],
)
for i, (op, r) in enumerate(positions):
bg_page = copy.copy(r.bg_pdf.getPage(0))
bg_page.trimBox = bg_page.mediaBox
bg_page = copy.copy(r.bg_pdf.pages[0])
bg_page.trimbox = bg_page.mediabox
offsetx = opt['margins'][3] + (i % opt['cols']) * opt['offsets'][0]
offsety = opt['margins'][2] + (opt['rows'] - 1 - i // opt['cols']) * opt['offsets'][1]
empty_pdf_page.mergeTranslatedPage(
bg_page,
tx=offsetx,
ty=offsety
)
empty_pdf_page.mergePage(canvas_pdf_reader.getPage(0))
bg_page.add_transformation(Transformation().translate(offsetx, offsety))
empty_pdf_page.merge_page(bg_page)
empty_pdf_page.merge_page(canvas_pdf_reader.pages[0])
pagebuffer = []
outbuffer = BytesIO()
@@ -221,7 +227,7 @@ def render_pdf(event, positions, opt):
if pagebuffer:
render_page(pagebuffer)
output_pdf_writer.addMetadata({
output_pdf_writer.add_metadata({
'/Title': 'Badges',
'/Creator': 'pretix',
})

View File

@@ -42,6 +42,7 @@ from django.db.models.signals import post_save
from django.dispatch import receiver
from django.urls import resolve, reverse
from django.utils import timezone
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from django_scopes import scope, scopes_disabled
@@ -134,6 +135,7 @@ def sendmail_run_rules(sender, **kwargs):
with scopes_disabled():
mails = ScheduledMail.objects.all()
unchanged = []
for m in mails.filter(Q(last_computed__isnull=True)
| Q(subevent__last_modified__gt=F('last_computed'))
| Q(event__last_modified__gt=F('last_computed'))):
@@ -141,6 +143,17 @@ def sendmail_run_rules(sender, **kwargs):
m.recompute()
if m.computed_datetime != previous:
m.save(update_fields=['last_computed', 'computed_datetime'])
else:
unchanged.append(m.pk)
if unchanged:
# Theoretically, we don't need to write back the unchanged ones to the database… but that will cause us to
# recompute them on every run until eternity. So we want to set their last_computed date to something more
# recent... but not for all of them at once, in case it's millions, so we don't stress the database without
# cause
batch_size = max(connection.ops.bulk_batch_size(['id'], unchanged) - 2, 100)
for i in range(max(1, 5000 // batch_size)):
ScheduledMail.objects.filter(pk__in=unchanged[i * batch_size:batch_size]).update(last_computed=now())
mails.filter(
state=ScheduledMail.STATE_SCHEDULED,

View File

@@ -43,7 +43,7 @@ from django.db.models import Q
from django.db.models.functions import Coalesce
from django.utils.timezone import make_aware
from django.utils.translation import gettext as _, gettext_lazy
from PyPDF2.merger import PdfFileMerger
from PyPDF2 import PdfMerger
from pretix.base.exporter import BaseExporter
from pretix.base.i18n import language
@@ -105,7 +105,7 @@ class AllTicketsPDF(BaseExporter):
return d
def render(self, form_data):
merger = PdfFileMerger()
merger = PdfMerger()
qs = OrderPosition.objects.filter(
order__event__in=self.events
).prefetch_related(

View File

@@ -44,7 +44,7 @@ from django.http import HttpRequest
from django.template.loader import get_template
from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _
from PyPDF2 import PdfFileMerger
from PyPDF2 import PdfMerger
from pretix.base.i18n import language
from pretix.base.models import Order, OrderPosition
@@ -112,7 +112,7 @@ class PdfTicketOutput(BaseTicketOutput):
return renderer.render_background(buffer, _('Ticket'))
def generate_order(self, order: Order):
merger = PdfFileMerger()
merger = PdfMerger()
with language(order.locale, self.event.settings.region):
for op in order.positions_with_tickets:
layout = override_layout.send_chained(

View File

@@ -93,6 +93,10 @@ event_patterns = [
name='event.payment.unlock'),
re_path(r'resend/$', pretix.presale.views.user.ResendLinkView.as_view(), name='event.resend_link'),
re_path(r'^favicon.ico/?$',
pretix.presale.views.organizer.OrganizerFavicon.as_view(),
name='event.favicon'),
re_path(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/open/(?P<hash>[a-z0-9]+)/$', pretix.presale.views.order.OrderOpen.as_view(),
name='event.order.open'),
re_path(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/$', pretix.presale.views.order.OrderDetails.as_view(),
@@ -164,6 +168,9 @@ event_patterns = [
organizer_patterns = [
re_path(r'^$', pretix.presale.views.organizer.OrganizerIndex.as_view(), name='organizer.index'),
re_path(r'^favicon.ico/?$',
pretix.presale.views.organizer.OrganizerFavicon.as_view(),
name='organizer.favicon'),
re_path(r'^events/ical/$',
pretix.presale.views.organizer.OrganizerIcalDownload.as_view(),
name='organizer.ical'),

View File

@@ -48,6 +48,7 @@ from django.db.models import Exists, Max, Min, OuterRef, Prefetch, Q
from django.db.models.functions import Coalesce, Greatest
from django.http import Http404, HttpResponse
from django.shortcuts import redirect
from django.templatetags.static import static
from django.utils.decorators import method_decorator
from django.utils.formats import date_format, get_format
from django.utils.timezone import get_current_timezone, now
@@ -66,6 +67,7 @@ from pretix.helpers.daterange import daterange
from pretix.helpers.formats.en.formats import (
SHORT_MONTH_DAY_FORMAT, WEEK_FORMAT,
)
from pretix.helpers.thumb import get_thumbnail
from pretix.multidomain.urlreverse import eventreverse
from pretix.presale.ical import get_public_ical
from pretix.presale.views import OrganizerViewMixin
@@ -1170,3 +1172,11 @@ class OrganizerIcalDownload(OrganizerViewMixin, View):
if request.organizer.settings.meta_noindex:
resp['X-Robots-Tag'] = 'noindex'
return resp
class OrganizerFavicon(View):
def get(self, *args, **kwargs):
if self.request.organizer.settings.favicon:
return redirect(get_thumbnail(self.request.organizer.settings.favicon, '32x32^').thumb.url)
else:
return redirect(static("pretixbase/img/favicon.ico"))

View File

@@ -170,7 +170,7 @@ PRETIX_SESSION_TIMEOUT_RELATIVE = 3600 * 3
PRETIX_SESSION_TIMEOUT_ABSOLUTE = 3600 * 12
PRETIX_PRIMARY_COLOR = '#8E44B3'
SITE_URL = config.get('pretix', 'url', fallback='http://localhost')
SITE_URL = config.get('pretix', 'url', fallback='http://localhost:8000')
if SITE_URL.endswith('/'):
SITE_URL = SITE_URL[:-1]

File diff suppressed because it is too large Load Diff

View File

@@ -4,13 +4,13 @@
"private": true,
"scripts": {},
"dependencies": {
"@babel/core": "^7.18.6",
"@babel/preset-env": "^7.18.6",
"@babel/core": "^7.18.10",
"@babel/preset-env": "^7.18.9",
"@rollup/plugin-babel": "^5.3.1",
"@rollup/plugin-node-resolve": "^13.3.0",
"vue": "^2.7.0",
"rollup": "^2.75.7",
"vue": "^2.7.8",
"rollup": "^2.77.2",
"rollup-plugin-vue": "^5.0.1",
"vue-template-compiler": "^2.7.0"
"vue-template-compiler": "^2.7.8"
}
}

View File

@@ -300,7 +300,6 @@ var editor = {
// Fetch the required page
editor.pdf.getPage(page_number).then(function (page) {
console.log('Page loaded');
var canvas = document.getElementById('pdf-canvas');
var scale = editor.$cva.width() / page.getViewport(1.0).width;
@@ -326,7 +325,6 @@ var editor = {
editor.pdf_page_number = page_number
editor._init_page_nav();
console.log('Page rendered');
if (dump || !editor._fabric_loaded) {
editor._init_fabric(dump);
} else {
@@ -349,7 +347,6 @@ var editor = {
}
$("#page_nav").append($li)
$a.on("click", function (event) {
console.log("switch to page", $(this).attr("data-page"));
editor.fabric.deactivateAll();
editor._load_page(parseInt($(this).attr("data-page")));
event.preventDefault();
@@ -369,7 +366,6 @@ var editor = {
// Asynchronous download of PDF
var loadingTask = PDFJS.getDocument(url);
loadingTask.promise.then(function (pdf) {
console.log('PDF loaded');
editor.pdf = pdf;
editor.pdf_page_count = pdf.numPages;
@@ -422,7 +418,6 @@ var editor = {
}
editor._fabric_loaded = true;
console.log("Fabric loaded");
if (editor._window_loaded) {
editor._ready();
}
@@ -430,7 +425,6 @@ var editor = {
_window_load_event: function () {
editor._window_loaded = true;
console.log("Window loaded");
if (editor._fabric_loaded) {
editor._ready();
}
@@ -520,7 +514,7 @@ var editor = {
}
},
_update_values_from_toolbox: function () {
_update_values_from_toolbox: function (e) {
var o = editor.fabric.getActiveObject();
if (!o) {
o = editor.fabric.getActiveGroup();
@@ -553,8 +547,16 @@ var editor = {
$("#toolbox-content-other-help").toggle($("#toolbox-content").val() === "other" || $("#toolbox-content").val() === "other_i18n");
o.content = $("#toolbox-content").val();
if ($("#toolbox-content").val() === "other") {
if (e.target.id === "toolbox-content") {
// user used dropdown to switch content-type, update value with value from i18n textarea
$("#toolbox-content-other").val($("#toolbox-content-other-i18n textarea").val());
}
o.text = $("#toolbox-content-other").val();
} else if ($("#toolbox-content").val() === "other_i18n") {
if (e.target.id === "toolbox-content") {
// user used dropdown to switch content-type, update value with value from "other" textarea
$("#toolbox-content-other-i18n textarea").val($("#toolbox-content-other").val());
}
o.text_i18n = {}
$("#toolbox-content-other-i18n textarea").each(function () {
o.text_i18n[$(this).attr("lang")] = $(this).val();
@@ -608,8 +610,16 @@ var editor = {
$("#toolbox-content-other-help").toggle($("#toolbox-content").val() === "other" || $("#toolbox-content").val() === "other_i18n");
o.content = $("#toolbox-content").val();
if ($("#toolbox-content").val() === "other") {
if (e.target.id === "toolbox-content") {
// user used dropdown to switch content-type, update value with value from i18n textarea
$("#toolbox-content-other").val($("#toolbox-content-other-i18n textarea").val());
}
o.setText($("#toolbox-content-other").val());
} else if ($("#toolbox-content").val() === "other_i18n") {
if (e.target.id === "toolbox-content") {
// user used dropdown to switch content-type, update value with value from "other" textarea
$("#toolbox-content-other-i18n textarea").val($("#toolbox-content-other").val());
}
o.text_i18n = {}
$("#toolbox-content-other-i18n textarea").each(function () {
o.text_i18n[$(this).attr("lang")] = $(this).val();
@@ -620,6 +630,14 @@ var editor = {
}
}
// empty text-inputs if not in use
if ($("#toolbox-content").val() !== "other") {
$("#toolbox-content-other").val("");
}
if ($("#toolbox-content").val() !== "other_i18n") {
$("#toolbox-content-other-i18n textarea").val("");
}
o.setCoords();
editor.fabric.renderAll();
},
@@ -1055,14 +1073,14 @@ var editor = {
$("#toolbox label.btn").bind('click change', editor._update_values_from_toolbox);
$("#toolbox select").bind('change', editor._update_values_from_toolbox);
$("#toolbox select").bind('change', editor._create_savepoint);
$("#toolbox button.toggling").bind('click change', function () {
$("#toolbox button.toggling").bind('click change', function (e) {
if ($(this).is(".option")) {
$(this).addClass("active");
$(this).parent().siblings().find("button").removeClass("active");
} else {
$(this).toggleClass("active");
}
editor._update_values_from_toolbox();
editor._update_values_from_toolbox(e);
editor._create_savepoint();
});
$("#toolbox .colorpickerfield").bind('changeColor', editor._update_values_from_toolbox);

View File

@@ -12,13 +12,8 @@ $(function () {
var $query = $(this).find('[data-typeahead-query]').length ? $(this).find('[data-typeahead-query]') : $($(this).attr("data-typeahead-field"));
$container.find("li:not(.query-holder)").remove();
var lastQuery = "";
$query.on("change", function () {
if ($container.attr("data-typeahead-field") && $query.val() === "") {
$container.removeClass('focused');
$container.find("li:not(.query-holder)").remove();
return;
}
var runQueryTimeout = null;
function runQuery() {
lastQuery = $query.val();
var thisQuery = $query.val();
$.getJSON(
@@ -119,6 +114,17 @@ $(function () {
$container.toggleClass('focused', $query.is(":focus") && $container.children().length > 0);
}
);
}
$query.on("change", function () {
if ($container.attr("data-typeahead-field") && $query.val() === "") {
$container.removeClass('focused');
$container.find("li:not(.query-holder)").remove();
return;
}
if (runQueryTimeout != null) {
window.clearTimeout(runQueryTimeout)
}
runQueryTimeout = window.setTimeout(runQuery, 250)
});
$query.on("keydown", function (event) {
var $selected = $container.find(".active");

View File

@@ -211,7 +211,7 @@ setup(
'psycopg2-binary',
'pycountry',
'pycparser==2.21',
'PyPDF2==1.27.9',
'PyPDF2==2.9.*',
'python-bidi==0.4.*', # Support for Arabic in reportlab
'python-dateutil==2.8.*',
'python-u2flib-server==4.*',

View File

@@ -93,7 +93,7 @@ def inputfile_factory():
'D': 'Test',
'E': 'Baz',
'F': '0.00',
'G': 'AU',
'G': 'XK',
'H': '',
'I': 'Foo,Bar',
'J': '2021-06-28 11:00:00',

View File

@@ -39,7 +39,7 @@ from io import BytesIO
import pytest
from django.utils.timezone import now
from django_scopes import scope
from PyPDF2 import PdfFileReader
from PyPDF2 import PdfReader
from pretix.base.models import (
Event, Item, ItemVariation, Order, OrderPosition, Organizer,
@@ -100,8 +100,8 @@ def test_generate_pdf(env):
'include_pending': True
})
assert ftype == 'application/pdf'
pdf = PdfFileReader(BytesIO(buf))
assert pdf.numPages == 2
pdf = PdfReader(BytesIO(buf))
assert len(pdf.pages) == 2
@pytest.mark.django_db
@@ -115,5 +115,5 @@ def test_generate_pdf_multi(env):
'include_pending': True
})
assert ftype == 'application/pdf'
pdf = PdfFileReader(BytesIO(buf))
assert pdf.numPages == 1
pdf = PdfReader(BytesIO(buf))
assert len(pdf.pages) == 1

View File

@@ -26,7 +26,7 @@ from io import BytesIO
import pytest
from django.utils.timezone import now
from django_scopes import scope
from PyPDF2 import PdfFileReader
from PyPDF2 import PdfReader
from pretix.base.models import (
Event, Item, ItemVariation, Order, OrderPosition, Organizer,
@@ -70,5 +70,5 @@ def test_generate_pdf(env0):
o = PdfTicketOutput(event)
fname, ftype, buf = o.generate(order.positions.first())
assert ftype == 'application/pdf'
pdf = PdfFileReader(BytesIO(buf))
assert pdf.numPages == 1
pdf = PdfReader(BytesIO(buf))
assert len(pdf.pages) == 1