Compare commits

..

7 Commits

Author SHA1 Message Date
Raphael Michel
53cd098a25 Bump to 2023.6.3 2023-09-12 12:04:25 +02:00
Raphael Michel
bdd474a0f1 Move new settings to _base_settings 2023-09-12 12:04:22 +02:00
Raphael Michel
0b6b0829ed Bump to 2023.6.2 2023-09-12 11:51:53 +02:00
Raphael Michel
bedeaef7da [SECURITY] Do not allow Pillow to parse EPS files 2023-09-12 11:51:37 +02:00
Raphael Michel
669f622ce6 Bump to 2023.6.1 2023-09-11 09:59:57 +02:00
Raphael Michel
d166c7ee68 Fix incorrect handling of boolean configuration flags 2023-09-11 09:59:44 +02:00
Raphael Michel
0282cfbdd5 Dockerfile: Remove broken npm installation line 2023-06-27 23:22:07 +02:00
41 changed files with 139 additions and 596 deletions

View File

@@ -32,8 +32,6 @@ as well as the type of underlying hardware. Example:
"token": "kpp4jn8g2ynzonp6",
"hardware_brand": "Samsung",
"hardware_model": "Galaxy S",
"os_name": "Android",
"os_version": "2.3.6",
"software_brand": "pretixdroid",
"software_version": "4.0.0"
}
@@ -100,8 +98,6 @@ following endpoint:
{
"hardware_brand": "Samsung",
"hardware_model": "Galaxy S",
"os_name": "Android",
"os_version": "2.3.6",
"software_brand": "pretixdroid",
"software_version": "4.1.0",
"info": {"arbitrary": "data"}

View File

@@ -24,8 +24,6 @@ all_events boolean Whether this de
limit_events list List of event slugs this device has access to
hardware_brand string Device hardware manufacturer (read-only)
hardware_model string Device hardware model (read-only)
os_name string Device operating system name (read-only)
os_version string Device operating system version (read-only)
software_brand string Device software product (read-only)
software_version string Device software version (read-only)
created datetime Creation time
@@ -78,8 +76,6 @@ Device endpoints
"security_profile": "full",
"hardware_brand": "Zebra",
"hardware_model": "TC25",
"os_name": "Android",
"os_version": "8.1.0",
"software_brand": "pretixSCAN",
"software_version": "1.5.1"
}
@@ -127,8 +123,6 @@ Device endpoints
"security_profile": "full",
"hardware_brand": "Zebra",
"hardware_model": "TC25",
"os_name": "Android",
"os_version": "8.1.0",
"software_brand": "pretixSCAN",
"software_version": "1.5.1"
}
@@ -179,8 +173,6 @@ Device endpoints
"initialized": null
"hardware_brand": null,
"hardware_model": null,
"os_name": null,
"os_version": null,
"software_brand": null,
"software_version": null
}

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__ = "2023.7.0.dev0"
__version__ = "2023.6.3"

View File

@@ -252,3 +252,20 @@ PRETIX_PRIMARY_COLOR = '#8E44B3'
# stressful for some cache setups so it is enabled by default and currently can't be enabled through pretix.cfg
CACHE_LARGE_VALUES_ALLOWED = False
CACHE_LARGE_VALUES_ALIAS = 'default'
# Allowed file extensions for various places plus matching Pillow formats.
# Never allow EPS, it is full of dangerous bugs.
FILE_UPLOAD_EXTENSIONS_IMAGE = (".png", ".jpg", ".gif", ".jpeg")
PILLOW_FORMATS_IMAGE = ('PNG', 'GIF', 'JPEG')
FILE_UPLOAD_EXTENSIONS_FAVICON = (".ico", ".png", "jpg", ".gif", ".jpeg")
FILE_UPLOAD_EXTENSIONS_QUESTION_IMAGE = (".png", "jpg", ".gif", ".jpeg", ".bmp", ".tif", ".tiff", ".jfif")
PILLOW_FORMATS_QUESTIONS_IMAGE = ('PNG', 'GIF', 'JPEG', 'BMP', 'TIFF')
FILE_UPLOAD_EXTENSIONS_EMAIL_ATTACHMENT = (
".png", ".jpg", ".gif", ".jpeg", ".pdf", ".txt", ".docx", ".gif", ".svg",
".pptx", ".ppt", ".doc", ".xlsx", ".xls", ".jfif", ".heic", ".heif", ".pages",
".bmp", ".tif", ".tiff"
)
FILE_UPLOAD_EXTENSIONS_OTHER = FILE_UPLOAD_EXTENSIONS_EMAIL_ATTACHMENT

View File

@@ -728,7 +728,6 @@ class EventSettingsSerializer(SettingsSerializer):
'payment_term_minutes',
'payment_term_last',
'payment_term_expire_automatically',
'payment_term_expire_delay_days',
'payment_term_accept_late',
'payment_explanation',
'payment_pending_hidden',

View File

@@ -251,8 +251,6 @@ class DeviceSerializer(serializers.ModelSerializer):
unique_serial = serializers.CharField(read_only=True)
hardware_brand = serializers.CharField(read_only=True)
hardware_model = serializers.CharField(read_only=True)
os_name = serializers.CharField(read_only=True)
os_version = serializers.CharField(read_only=True)
software_brand = serializers.CharField(read_only=True)
software_version = serializers.CharField(read_only=True)
created = serializers.DateTimeField(read_only=True)
@@ -265,7 +263,7 @@ class DeviceSerializer(serializers.ModelSerializer):
fields = (
'device_id', 'unique_serial', 'initialization_token', 'all_events', 'limit_events',
'revoked', 'name', 'created', 'initialized', 'hardware_brand', 'hardware_model',
'os_name', 'os_version', 'software_brand', 'software_version', 'security_profile'
'software_brand', 'software_version', 'security_profile'
)

View File

@@ -42,8 +42,6 @@ class InitializationRequestSerializer(serializers.Serializer):
token = serializers.CharField(max_length=190)
hardware_brand = serializers.CharField(max_length=190)
hardware_model = serializers.CharField(max_length=190)
os_name = serializers.CharField(max_length=190, required=False, allow_null=True)
os_version = serializers.CharField(max_length=190, required=False, allow_null=True)
software_brand = serializers.CharField(max_length=190)
software_version = serializers.CharField(max_length=190)
info = serializers.JSONField(required=False, allow_null=True)
@@ -52,8 +50,6 @@ class InitializationRequestSerializer(serializers.Serializer):
class UpdateRequestSerializer(serializers.Serializer):
hardware_brand = serializers.CharField(max_length=190)
hardware_model = serializers.CharField(max_length=190)
os_name = serializers.CharField(max_length=190, required=False, allow_null=True)
os_version = serializers.CharField(max_length=190, required=False, allow_null=True)
software_brand = serializers.CharField(max_length=190)
software_version = serializers.CharField(max_length=190)
info = serializers.JSONField(required=False, allow_null=True)
@@ -103,8 +99,6 @@ class InitializeView(APIView):
device.initialized = now()
device.hardware_brand = serializer.validated_data.get('hardware_brand')
device.hardware_model = serializer.validated_data.get('hardware_model')
device.os_name = serializer.validated_data.get('os_name')
device.os_version = serializer.validated_data.get('os_version')
device.software_brand = serializer.validated_data.get('software_brand')
device.software_version = serializer.validated_data.get('software_version')
device.info = serializer.validated_data.get('info')
@@ -126,8 +120,6 @@ class UpdateView(APIView):
device = request.auth
device.hardware_brand = serializer.validated_data.get('hardware_brand')
device.hardware_model = serializer.validated_data.get('hardware_model')
device.os_name = serializer.validated_data.get('os_name')
device.os_version = serializer.validated_data.get('os_version')
device.software_brand = serializer.validated_data.get('software_brand')
device.software_version = serializer.validated_data.get('software_version')
device.info = serializer.validated_data.get('info')

View File

@@ -26,6 +26,7 @@ from decimal import Decimal
from zoneinfo import ZoneInfo
import django_filters
from django.conf import settings
from django.db import transaction
from django.db.models import (
Exists, F, OuterRef, Prefetch, Q, Subquery, prefetch_related_objects,
@@ -1190,7 +1191,7 @@ class OrderPositionViewSet(viewsets.ModelViewSet):
ftype, ignored = mimetypes.guess_type(image_file.name)
extension = os.path.basename(image_file.name).split('.')[-1]
else:
img = Image.open(image_file)
img = Image.open(image_file, formats=settings.PILLOW_FORMATS_QUESTIONS_IMAGE)
ftype = Image.MIME[img.format]
extensions = {
'GIF': 'gif', 'TIFF': 'tif', 'BMP': 'bmp', 'JPEG': 'jpg', 'PNG': 'png'

View File

@@ -500,14 +500,14 @@ class PortraitImageField(SizeValidationMixin, ExtValidationMixin, forms.FileFiel
file = BytesIO(data['content'])
try:
image = Image.open(file)
image = Image.open(file, formats=settings.PILLOW_FORMATS_QUESTIONS_IMAGE)
# verify() must be called immediately after the constructor.
image.verify()
# We want to do more than just verify(), so we need to re-open the file
if hasattr(file, 'seek'):
file.seek(0)
image = Image.open(file)
image = Image.open(file, formats=settings.PILLOW_FORMATS_QUESTIONS_IMAGE)
# load() is a potential DoS vector (see Django bug #18520), so we verify the size first
if image.width > 10_000 or image.height > 10_000:
@@ -566,7 +566,7 @@ class PortraitImageField(SizeValidationMixin, ExtValidationMixin, forms.FileFiel
return f
def __init__(self, *args, **kwargs):
kwargs.setdefault('ext_whitelist', (".png", ".jpg", ".jpeg", ".jfif", ".tif", ".tiff", ".bmp"))
kwargs.setdefault('ext_whitelist', settings.FILE_UPLOAD_EXTENSIONS_QUESTION_IMAGE)
kwargs.setdefault('max_size', settings.FILE_UPLOAD_MAX_SIZE_IMAGE)
super().__init__(*args, **kwargs)
@@ -826,11 +826,7 @@ class BaseQuestionsForm(forms.Form):
help_text=help_text,
initial=initial.file if initial else None,
widget=UploadedFileWidget(position=pos, event=event, answer=initial),
ext_whitelist=(
".png", ".jpg", ".gif", ".jpeg", ".pdf", ".txt", ".docx", ".gif", ".svg",
".pptx", ".ppt", ".doc", ".xlsx", ".xls", ".jfif", ".heic", ".heif", ".pages",
".bmp", ".tif", ".tiff"
),
ext_whitelist=settings.FILE_UPLOAD_EXTENSIONS_OTHER,
max_size=settings.FILE_UPLOAD_MAX_SIZE_OTHER,
)
elif q.type == Question.TYPE_DATE:

View File

@@ -1,23 +0,0 @@
# Generated by Django 4.1.9 on 2023-06-26 10:59
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0242_auto_20230512_1008'),
]
operations = [
migrations.AddField(
model_name='device',
name='os_name',
field=models.CharField(max_length=190, null=True),
),
migrations.AddField(
model_name='device',
name='os_version',
field=models.CharField(max_length=190, null=True),
),
]

View File

@@ -143,14 +143,6 @@ class Device(LoggedModel):
max_length=190,
null=True, blank=True
)
os_name = models.CharField(
max_length=190,
null=True, blank=True
)
os_version = models.CharField(
max_length=190,
null=True, blank=True
)
software_brand = models.CharField(
max_length=190,
null=True, blank=True

View File

@@ -896,28 +896,6 @@ class Order(LockModel, LoggedModel):
), tz)
return term_last
@property
def payment_term_expire_date(self):
delay = self.event.settings.get('payment_term_expire_delay_days', as_type=int)
if not delay: # performance saver + backwards compatibility
return self.expires
term_last = self.payment_term_last
if term_last and self.expires > term_last: # backwards compatibility
return self.expires
expires = self.expires.date() + timedelta(days=delay)
tz = ZoneInfo(self.event.settings.timezone)
expires = make_aware(datetime.combine(
expires,
time(hour=23, minute=59, second=59)
), tz)
if term_last:
return min(expires, term_last)
else:
return expires
def _can_be_paid(self, count_waitinglist=True, ignore_date=False, force=False) -> Union[bool, str]:
error_messages = {
'late_lastdate': _("The payment can not be accepted as the last date of payments configured in the "
@@ -1241,7 +1219,7 @@ class QuestionAnswer(models.Model):
@property
def is_image(self):
return any(self.file.name.lower().endswith(e) for e in ('.jpg', '.png', '.gif', '.tiff', '.bmp', '.jpeg'))
return any(self.file.name.lower().endswith(e) for e in settings.FILE_UPLOAD_EXTENSIONS_QUESTION_IMAGE)
@property
def file_name(self):

View File

@@ -830,12 +830,13 @@ class QuestionColumn(ImportColumn):
class CustomerColumn(ImportColumn):
identifier = 'customer'
verbose_name = gettext_lazy('Customer')
default_value = None
def clean(self, value, previous_values):
if value:
try:
value = self.event.organizer.customers.get(
Q(identifier=value) | Q(email__iexact=value) | Q(external_identifier=value)
Q(identifier=value) | Q(email=value) | Q(external_identifier=value)
)
except Customer.MultipleObjectsReturned:
value = self.event.organizer.customers.get(

View File

@@ -520,7 +520,7 @@ def images_from_questions(sender, *args, **kwargs):
else:
a = op.answers.filter(question_id=question_id).first() or a
if not a or not a.file or not any(a.file.name.lower().endswith(e) for e in (".jpg", ".jpeg", ".png", ".gif", ".bmp", ".tif", ".tiff")):
if not a or not a.file or not any(a.file.name.lower().endswith(e) for e in settings.FILE_UPLOAD_EXTENSIONS_QUESTION_IMAGE):
return None
else:
if etag:
@@ -939,7 +939,7 @@ class Renderer:
# reportlab does not support unicode combination characters
# It's important we do this before we use ArabicReshaper
text = unicodedata.normalize("NFC", text)
text = unicodedata.normalize("NFKC", text)
# reportlab does not support RTL, ligature-heavy scripts like Arabic. Therefore, we use ArabicReshaper
# to resolve all ligatures and python-bidi to switch RTL texts.
@@ -992,10 +992,7 @@ class Renderer:
elif o['type'] == "poweredby":
self._draw_poweredby(canvas, op, o)
if self.bg_pdf:
page_size = (
self.bg_pdf.pages[0].mediabox[2] - self.bg_pdf.pages[0].mediabox[0],
self.bg_pdf.pages[0].mediabox[3] - self.bg_pdf.pages[0].mediabox[1]
)
page_size = (self.bg_pdf.pages[0].mediabox[2], self.bg_pdf.pages[0].mediabox[3])
if self.bg_pdf.pages[0].get('/Rotate') in (90, 270):
# swap dimensions due to pdf being rotated
page_size = page_size[::-1]

View File

@@ -1270,12 +1270,12 @@ def expire_orders(sender, **kwargs):
Exists(
OrderFee.objects.filter(order_id=OuterRef('pk'), fee_type=OrderFee.FEE_TYPE_CANCELLATION)
)
).prefetch_related('event').order_by('event_id')
).select_related('event').order_by('event_id')
for o in qs:
if o.event_id != event_id:
expire = o.event.settings.get('payment_term_expire_automatically', as_type=bool)
event_id = o.event_id
if expire and now() >= o.payment_term_expire_date:
if expire:
mark_order_expired(o)

View File

@@ -893,28 +893,6 @@ DEFAULTS = {
"the pool and can be ordered by other people."),
)
},
'payment_term_expire_delay_days': {
'default': '0',
'type': int,
'form_class': forms.IntegerField,
'serializer_class': serializers.IntegerField,
'form_kwargs': dict(
label=_('Expiration delay'),
help_text=_("The order will only actually expire this many days after the expiration date communicated "
"to the customer. However, this will not delay beyond the \"last date of payments\" "
"configured above, which is always enforced. The delay may also end on a weekend regardless "
"of the other settings above."),
# Every order in between the official expiry date and the delayed expiry date has a performance penalty
# for the cron job, so we limit this feature to 30 days to prevent arbitrary numbers of orders needing
# to be checked.
min_value=0,
max_value=30,
),
'serializer_kwargs': dict(
min_value=0,
max_value=30,
),
},
'payment_pending_hidden': {
'default': 'False',
'type': bool,
@@ -2735,7 +2713,7 @@ Your {organizer} team""")) # noqa: W291
'form_class': ExtFileField,
'form_kwargs': dict(
label=_('Header image'),
ext_whitelist=(".png", ".jpg", ".gif", ".jpeg"),
ext_whitelist=settings.FILE_UPLOAD_EXTENSIONS_IMAGE,
max_size=settings.FILE_UPLOAD_MAX_SIZE_IMAGE,
help_text=_('If you provide a logo image, we will by default not show your event name and date '
'in the page header. By default, we show your logo with a size of up to 1140x120 pixels. You '
@@ -2778,7 +2756,7 @@ Your {organizer} team""")) # noqa: W291
'form_class': ExtFileField,
'form_kwargs': dict(
label=_('Header image'),
ext_whitelist=(".png", ".jpg", ".gif", ".jpeg"),
ext_whitelist=settings.FILE_UPLOAD_EXTENSIONS_IMAGE,
max_size=settings.FILE_UPLOAD_MAX_SIZE_IMAGE,
help_text=_('If you provide a logo image, we will by default not show your organization name '
'in the page header. By default, we show your logo with a size of up to 1140x120 pixels. You '
@@ -2818,7 +2796,7 @@ Your {organizer} team""")) # noqa: W291
'form_class': ExtFileField,
'form_kwargs': dict(
label=_('Social media image'),
ext_whitelist=(".png", ".jpg", ".gif", ".jpeg"),
ext_whitelist=settings.FILE_UPLOAD_EXTENSIONS_IMAGE,
max_size=settings.FILE_UPLOAD_MAX_SIZE_IMAGE,
help_text=_('This picture will be used as a preview if you post links to your ticket shop on social media. '
'Facebook advises to use a picture size of 1200 x 630 pixels, however some platforms like '
@@ -2839,7 +2817,7 @@ Your {organizer} team""")) # noqa: W291
'form_class': ExtFileField,
'form_kwargs': dict(
label=_('Logo image'),
ext_whitelist=(".png", ".jpg", ".gif", ".jpeg"),
ext_whitelist=settings.FILE_UPLOAD_EXTENSIONS_IMAGE,
required=False,
max_size=settings.FILE_UPLOAD_MAX_SIZE_IMAGE,
help_text=_('We will show your logo with a maximal height and width of 2.5 cm.')

View File

@@ -48,8 +48,6 @@ from django.utils.http import url_has_allowed_host_and_scheme
from django.utils.safestring import mark_safe
from markdown import Extension
from markdown.inlinepatterns import SubstituteTagInlineProcessor
from markdown.postprocessors import Postprocessor
from markdown.treeprocessors import UnescapeTreeprocessor
from tlds import tld_set
register = template.Library()
@@ -110,8 +108,6 @@ URL_RE = SimpleLazyObject(lambda: build_url_re(tlds=sorted(tld_set, key=len, rev
EMAIL_RE = SimpleLazyObject(lambda: build_email_re(tlds=sorted(tld_set, key=len, reverse=True)))
DOT_ESCAPE = "|escaped-dot-sGnY9LMK|"
def safelink_callback(attrs, new=False):
"""
@@ -189,109 +185,6 @@ class EmailNl2BrExtension(Extension):
md.inlinePatterns.register(br_tag, 'nl', 5)
class LinkifyPostprocessor(Postprocessor):
def __init__(self, linker):
self.linker = linker
super().__init__()
def run(self, text):
return self.linker.linkify(text)
class CleanPostprocessor(Postprocessor):
def __init__(self, tags, attributes, protocols, strip):
self.tags = tags
self.attributes = attributes
self.protocols = protocols
self.strip = strip
super().__init__()
def run(self, text):
return bleach.clean(
text,
tags=self.tags,
attributes=self.attributes,
protocols=self.protocols,
strip=self.strip
)
class CustomUnescapeTreeprocessor(UnescapeTreeprocessor):
"""
This un-escapes everything except \\.
"""
def _unescape(self, m):
if m.group(1) == "46": # 46 is the ASCII position of .
return DOT_ESCAPE
return chr(int(m.group(1)))
class CustomUnescapePostprocessor(Postprocessor):
"""
Restore escaped .
"""
def run(self, text):
return text.replace(DOT_ESCAPE, ".")
class LinkifyAndCleanExtension(Extension):
r"""
We want to do:
input --> markdown --> bleach clean --> linkify --> output
Internally, the markdown library does:
source --> parse --> (tree|inline)processors --> serializing --> postprocessors
All escaped characters such as \. will be turned to something like <STX>46<ETX> in the processors
step and then will be converted to . back again in the last tree processor, before serialization.
Therefore, linkify does not see the escaped character anymore. This is annoying for the one case
where you want to type "rich_text.py" and *not* have it turned into a link, since you can't type
"rich_text\.py" either.
A simple solution would be to run linkify before markdown, but that may cause other issues when
linkify messes with the markdown syntax and it makes handling our attributes etc. harder.
So we do a weird hack where we modify the unescape processor to unescape everything EXCEPT for the
dot and then unescape that one manually after linkify. However, to make things even harder, the bleach
clean step removes any invisible characters, so we need to cheat a bit more.
"""
def __init__(self, linker, tags, attributes, protocols, strip):
self.linker = linker
self.tags = tags
self.attributes = attributes
self.protocols = protocols
self.strip = strip
super().__init__()
def extendMarkdown(self, md):
md.treeprocessors.deregister('unescape')
md.treeprocessors.register(
CustomUnescapeTreeprocessor(md),
'unescape',
0
)
md.postprocessors.register(
CleanPostprocessor(self.tags, self.attributes, self.protocols, self.strip),
'clean',
2
)
md.postprocessors.register(
LinkifyPostprocessor(self.linker),
'linkify',
1
)
md.postprocessors.register(
CustomUnescapePostprocessor(self.linker),
'unescape_dot',
0
)
def markdown_compile_email(source):
linker = bleach.Linker(
url_re=URL_RE,
@@ -299,20 +192,18 @@ def markdown_compile_email(source):
callbacks=DEFAULT_CALLBACKS + [truelink_callback, abslink_callback],
parse_email=True
)
return markdown.markdown(
source,
extensions=[
'markdown.extensions.sane_lists',
EmailNl2BrExtension(),
LinkifyAndCleanExtension(
linker,
tags=ALLOWED_TAGS,
attributes=ALLOWED_ATTRIBUTES,
protocols=ALLOWED_PROTOCOLS,
strip=False,
)
]
)
return linker.linkify(bleach.clean(
markdown.markdown(
source,
extensions=[
'markdown.extensions.sane_lists',
EmailNl2BrExtension(),
]
),
tags=ALLOWED_TAGS,
attributes=ALLOWED_ATTRIBUTES,
protocols=ALLOWED_PROTOCOLS,
))
class SnippetExtension(markdown.extensions.Extension):
@@ -322,24 +213,23 @@ class SnippetExtension(markdown.extensions.Extension):
md.parser.blockprocessors.deregister('quote')
def markdown_compile(source, linker, snippet=False):
def markdown_compile(source, snippet=False):
tags = ALLOWED_TAGS_SNIPPET if snippet else ALLOWED_TAGS
exts = [
'markdown.extensions.sane_lists',
'markdown.extensions.nl2br',
LinkifyAndCleanExtension(
linker,
tags=tags,
attributes=ALLOWED_ATTRIBUTES,
protocols=ALLOWED_PROTOCOLS,
strip=snippet,
)
'markdown.extensions.nl2br'
]
if snippet:
exts.append(SnippetExtension())
return markdown.markdown(
source,
extensions=exts
return bleach.clean(
markdown.markdown(
source,
extensions=exts
),
strip=snippet,
tags=tags,
attributes=ALLOWED_ATTRIBUTES,
protocols=ALLOWED_PROTOCOLS,
)
@@ -355,7 +245,7 @@ def rich_text(text: str, **kwargs):
callbacks=DEFAULT_CALLBACKS + ([truelink_callback, safelink_callback] if kwargs.get('safelinks', True) else [truelink_callback, abslink_callback]),
parse_email=True
)
body_md = markdown_compile(text, linker)
body_md = linker.linkify(markdown_compile(text))
return mark_safe(body_md)
@@ -371,5 +261,5 @@ def rich_text_snippet(text: str, **kwargs):
callbacks=DEFAULT_CALLBACKS + ([truelink_callback, safelink_callback] if kwargs.get('safelinks', True) else [truelink_callback, abslink_callback]),
parse_email=True
)
body_md = markdown_compile(text, linker, snippet=True)
body_md = linker.linkify(markdown_compile(text, snippet=True))
return mark_safe(body_md)

View File

@@ -127,7 +127,7 @@ class ClearableBasenameFileInput(forms.ClearableFileInput):
@property
def is_img(self):
return any(self.file.name.lower().endswith(e) for e in ('.jpg', '.jpeg', '.png', '.gif'))
return any(self.file.name.lower().endswith(e) for e in settings.FILE_UPLOAD_EXTENSIONS_IMAGE)
def __str__(self):
if hasattr(self.file, 'display_name'):

View File

@@ -749,7 +749,6 @@ class PaymentSettingsForm(EventSettingsValidationMixin, SettingsForm):
'payment_term_minutes',
'payment_term_last',
'payment_term_expire_automatically',
'payment_term_expire_delay_days',
'payment_term_accept_late',
'payment_pending_hidden',
'payment_explanation',

View File

@@ -416,7 +416,7 @@ class OrganizerSettingsForm(SettingsForm):
organizer_logo_image = ExtFileField(
label=_('Header image'),
ext_whitelist=(".png", ".jpg", ".gif", ".jpeg"),
ext_whitelist=settings.FILE_UPLOAD_EXTENSIONS_IMAGE,
max_size=settings.FILE_UPLOAD_MAX_SIZE_IMAGE,
required=False,
help_text=_('If you provide a logo image, we will by default not show your organization name '
@@ -426,7 +426,7 @@ class OrganizerSettingsForm(SettingsForm):
)
favicon = ExtFileField(
label=_('Favicon'),
ext_whitelist=(".ico", ".png", ".jpg", ".gif", ".jpeg"),
ext_whitelist=settings.FILE_UPLOAD_EXTENSIONS_FAVICON,
required=False,
max_size=settings.FILE_UPLOAD_MAX_SIZE_FAVICON,
help_text=_('If you provide a favicon, we will show it instead of the default pretix icon. '

View File

@@ -65,8 +65,6 @@
{% bootstrap_field form.payment_term_minutes layout="control" %}
{% bootstrap_field form.payment_term_last layout="control" %}
{% bootstrap_field form.payment_term_expire_automatically layout="control" %}
{% trans "days" context "unit" as days %}
{% bootstrap_field form.payment_term_expire_delay_days layout="control" addon_after=days %}
{% bootstrap_field form.payment_term_accept_late layout="control" %}
{% bootstrap_field form.payment_pending_hidden layout="control" %}
</fieldset>

View File

@@ -1918,7 +1918,7 @@ class OrderContactChange(OrderView):
'pretix.event.order.contact.changed',
data={
'old_email': old_email,
'new_email': self.form.cleaned_data.get('email'),
'new_email': self.form.cleaned_data['email'],
},
user=self.request.user,
)
@@ -1930,7 +1930,7 @@ class OrderContactChange(OrderView):
'pretix.event.order.phone.changed',
data={
'old_phone': old_phone,
'new_phone': self.form.cleaned_data.get('phone'),
'new_phone': self.form.cleaned_data['phone'],
},
user=self.request.user,
)

View File

@@ -22,6 +22,7 @@
import logging
from io import BytesIO
from django.conf import settings
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
from PIL.Image import MAX_IMAGE_PIXELS, DecompressionBombError
@@ -51,7 +52,7 @@ def validate_uploaded_file_for_valid_image(f):
try:
try:
image = Image.open(file)
image = Image.open(file, formats=settings.PILLOW_FORMATS_QUESTIONS_IMAGE)
# verify() must be called immediately after the constructor.
image.verify()
except DecompressionBombError:

View File

@@ -21,6 +21,8 @@
#
from datetime import datetime
from PIL import Image
def monkeypatch_vobject_performance():
"""
@@ -52,5 +54,19 @@ def monkeypatch_vobject_performance():
icalendar.tzinfo_eq = new_tzinfo_eq
def monkeypatch_pillow_safer():
"""
Pillow supports many file formats, among them EPS. For EPS, Pillow loads GhostScript whenever GhostScript
is installed (cannot officially be disabled). However, GhostScript is known for regular security vulnerabilities.
We have no use of reading EPS files and usually prevent this by using `Image.open(…, formats=[…])` to disable EPS
support explicitly. However, we are worried about our dependencies like reportlab using `Image.open` without the
`formats=` parameter. Therefore, as a defense in depth approach, we monkeypatch EPS support away by modifying the
internal image format registry of Pillow.
"""
if "EPS" in Image.ID:
Image.ID.remove("EPS")
def monkeypatch_all_at_ready():
monkeypatch_vobject_performance()
monkeypatch_pillow_safer()

View File

@@ -20,8 +20,9 @@
# <https://www.gnu.org/licenses/>.
#
from arabic_reshaper import ArabicReshaper
from django.conf import settings
from django.utils.functional import SimpleLazyObject
from PIL.Image import Resampling
from PIL import Image
from reportlab.lib.utils import ImageReader
@@ -33,7 +34,7 @@ class ThumbnailingImageReader(ImageReader):
height = width * self._image.size[1] / self._image.size[0]
self._image.thumbnail(
size=(int(width * dpi / 72), int(height * dpi / 72)),
resample=Resampling.BICUBIC
resample=Image.Resampling.BICUBIC
)
self._data = None
return width, height
@@ -44,6 +45,9 @@ class ThumbnailingImageReader(ImageReader):
# (smaller) size of the modified image.
return None
def _read_image(self, fp):
return Image.open(fp, formats=settings.PILLOW_FORMATS_IMAGE)
reshaper = SimpleLazyObject(lambda: ArabicReshaper(configuration={
'delete_harakat': True,

View File

@@ -23,6 +23,7 @@ import hashlib
import math
from io import BytesIO
from django.conf import settings
from django.core.files.base import ContentFile
from django.core.files.storage import default_storage
from PIL import Image, ImageOps, ImageSequence
@@ -165,7 +166,7 @@ def resize_image(image, size):
def create_thumbnail(sourcename, size):
source = default_storage.open(sourcename)
image = Image.open(BytesIO(source.read()))
image = Image.open(BytesIO(source.read()), formats=settings.PILLOW_FORMATS_QUESTIONS_IMAGE)
try:
image.load()
except:

View File

@@ -5,8 +5,8 @@ msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-06-27 12:51+0000\n"
"PO-Revision-Date: 2023-06-29 05:00+0000\n"
"Last-Translator: Moritz Lerch <dev@moritz-lerch.de>\n"
"PO-Revision-Date: 2023-06-27 14:53+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"
@@ -16352,7 +16352,7 @@ msgstr "Terminal-ID"
#: pretix/control/templates/pretixcontrol/boxoffice/payment.html:96
msgid "Card holder"
msgstr "Karteninhaber*in"
msgstr "Karteninhaber"
#: pretix/control/templates/pretixcontrol/boxoffice/payment.html:100
msgid "Card expiration"
@@ -21206,7 +21206,7 @@ msgstr "Import durchführen"
#: pretix/control/templates/pretixcontrol/orders/import_start.html:10
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/import_form.html:16
msgid "Upload a new file"
msgstr "Neue Datei hochladen"
msgstr "Neuen Datei hochladen"
#: pretix/control/templates/pretixcontrol/orders/import_start.html:16
msgid ""

View File

@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-06-27 12:51+0000\n"
"PO-Revision-Date: 2023-06-29 05:00+0000\n"
"Last-Translator: Moritz Lerch <dev@moritz-lerch.de>\n"
"PO-Revision-Date: 2023-06-27 14:53+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"
@@ -16324,7 +16324,7 @@ msgstr "Terminal-ID"
#: pretix/control/templates/pretixcontrol/boxoffice/payment.html:96
msgid "Card holder"
msgstr "Karteninhaber*in"
msgstr "Karteninhaber"
#: pretix/control/templates/pretixcontrol/boxoffice/payment.html:100
msgid "Card expiration"
@@ -21172,7 +21172,7 @@ msgstr "Import durchführen"
#: pretix/control/templates/pretixcontrol/orders/import_start.html:10
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/import_form.html:16
msgid "Upload a new file"
msgstr "Neue Datei hochladen"
msgstr "Neuen Datei hochladen"
#: pretix/control/templates/pretixcontrol/orders/import_start.html:16
msgid ""

View File

@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-06-27 12:51+0000\n"
"PO-Revision-Date: 2023-06-28 06:00+0000\n"
"Last-Translator: Yucheng Lin <yuchenglinedu@gmail.com>\n"
"PO-Revision-Date: 2023-06-27 07:40+0000\n"
"Last-Translator: Raphael Michel <michel@rami.io>\n"
"Language-Team: Chinese (Traditional) <https://translate.pretix.eu/projects/"
"pretix/pretix/zh_Hant/>\n"
"Language: zh_Hant\n"
@@ -522,20 +522,24 @@ msgid "Test-Mode of shop has been deactivated"
msgstr "商店的測試模式已啟動"
#: pretix/api/webhooks.py:339
#, fuzzy
msgid "Waiting list entry added"
msgstr "候補名單條目已添加"
msgstr "候補名單條目"
#: pretix/api/webhooks.py:343
#, fuzzy
msgid "Waiting list entry changed"
msgstr "候補名單條目已更改"
msgstr "候補名單條目"
#: pretix/api/webhooks.py:347
#, fuzzy
msgid "Waiting list entry deleted"
msgstr "候補名單條目已刪除"
msgstr "候補名單條目"
#: pretix/api/webhooks.py:351
#, fuzzy
msgid "Waiting list entry received voucher"
msgstr "候補名單條目收到憑證"
msgstr "候補名單條目"
#: pretix/base/addressvalidation.py:100 pretix/base/addressvalidation.py:103
#: pretix/base/addressvalidation.py:108 pretix/base/forms/questions.py:938
@@ -5343,8 +5347,9 @@ msgid "Seat {number}"
msgstr "座位 {number}"
#: pretix/base/models/tax.py:157
#, fuzzy
msgid "Your set of rules is not valid. Error message: {}"
msgstr "你的條例規則並非有效。錯誤訊息:{}"
msgstr "你的樣式檔案並非有效樣式。錯誤訊息:{}"
#: pretix/base/models/tax.py:168 pretix/base/models/tax.py:146
msgid "Official name"
@@ -7637,8 +7642,9 @@ msgid "Something happened in your event after the export, please try again."
msgstr "匯出後你的活動中發生一些事情,請重試。"
#: pretix/base/services/shredder.py:177
#, fuzzy
msgid "Data shredding completed"
msgstr "數據粉碎完成"
msgstr "完成抓取。"
#: pretix/base/services/stats.py:210
msgid "Uncategorized"
@@ -10714,20 +10720,6 @@ msgid ""
"\n"
"Your pretix team\n"
msgstr ""
"你好\n"
"\n"
"我們特此確認以下數據粉碎工作已完成:\n"
"\n"
"召集人:%(organizer)s\n"
"\n"
"事件: %(event)s\n"
"\n"
"數據選擇: %(shredders)s\n"
"開始時間:%(start_time)s在此時間之後添加的新資料可能尚未刪除\n"
"\n"
"敬上\n"
"\n"
"你的卓越團隊\n"
#: pretix/base/templates/pretixbase/forms/widgets/portrait_image.html:10
msgid "Upload photo"
@@ -13921,11 +13913,11 @@ msgstr "活動已被刪除。"
#: pretix/control/logdisplay.py:375
msgid "A removal process for personal data has been started."
msgstr "個人數據的刪除過程已啟動。"
msgstr ""
#: pretix/control/logdisplay.py:376
msgid "A removal process for personal data has been completed."
msgstr "個人數據的刪除過程已完成。"
msgstr ""
#: pretix/control/logdisplay.py:377 pretix/control/logdisplay.py:375
msgid "The order details have been changed."
@@ -21596,8 +21588,7 @@ msgstr "確認碼"
msgid ""
"Depending on the amount of data in your event, the following step may take a "
"while to complete. We will inform you via email once it has been completed."
msgstr "根據事件中的數據量,以下步驟可能需要一段時間才能完成。完成後,我們將通過電子"
"郵件通知你。"
msgstr ""
#: pretix/control/templates/pretixcontrol/shredder/index.html:11
msgid ""

View File

@@ -35,7 +35,7 @@
import json
import logging
from collections import OrderedDict
from datetime import date, datetime, time, timedelta
from datetime import datetime, time, timedelta
from decimal import Decimal
from io import BytesIO
from typing import Tuple
@@ -351,19 +351,15 @@ class BadgeExporter(BaseExporter):
qs = qs.filter(Q(order__status=Order.STATUS_PAID) | Q(order__status=Order.STATUS_PENDING, order__valid_if_pending=True))
if form_data.get('date_from'):
if not isinstance(form_data.get('date_from'), date):
form_data['date_from'] = dateutil.parser.parse(form_data['date_from']).date()
df = make_aware(datetime.combine(
form_data['date_from'],
dt = make_aware(datetime.combine(
dateutil.parser.parse(form_data['date_from']).date(),
time(hour=0, minute=0, second=0)
), self.event.timezone)
qs = qs.filter(Q(subevent__date_from__gte=df) | Q(subevent__isnull=True, order__event__date_from__gte=df))
qs = qs.filter(Q(subevent__date_from__gte=dt) | Q(subevent__isnull=True, order__event__date_from__gte=dt))
if form_data.get('date_to'):
if not isinstance(form_data.get('date_to'), date):
form_data['date_to'] = dateutil.parser.parse(form_data['date_to']).date()
dt = make_aware(datetime.combine(
form_data['date_to'] + timedelta(days=1),
dateutil.parser.parse(form_data['date_to']).date() + timedelta(days=1),
time(hour=0, minute=0, second=0)
), self.event.timezone)
qs = qs.filter(Q(subevent__date_from__lt=dt) | Q(subevent__isnull=True, order__event__date_from__lt=dt))

View File

@@ -76,11 +76,7 @@ class BaseMailForm(FormPlaceholderMixin, forms.Form):
attachment = CachedFileField(
label=_("Attachment"),
required=False,
ext_whitelist=(
".png", ".jpg", ".gif", ".jpeg", ".pdf", ".txt", ".docx", ".gif", ".svg",
".pptx", ".ppt", ".doc", ".xlsx", ".xls", ".jfif", ".heic", ".heif", ".pages",
".bmp", ".tif", ".tiff"
),
ext_whitelist=settings.FILE_UPLOAD_EXTENSIONS_EMAIL_ATTACHMENT,
help_text=_('Sending an attachment increases the chance of your email not arriving or being sorted into spam folders. We recommend only using PDFs '
'of no more than 2 MB in size.'),
max_size=settings.FILE_UPLOAD_MAX_SIZE_EMAIL_ATTACHMENT

View File

@@ -350,9 +350,7 @@ def _detect_event(request, require_live=True, require_plugin=None):
)
pathparts = request.get_full_path().split('/')
pathparts[1] = event.slug
r = redirect('/'.join(pathparts))
r['Access-Control-Allow-Origin'] = '*'
return r
return redirect('/'.join(pathparts))
else:
if 'event' in url.kwargs and 'organizer' in url.kwargs:
event = Event.objects.select_related('organizer').get(
@@ -362,9 +360,7 @@ def _detect_event(request, require_live=True, require_plugin=None):
pathparts = request.get_full_path().split('/')
pathparts[1] = event.organizer.slug
pathparts[2] = event.slug
r = redirect('/'.join(pathparts))
r['Access-Control-Allow-Origin'] = '*'
return r
return redirect('/'.join(pathparts))
except Event.DoesNotExist:
raise Http404(_('The selected event was not found.'))
raise Http404(_('The selected event was not found.'))
@@ -378,9 +374,7 @@ def _detect_event(request, require_live=True, require_plugin=None):
raise Http404(_('The selected organizer was not found.'))
pathparts = request.get_full_path().split('/')
pathparts[1] = organizer.slug
r = redirect('/'.join(pathparts))
r['Access-Control-Allow-Origin'] = '*'
return r
return redirect('/'.join(pathparts))
raise Http404(_('The selected organizer was not found.'))
request._event_detected = True

View File

@@ -212,14 +212,11 @@ def price_dict(item, price):
}
def get_picture(event, picture, size=None):
thumb = None
if size:
try:
thumb = get_thumbnail(picture.name, size).thumb.url
except:
logger.exception(f'Failed to create thumbnail of {picture.name}')
if not thumb:
def get_picture(event, picture):
try:
thumb = get_thumbnail(picture.name, '60x60^').thumb.url
except:
logger.exception(f'Failed to create thumbnail of {picture.name}')
thumb = default_storage.url(picture.name)
return urljoin(build_absolute_uri(event, 'presale:event.index'), thumb)
@@ -267,8 +264,7 @@ class WidgetAPIProductList(EventListMixin, View):
{
'id': item.pk,
'name': str(item.name),
'picture': get_picture(self.request.event, item.picture, '60x60^') if item.picture else None,
'picture_fullsize': get_picture(self.request.event, item.picture) if item.picture else None,
'picture': get_picture(self.request.event, item.picture) if item.picture else None,
'description': str(rich_text(item.description, safelinks=False)) if item.description else None,
'has_variations': item.has_variations,
'require_voucher': item.require_voucher,

View File

@@ -165,13 +165,13 @@ if SITE_URL.endswith('/'):
CSRF_TRUSTED_ORIGINS = [urlparse(SITE_URL).scheme + '://' + urlparse(SITE_URL).hostname]
TRUST_X_FORWARDED_FOR = config.get('pretix', 'trust_x_forwarded_for', fallback=False)
USE_X_FORWARDED_HOST = config.get('pretix', 'trust_x_forwarded_host', fallback=False)
TRUST_X_FORWARDED_FOR = config.getboolean('pretix', 'trust_x_forwarded_for', fallback=False)
USE_X_FORWARDED_HOST = config.getboolean('pretix', 'trust_x_forwarded_host', fallback=False)
REQUEST_ID_HEADER = config.get('pretix', 'request_id_header', fallback=False)
if config.get('pretix', 'trust_x_forwarded_proto', fallback=False):
if config.getboolean('pretix', 'trust_x_forwarded_proto', fallback=False):
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
PRETIX_PLUGINS_DEFAULT = config.get('pretix', 'plugins_default',
@@ -684,4 +684,5 @@ FILE_UPLOAD_MAX_SIZE_EMAIL_ATTACHMENT = 1024 * 1024 * config.getint("pretix_file
FILE_UPLOAD_MAX_SIZE_EMAIL_AUTO_ATTACHMENT = 1024 * 1024 * config.getint("pretix_file_upload", "max_size_email_auto_attachment", fallback=1)
FILE_UPLOAD_MAX_SIZE_OTHER = 1024 * 1024 * config.getint("pretix_file_upload", "max_size_other", fallback=10)
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' # sadly. we would prefer BigInt, and should use it for all new models but the migration will be hard

View File

@@ -450,7 +450,7 @@ Vue.component('item', {
// Product description
+ '<div class="pretix-widget-item-info-col">'
+ '<a :href="item.picture_fullsize" v-if="item.picture" class="pretix-widget-item-picture-link" @click.prevent.stop="lightbox"><img :src="item.picture" class="pretix-widget-item-picture"></a>'
+ '<img :src="item.picture" v-if="item.picture" class="pretix-widget-item-picture">'
+ '<div class="pretix-widget-item-title-and-description">'
+ '<a v-if="item.has_variations && show_toggle" class="pretix-widget-item-title" :href="\'#\' + item.id + \'-variants\'"'
+ ' @click.prevent.stop="expand" role="button" tabindex="0"'
@@ -530,12 +530,6 @@ Vue.component('item', {
methods: {
expand: function () {
this.expanded = !this.expanded;
},
lightbox: function () {
this.$root.overlay.lightbox = {
image: this.item.picture_fullsize,
description: this.item.name,
}
}
},
computed: {
@@ -790,44 +784,12 @@ var shared_alert_fragment = (
+ '</div>'
);
var shared_lightbox_fragment = (
'<div :class="lightboxClasses" role="dialog" aria-modal="true" v-if="$root.lightbox" @click="lightboxClose">'
+ '<div class="pretix-widget-lightbox-loading" v-if="$root.lightbox?.loading">'
+ '<svg width="256" height="256" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path class="pretix-widget-primary-color" d="M1152 896q0-106-75-181t-181-75-181 75-75 181 75 181 181 75 181-75 75-181zm512-109v222q0 12-8 23t-20 13l-185 28q-19 54-39 91 35 50 107 138 10 12 10 25t-9 23q-27 37-99 108t-94 71q-12 0-26-9l-138-108q-44 23-91 38-16 136-29 186-7 28-36 28h-222q-14 0-24.5-8.5t-11.5-21.5l-28-184q-49-16-90-37l-141 107q-10 9-25 9-14 0-25-11-126-114-165-168-7-10-7-23 0-12 8-23 15-21 51-66.5t54-70.5q-27-50-41-99l-183-27q-13-2-21-12.5t-8-23.5v-222q0-12 8-23t19-13l186-28q14-46 39-92-40-57-107-138-10-12-10-24 0-10 9-23 26-36 98.5-107.5t94.5-71.5q13 0 26 10l138 107q44-23 91-38 16-136 29-186 7-28 36-28h222q14 0 24.5 8.5t11.5 21.5l28 184q49 16 90 37l142-107q9-9 24-9 13 0 25 10 129 119 165 170 7 8 7 22 0 12-8 23-15 21-51 66.5t-54 70.5q26 50 41 98l183 28q13 2 21 12.5t8 23.5z"/></svg>'
+ '</div>'
+ '<div class="pretix-widget-lightbox-inner" @click.stop="">'
+ '<figure class="pretix-widget-lightbox-image">'
+ '<img :src="$root.lightbox.image" :alt="$root.lightbox.description" @load="lightboxLoaded" ref="lightboxImage">'
+ '<figcaption v-if="$root.lightbox.description">{{$root.lightbox.description}}</figcaption>'
+ '</figure>'
+ '<button type="button" class="pretix-widget-lightbox-close" @click="lightboxClose" aria-label="'+strings.close+'">'
+ '<svg height="16" viewBox="0 0 512 512" width="16" xmlns="http://www.w3.org/2000/svg"><path fill="#fff" d="M437.5,386.6L306.9,256l130.6-130.6c14.1-14.1,14.1-36.8,0-50.9c-14.1-14.1-36.8-14.1-50.9,0L256,205.1L125.4,74.5 c-14.1-14.1-36.8-14.1-50.9,0c-14.1,14.1-14.1,36.8,0,50.9L205.1,256L74.5,386.6c-14.1,14.1-14.1,36.8,0,50.9 c14.1,14.1,36.8,14.1,50.9,0L256,306.9l130.6,130.6c14.1,14.1,36.8,14.1,50.9,0C451.5,423.4,451.5,400.6,437.5,386.6z"/></svg>'
+ '</button>'
+ '</div>'
+ '</div>'
);
Vue.component('pretix-overlay', {
template: ('<div class="pretix-widget-overlay">'
+ shared_iframe_fragment
+ shared_alert_fragment
+ shared_lightbox_fragment
+ '</div>'
),
watch: {
'$root.lightbox': function (newValue, oldValue) {
if (newValue) {
if (newValue.image != oldValue?.image) {
this.$set(newValue, "loading", true);
}
if (!oldValue) {
window.addEventListener('keyup', this.lightboxCloseOnKeyup);
}
} else {
window.removeEventListener('keyup', this.lightboxCloseOnKeyup);
}
}
},
computed: {
frameClasses: function () {
return {
@@ -841,27 +803,8 @@ Vue.component('pretix-overlay', {
'pretix-widget-alert-shown': this.$root.error_message,
};
},
lightboxClasses: function () {
return {
'pretix-widget-lightbox-holder': true,
'pretix-widget-lightbox-shown': this.$root.lightbox,
'pretix-widget-lightbox-isloading': this.$root.lightbox?.loading,
};
},
},
methods: {
lightboxCloseOnKeyup: function (event) {
if (event.keyCode === 27) {
// abort on ESC-key
this.lightboxClose();
}
},
lightboxClose: function () {
this.$root.lightbox = null;
},
lightboxLoaded: function () {
this.$root.lightbox.loading = false;
},
errorClose: function () {
this.$root.error_message = null;
this.$root.error_url_after = null;
@@ -1852,7 +1795,6 @@ var create_overlay = function (app) {
error_url_after: null,
error_url_after_new_tab: true,
error_message: null,
lightbox: null,
}
},
methods: {

View File

@@ -768,102 +768,6 @@
}
}
.pretix-widget-lightbox-holder {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: rgba(255, 255, 255, 0.8);
z-index: 16777271;
visibility: hidden;
opacity: 0;
transition: opacity 0.5s, visibility 0.5s;
display: flex;
align-items: center;
justify-content: center;
.pretix-widget-lightbox-loading svg {
margin: 40px;
-webkit-animation: pretix-widget-spin 6s linear infinite;
-moz-animation: pretix-widget-spin 6s linear infinite;
animation: pretix-widget-spin 6s linear infinite;
}
&.pretix-widget-lightbox-shown {
visibility: visible;
opacity: 1;
transition: opacity 0.5s, visibility 0.5s;
}
.pretix-widget-lightbox-inner {
position: relative;
background: white;
border-radius: 5px 5px 5px 5px;
-moz-border-radius: 5px 5px 5px 5px;
-webkit-border-radius: 5px 5px 5px 5px;
box-shadow: 0 4px 18px 0 rgba(0, 0, 0, 0.1), 0 6px 20px 0 rgba(0, 0, 0, 0.09);
-webkit-box-shadow: 0 4px 18px 0 rgba(0, 0, 0, 0.1), 0 6px 20px 0 rgba(0, 0, 0, 0.09);
-moz-box-shadow: 0 4px 18px 0 rgba(0, 0, 0, 0.1), 0 6px 20px 0 rgba(0, 0, 0, 0.09);
box-sizing: border-box;
padding: 10px;
max-width: 90%;
max-height: 90%;
}
&.pretix-widget-lightbox-isloading .pretix-widget-lightbox-inner {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0,0,0,0);
border: 0;
}
.pretix-widget-lightbox-image {
margin: 0;
padding: 0;
text-align: center;
}
.pretix-widget-lightbox-image img {
max-width: 80vw;
max-height: 80vh;
object-fit: scale-down;
}
.pretix-widget-lightbox-image figcaption {
margin: 0.5em 0 0;
}
.pretix-widget-lightbox-close {
position: absolute;
right: -12px;
top: -12px;
width: 24px;
height: 24px;
background: $brand-primary;
margin: 0;
border: none;
border-radius: 12px;
-moz-border-radius: 12px;
-webkit-border-radius: 12px;
text-align: center;
color: white;
font-weight: bold;
font-family: sans-serif;
text-decoration: none;
padding: 4px 0;
display: inline-block;
line-height: 16px;
cursor: pointer;
}
.pretix-widget-lightbox-close svg {
display: inline-block;
border: none;
}
}
.pretix-widget-primary-color {
/* in SVG */
fill: $brand-primary;

View File

@@ -94,8 +94,6 @@ def test_initialize_valid_token(client, new_device: Device):
'hardware_brand': 'Samsung',
'hardware_model': 'Galaxy S',
'software_brand': 'pretixdroid',
'os_name': 'Android',
'os_version': '2.3.3',
'software_version': '4.0.0'
})
assert resp.status_code == 200
@@ -107,7 +105,6 @@ def test_initialize_valid_token(client, new_device: Device):
new_device.refresh_from_db()
assert new_device.api_token
assert new_device.initialized
assert new_device.os_version == "2.3.3"
@pytest.mark.django_db
@@ -145,8 +142,6 @@ def test_update_valid_fields(device_client, device: Device):
resp = device_client.post('/api/v1/device/update', {
'hardware_brand': 'Samsung',
'hardware_model': 'Galaxy S',
'os_name': 'Android',
'os_version': '2.3.3',
'software_brand': 'pretixdroid',
'software_version': '5.0.0',
'info': {
@@ -156,23 +151,9 @@ def test_update_valid_fields(device_client, device: Device):
assert resp.status_code == 200
device.refresh_from_db()
assert device.software_version == '5.0.0'
assert device.os_version == '2.3.3'
assert device.info == {'foo': 'bar'}
@pytest.mark.django_db
def test_update_valid_without_optional_fields(device_client, device: Device):
resp = device_client.post('/api/v1/device/update', {
'hardware_brand': 'Samsung',
'hardware_model': 'Galaxy S',
'software_brand': 'pretixdroid',
'software_version': '5.0.0',
}, format='json')
assert resp.status_code == 200
device.refresh_from_db()
assert device.software_version == '5.0.0'
@pytest.mark.django_db
def test_keyroll_required_auth(client, token_client, device: Device):
resp = client.post('/api/v1/device/roll', {})

View File

@@ -35,8 +35,6 @@ def device(organizer, event):
unique_serial="UOS3GNZ27O39V3QS",
initialization_token="frkso3m2w58zuw70",
hardware_model="TC25",
os_name="Android",
os_version="8.1.0",
software_brand="pretixSCAN",
software_version="1.5.1",
initialized=now(),
@@ -60,8 +58,6 @@ TEST_DEV_RES = {
"initialized": "2020-09-18T14:17:44.190021Z",
"hardware_brand": "Zebra",
"hardware_model": "TC25",
"os_name": "Android",
"os_version": "8.1.0",
"software_brand": "pretixSCAN",
"software_version": "1.5.1",
"security_profile": "full"

View File

@@ -20,8 +20,7 @@
# <https://www.gnu.org/licenses/>.
#
import json
import zoneinfo
from datetime import date, datetime, timedelta
from datetime import datetime, timedelta
from decimal import Decimal
from zoneinfo import ZoneInfo
@@ -32,7 +31,6 @@ from django.test import TestCase
from django.utils.timezone import make_aware, now
from django_countries.fields import Country
from django_scopes import scope
from freezegun import freeze_time
from tests.testdummy.signals import FoobazSalesChannel
from pretix.base.decimal import round_decimal
@@ -408,56 +406,6 @@ def test_expiring_auto_disabled(event):
assert o2.status == Order.STATUS_PENDING
@pytest.mark.django_db
def test_expiring_auto_delayed(event):
event.settings.set('payment_term_expire_delay_days', 3)
event.settings.set('payment_term_last', date(2023, 7, 2))
event.settings.set('timezone', 'Europe/Berlin')
o1 = Order.objects.create(
code='FOO', event=event, email='dummy@dummy.test',
status=Order.STATUS_PENDING,
datetime=datetime(2023, 6, 22, 12, 13, 14, tzinfo=zoneinfo.ZoneInfo("Europe/Berlin")),
expires=datetime(2023, 6, 30, 23, 59, 59, tzinfo=zoneinfo.ZoneInfo("Europe/Berlin")),
total=0,
)
o2 = Order.objects.create(
code='FO2', event=event, email='dummy@dummy.test',
status=Order.STATUS_PENDING,
datetime=datetime(2023, 6, 22, 12, 13, 14, tzinfo=zoneinfo.ZoneInfo("Europe/Berlin")),
expires=datetime(2023, 6, 28, 23, 59, 59, tzinfo=zoneinfo.ZoneInfo("Europe/Berlin")),
total=0,
)
assert o1.payment_term_expire_date == o1.expires + timedelta(days=2) # limited by term_last
assert o2.payment_term_expire_date == o2.expires + timedelta(days=3)
with freeze_time("2023-06-29T00:01:00+02:00"):
expire_orders(None)
o1 = Order.objects.get(id=o1.id)
assert o1.status == Order.STATUS_PENDING
o2 = Order.objects.get(id=o2.id)
assert o2.status == Order.STATUS_PENDING
with freeze_time("2023-07-01T23:50:00+02:00"):
expire_orders(None)
o1 = Order.objects.get(id=o1.id)
assert o1.status == Order.STATUS_PENDING
o2 = Order.objects.get(id=o2.id)
assert o2.status == Order.STATUS_PENDING
with freeze_time("2023-07-02T00:01:00+02:00"):
expire_orders(None)
o1 = Order.objects.get(id=o1.id)
assert o1.status == Order.STATUS_PENDING
o2 = Order.objects.get(id=o2.id)
assert o2.status == Order.STATUS_EXPIRED
with freeze_time("2023-07-03T00:01:00+02:00"):
expire_orders(None)
o1 = Order.objects.get(id=o1.id)
assert o1.status == Order.STATUS_EXPIRED
o2 = Order.objects.get(id=o2.id)
assert o2.status == Order.STATUS_EXPIRED
@pytest.mark.django_db
def test_do_not_expire_if_approval_pending(event):
o1 = Order.objects.create(

View File

@@ -30,8 +30,6 @@ from pretix.base.templatetags.rich_text import (
# Test link detection
("google.com",
'<a href="http://google.com" rel="noopener" target="_blank">google.com</a>'),
# Test link escaping
("google\\.com", 'google.com'),
# Test abslink_callback
("[Call](tel:+12345)",
'<a href="tel:+12345" rel="nofollow">Call</a>'),
@@ -81,20 +79,3 @@ def test_newline_handling(content, result):
])
def test_newline_handling_email(content, result):
assert markdown_compile_email(content) == result
@pytest.mark.parametrize("content,result,result_snippet", [
# attributes
('<a onclick="javascript:foo()">foo</a>', '<p><a>foo</a></p>', '<a>foo</a>'),
('<strong color="red">foo</strong>',
'<p><strong>foo</strong></p>',
'<strong>foo</strong>'),
# protocols
('<a href="javascript:foo()">foo</a>', '<p><a>foo</a></p>', '<a>foo</a>'),
# tags
('<script>foo</script>', '&lt;script&gt;foo&lt;/script&gt;', 'foo'),
])
def test_cleanup(content, result, result_snippet):
assert rich_text(content) == result
assert rich_text_snippet(content) == result_snippet
assert markdown_compile_email(content) == result

View File

@@ -185,7 +185,6 @@ class WidgetCartTest(CartTestMixin, TestCase):
"max_price": None,
"price": {"gross": "23.00", "net": "19.33", "tax": "3.67", "name": "", "rate": "19.00", "includes_mixed_tax_rate": False},
"picture": None,
"picture_fullsize": None,
"has_variations": 0,
"allow_waitinglist": True,
"mandatory_priced_addons": False,
@@ -205,7 +204,6 @@ class WidgetCartTest(CartTestMixin, TestCase):
"max_price": "14.00",
"price": None,
"picture": None,
"picture_fullsize": None,
"has_variations": 4,
"allow_waitinglist": True,
"mandatory_priced_addons": False,
@@ -267,7 +265,6 @@ class WidgetCartTest(CartTestMixin, TestCase):
"price": {"gross": "23.00", "net": "19.33", "tax": "3.67", "name": "", "rate": "19.00",
"includes_mixed_tax_rate": False},
"picture": None,
"picture_fullsize": None,
"has_variations": 0,
"allow_waitinglist": True,
"mandatory_priced_addons": False,
@@ -313,7 +310,6 @@ class WidgetCartTest(CartTestMixin, TestCase):
"max_price": "14.00",
"price": None,
"picture": None,
"picture_fullsize": None,
"has_variations": 4,
"allow_waitinglist": True,
"mandatory_priced_addons": False,
@@ -375,7 +371,6 @@ class WidgetCartTest(CartTestMixin, TestCase):
"max_price": None,
"price": {"gross": "23.00", "net": "19.33", "tax": "3.67", "name": "", "rate": "19.00", "includes_mixed_tax_rate": False},
"picture": None,
"picture_fullsize": None,
"has_variations": 0,
"allow_waitinglist": True,
"mandatory_priced_addons": False,
@@ -430,7 +425,6 @@ class WidgetCartTest(CartTestMixin, TestCase):
'id': self.shirt.pk,
'name': 'T-Shirt',
'picture': None,
"picture_fullsize": None,
'description': None,
'has_variations': 2,
"allow_waitinglist": True,