mirror of
https://github.com/pretix/pretix.git
synced 2025-12-17 15:52:26 +00:00
Compare commits
16 Commits
hide-empty
...
fix-lazy-t
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1897bd4b26 | ||
|
|
fd6843822b | ||
|
|
ee1644e037 | ||
|
|
a6c1486650 | ||
|
|
f4b437e92b | ||
|
|
446c55dc89 | ||
|
|
0990eeeea0 | ||
|
|
591fe23a99 | ||
|
|
ad70765287 | ||
|
|
c59d29493c | ||
|
|
bd32b33ba9 | ||
|
|
3a8556bb78 | ||
|
|
c972d24ce7 | ||
|
|
647e68ef01 | ||
|
|
f439a591df | ||
|
|
8f17b338d1 |
4
.github/workflows/style.yml
vendored
4
.github/workflows/style.yml
vendored
@@ -35,7 +35,7 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pip-
|
||||
- name: Install Dependencies
|
||||
run: pip3 install -e ".[dev]" mysqlclient psycopg2-binary
|
||||
run: pip3 install -e ".[dev]" psycopg2-binary
|
||||
- name: Run isort
|
||||
run: isort -c .
|
||||
working-directory: ./src
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pip-
|
||||
- name: Install Dependencies
|
||||
run: pip3 install -e ".[dev]" mysqlclient psycopg2-binary
|
||||
run: pip3 install -e ".[dev]" psycopg2-binary
|
||||
- name: Run flake8
|
||||
run: flake8 .
|
||||
working-directory: ./src
|
||||
|
||||
16
.github/workflows/tests.yml
vendored
16
.github/workflows/tests.yml
vendored
@@ -25,24 +25,14 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.9", "3.10", "3.11"]
|
||||
database: [sqlite, postgres, mysql]
|
||||
database: [sqlite, postgres]
|
||||
exclude:
|
||||
- database: mysql
|
||||
python-version: "3.9"
|
||||
- database: mysql
|
||||
python-version: "3.11"
|
||||
- database: sqlite
|
||||
python-version: "3.9"
|
||||
- database: sqlite
|
||||
python-version: "3.10"
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: getong/mariadb-action@v1.1
|
||||
with:
|
||||
mariadb version: '10.10'
|
||||
mysql database: 'pretix'
|
||||
mysql root password: ''
|
||||
if: matrix.database == 'mysql'
|
||||
- uses: harmon758/postgresql-action@v1
|
||||
with:
|
||||
postgresql version: '11'
|
||||
@@ -61,9 +51,9 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pip-
|
||||
- name: Install system dependencies
|
||||
run: sudo apt update && sudo apt install gettext mariadb-client
|
||||
run: sudo apt update && sudo apt install gettext
|
||||
- name: Install Python dependencies
|
||||
run: pip3 install --ignore-requires-python -e ".[dev]" mysqlclient psycopg2-binary # We ignore that flake8 needs newer python as we don't run flake8 during tests
|
||||
run: pip3 install --ignore-requires-python -e ".[dev]" psycopg2-binary # We ignore that flake8 needs newer python as we don't run flake8 during tests
|
||||
- name: Run checks
|
||||
run: python manage.py check
|
||||
working-directory: ./src
|
||||
|
||||
@@ -3,7 +3,6 @@ FROM python:3.11-bullseye
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
libmariadb-dev \
|
||||
gettext \
|
||||
git \
|
||||
libffi-dev \
|
||||
@@ -58,7 +57,7 @@ RUN pip3 install -U \
|
||||
wheel && \
|
||||
cd /pretix && \
|
||||
PRETIX_DOCKER_BUILD=TRUE pip3 install \
|
||||
-e ".[memcached,mysql]" \
|
||||
-e ".[memcached]" \
|
||||
gunicorn django-extensions ipython && \
|
||||
rm -rf ~/.cache/pip
|
||||
|
||||
|
||||
@@ -154,23 +154,15 @@ Example::
|
||||
port=3306
|
||||
|
||||
``backend``
|
||||
One of ``mysql`` (deprecated), ``sqlite3`` and ``postgresql``.
|
||||
One of ``sqlite3`` and ``postgresql``.
|
||||
Default: ``sqlite3``.
|
||||
|
||||
If you use MySQL, be sure to create your database using
|
||||
``CREATE DATABASE <dbname> CHARACTER SET utf8;``. Otherwise, Unicode
|
||||
support will not properly work.
|
||||
|
||||
``name``
|
||||
The database's name. Default: ``db.sqlite3``.
|
||||
|
||||
``user``, ``password``, ``host``, ``port``
|
||||
Connection details for the database connection. Empty by default.
|
||||
|
||||
``galera``
|
||||
(Deprecated) Indicates if the database backend is a MySQL/MariaDB Galera cluster and
|
||||
turns on some optimizations/special case handlers. Default: ``False``
|
||||
|
||||
.. _`config-replica`:
|
||||
|
||||
Database replica settings
|
||||
|
||||
@@ -16,14 +16,11 @@ To use pretix, you will need the following things:
|
||||
* A periodic task runner, e.g. ``cron``
|
||||
|
||||
* **A database**. This needs to be a SQL-based that is supported by Django. We highly recommend to either
|
||||
go for **PostgreSQL** or **MySQL/MariaDB**. If you do not provide one, pretix will run on SQLite, which is useful
|
||||
go for **PostgreSQL**. If you do not provide one, pretix will run on SQLite, which is useful
|
||||
for evaluation and development purposes.
|
||||
|
||||
.. warning:: Do not ever use SQLite in production. It will break.
|
||||
|
||||
.. warning:: We recommend **PostgreSQL**. If you go for MySQL, make sure you run **MySQL 5.7 or newer** or
|
||||
**MariaDB 10.2.7 or newer**.
|
||||
|
||||
* A **reverse proxy**. pretix needs to deliver some static content to your users (e.g. CSS, images, ...). While pretix
|
||||
is capable of doing this, having this handled by a proper web server like **nginx** or **Apache** will be much
|
||||
faster. Also, you need a proxying web server in front to provide SSL encryption.
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
Migrating from MySQL/MariaDB to PostgreSQL
|
||||
==========================================
|
||||
|
||||
Our recommended database for all production installations is PostgreSQL. Support for MySQL/MariaDB will be removed in
|
||||
pretix 5.0.
|
||||
Our recommended database for all production installations is PostgreSQL. Support for MySQL/MariaDB has been removed
|
||||
in newer pretix releases.
|
||||
|
||||
In order to follow this guide, your pretix installation needs to be a version that fully supports MySQL/MariaDB. If you
|
||||
already upgraded to pretix 5.0, downgrade back to the last 4.x release using ``pip``.
|
||||
already upgraded to pretix 5.0 or later, downgrade back to the last 4.x release using ``pip``.
|
||||
|
||||
.. note:: We have tested this guide carefully, but we can't assume any liability for its correctness. The data loss
|
||||
risk should be low as long as pretix is not running while you do the migration. If you are a pretix Enterprise
|
||||
|
||||
@@ -18,13 +18,13 @@ If you want to add a custom view to the control area of an event, just register
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from django.conf.urls import url
|
||||
from django.urls import re_path
|
||||
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/mypluginname/',
|
||||
views.admin_view, name='backend'),
|
||||
re_path(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/mypluginname/',
|
||||
views.admin_view, name='backend'),
|
||||
]
|
||||
|
||||
It is required that your URL parameters are called ``organizer`` and ``event``. If you want to
|
||||
|
||||
@@ -35,13 +35,13 @@ automatically and should be provided by any plugin that provides any view.
|
||||
A very basic example that provides one view in the admin panel and one view in the frontend
|
||||
could look like this::
|
||||
|
||||
from django.conf.urls import url
|
||||
from django.urls import re_path
|
||||
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/mypluginname/',
|
||||
views.AdminView.as_view(), name='backend'),
|
||||
re_path(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/mypluginname/',
|
||||
views.AdminView.as_view(), name='backend'),
|
||||
]
|
||||
|
||||
event_patterns = [
|
||||
|
||||
@@ -22,7 +22,7 @@ classifiers = [
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Framework :: Django :: 3.2",
|
||||
"Framework :: Django :: 4.1",
|
||||
]
|
||||
|
||||
dependencies = [
|
||||
@@ -36,7 +36,7 @@ dependencies = [
|
||||
"css-inline==0.8.*",
|
||||
"defusedcsv>=1.1.0",
|
||||
"dj-static",
|
||||
"Django==3.2.*,>=3.2.18",
|
||||
"Django==4.1.*",
|
||||
"django-bootstrap3==23.1.*",
|
||||
"django-compressor==4.3.*",
|
||||
"django-countries==7.5.*",
|
||||
@@ -49,7 +49,6 @@ dependencies = [
|
||||
"django-libsass==0.9",
|
||||
"django-localflavor==4.0",
|
||||
"django-markup",
|
||||
"django-mysql",
|
||||
"django-oauth-toolkit==2.2.*",
|
||||
"django-otp==1.2.*",
|
||||
"django-phonenumber-field==7.1.*",
|
||||
@@ -87,6 +86,7 @@ dependencies = [
|
||||
"python-dateutil==2.8.*",
|
||||
"python-u2flib-server==4.*",
|
||||
"pytz",
|
||||
"pytz-deprecation-shim==0.1.*",
|
||||
"pyuca",
|
||||
"qrcode==7.4.*",
|
||||
"redis==4.5.*,>=4.5.4",
|
||||
@@ -108,7 +108,6 @@ dependencies = [
|
||||
|
||||
[project.optional-dependencies]
|
||||
memcached = ["pylibmc"]
|
||||
mysql = ["mysqlclient"]
|
||||
dev = [
|
||||
"coverage",
|
||||
"coveralls",
|
||||
@@ -125,7 +124,7 @@ dev = [
|
||||
"pytest-mock==3.10.*",
|
||||
"pytest-rerunfailures==11.*",
|
||||
"pytest-sugar",
|
||||
"pytest-xdist==3.2.*",
|
||||
"pytest-xdist==3.3.*",
|
||||
"pytest==7.3.*",
|
||||
"responses",
|
||||
]
|
||||
|
||||
@@ -30,7 +30,6 @@ from django.utils.translation import gettext_lazy as _ # NOQA
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
|
||||
|
||||
USE_I18N = True
|
||||
USE_L10N = True
|
||||
USE_TZ = True
|
||||
|
||||
INSTALLED_APPS = [
|
||||
@@ -68,6 +67,7 @@ INSTALLED_APPS = [
|
||||
'oauth2_provider',
|
||||
'phonenumber_field',
|
||||
'statici18n',
|
||||
'django.forms', # after pretix.base for overrides
|
||||
]
|
||||
|
||||
FORMAT_MODULE_PATH = [
|
||||
@@ -180,6 +180,8 @@ TEMPLATES = [
|
||||
},
|
||||
]
|
||||
|
||||
FORM_RENDERER = "django.forms.renderers.TemplatesSetting"
|
||||
|
||||
STATIC_ROOT = os.path.join(os.path.dirname(__file__), 'static.dist')
|
||||
|
||||
STATICFILES_FINDERS = (
|
||||
|
||||
@@ -535,8 +535,9 @@ class OrderPaymentTypeField(serializers.Field):
|
||||
# TODO: Remove after pretix 2.2
|
||||
def to_representation(self, instance: Order):
|
||||
t = None
|
||||
for p in instance.payments.all():
|
||||
t = p.provider
|
||||
if instance.pk:
|
||||
for p in instance.payments.all():
|
||||
t = p.provider
|
||||
return t
|
||||
|
||||
|
||||
@@ -544,10 +545,10 @@ class OrderPaymentDateField(serializers.DateField):
|
||||
# TODO: Remove after pretix 2.2
|
||||
def to_representation(self, instance: Order):
|
||||
t = None
|
||||
for p in instance.payments.all():
|
||||
t = p.payment_date or t
|
||||
if instance.pk:
|
||||
for p in instance.payments.all():
|
||||
t = p.payment_date or t
|
||||
if t:
|
||||
|
||||
return super().to_representation(t.date())
|
||||
|
||||
|
||||
@@ -1363,6 +1364,7 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
|
||||
answers.append(answ)
|
||||
pos.answers = answers
|
||||
pos.pseudonymization_id = "PREVIEW"
|
||||
pos.checkins = []
|
||||
pos_map[pos.positionid] = pos
|
||||
else:
|
||||
if pos.voucher:
|
||||
@@ -1459,6 +1461,8 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
|
||||
if simulate:
|
||||
order.fees = fees
|
||||
order.positions = pos_map.values()
|
||||
order.payments = []
|
||||
order.refunds = []
|
||||
return order # ignore payments
|
||||
else:
|
||||
order.save(update_fields=['total'])
|
||||
|
||||
@@ -35,8 +35,7 @@
|
||||
import importlib
|
||||
|
||||
from django.apps import apps
|
||||
from django.conf.urls import re_path
|
||||
from django.urls import include
|
||||
from django.urls import include, re_path
|
||||
from rest_framework import routers
|
||||
|
||||
from pretix.api.views import cart
|
||||
|
||||
@@ -23,9 +23,9 @@ import datetime
|
||||
import mimetypes
|
||||
import os
|
||||
from decimal import Decimal
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import django_filters
|
||||
import pytz
|
||||
from django.db import transaction
|
||||
from django.db.models import (
|
||||
Exists, F, OuterRef, Prefetch, Q, Subquery, prefetch_related_objects,
|
||||
@@ -612,7 +612,7 @@ class OrderViewSet(viewsets.ModelViewSet):
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
tz = pytz.timezone(self.request.event.settings.timezone)
|
||||
tz = ZoneInfo(self.request.event.settings.timezone)
|
||||
new_date = make_aware(datetime.datetime.combine(
|
||||
new_date,
|
||||
datetime.time(hour=23, minute=59, second=59)
|
||||
|
||||
@@ -37,8 +37,8 @@ import tempfile
|
||||
from collections import OrderedDict, namedtuple
|
||||
from decimal import Decimal
|
||||
from typing import Optional, Tuple
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import pytz
|
||||
from defusedcsv import csv
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
@@ -68,7 +68,7 @@ class BaseExporter:
|
||||
self.events = event
|
||||
self.event = None
|
||||
e = self.events.first()
|
||||
self.timezone = e.timezone if e else pytz.timezone(settings.TIME_ZONE)
|
||||
self.timezone = e.timezone if e else ZoneInfo(settings.TIME_ZONE)
|
||||
else:
|
||||
self.events = Event.objects.filter(pk=event.pk)
|
||||
self.timezone = event.timezone
|
||||
|
||||
@@ -34,8 +34,8 @@
|
||||
|
||||
from collections import OrderedDict
|
||||
from decimal import Decimal
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import pytz
|
||||
from django import forms
|
||||
from django.db.models import (
|
||||
Case, CharField, Count, DateTimeField, F, IntegerField, Max, Min, OuterRef,
|
||||
@@ -326,7 +326,7 @@ class OrderListExporter(MultiSheetListExporter):
|
||||
|
||||
yield self.ProgressSetTotal(total=qs.count())
|
||||
for order in qs.order_by('datetime').iterator():
|
||||
tz = pytz.timezone(self.event_object_cache[order.event_id].settings.timezone)
|
||||
tz = ZoneInfo(self.event_object_cache[order.event_id].settings.timezone)
|
||||
|
||||
row = [
|
||||
self.event_object_cache[order.event_id].slug,
|
||||
@@ -459,7 +459,7 @@ class OrderListExporter(MultiSheetListExporter):
|
||||
yield self.ProgressSetTotal(total=qs.count())
|
||||
for op in qs.order_by('order__datetime').iterator():
|
||||
order = op.order
|
||||
tz = pytz.timezone(order.event.settings.timezone)
|
||||
tz = ZoneInfo(order.event.settings.timezone)
|
||||
row = [
|
||||
self.event_object_cache[order.event_id].slug,
|
||||
order.code,
|
||||
@@ -631,7 +631,7 @@ class OrderListExporter(MultiSheetListExporter):
|
||||
|
||||
for op in ops:
|
||||
order = op.order
|
||||
tz = pytz.timezone(self.event_object_cache[order.event_id].settings.timezone)
|
||||
tz = ZoneInfo(self.event_object_cache[order.event_id].settings.timezone)
|
||||
row = [
|
||||
self.event_object_cache[order.event_id].slug,
|
||||
order.code,
|
||||
@@ -1024,7 +1024,7 @@ class PaymentListExporter(ListExporter):
|
||||
|
||||
yield self.ProgressSetTotal(total=len(objs))
|
||||
for obj in objs:
|
||||
tz = pytz.timezone(obj.order.event.settings.timezone)
|
||||
tz = ZoneInfo(obj.order.event.settings.timezone)
|
||||
if isinstance(obj, OrderPayment) and obj.payment_date:
|
||||
d2 = obj.payment_date.astimezone(tz).date().strftime('%Y-%m-%d')
|
||||
elif isinstance(obj, OrderRefund) and obj.execution_date:
|
||||
@@ -1203,7 +1203,7 @@ class GiftcardRedemptionListExporter(ListExporter):
|
||||
yield headers
|
||||
|
||||
for obj in objs:
|
||||
tz = pytz.timezone(obj.order.event.settings.timezone)
|
||||
tz = ZoneInfo(obj.order.event.settings.timezone)
|
||||
gc = GiftCard.objects.get(pk=obj.info_data.get('gift_card'))
|
||||
row = [
|
||||
obj.order.event.slug,
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
from collections import OrderedDict
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import pytz
|
||||
from django import forms
|
||||
from django.db.models import F, Q
|
||||
from django.dispatch import receiver
|
||||
@@ -137,7 +137,7 @@ class WaitingListExporter(ListExporter):
|
||||
|
||||
# which event should be used to output dates in columns "Start date" and "End date"
|
||||
event_for_date_columns = entry.subevent if entry.subevent else entry.event
|
||||
tz = pytz.timezone(entry.event.settings.timezone)
|
||||
tz = ZoneInfo(entry.event.settings.timezone)
|
||||
datetime_format = '%Y-%m-%d %H:%M:%S'
|
||||
|
||||
row = [
|
||||
|
||||
@@ -167,6 +167,7 @@ class SettingsForm(i18nfield.forms.I18nFormMixin, HierarkeyForm):
|
||||
|
||||
class PrefixForm(forms.Form):
|
||||
prefix = forms.CharField(widget=forms.HiddenInput)
|
||||
template_name = "django/forms/table.html"
|
||||
|
||||
|
||||
class SafeSessionWizardView(SessionWizardView):
|
||||
|
||||
@@ -38,10 +38,10 @@ import logging
|
||||
from datetime import timedelta
|
||||
from decimal import Decimal
|
||||
from io import BytesIO
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import dateutil.parser
|
||||
import pycountry
|
||||
import pytz
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
@@ -733,7 +733,7 @@ class BaseQuestionsForm(forms.Form):
|
||||
initial = answers[0]
|
||||
else:
|
||||
initial = None
|
||||
tz = pytz.timezone(event.settings.timezone)
|
||||
tz = ZoneInfo(event.settings.timezone)
|
||||
help_text = rich_text(q.help_text)
|
||||
label = escape(q.question) # django-bootstrap3 calls mark_safe
|
||||
required = q.required and not self.all_optional
|
||||
|
||||
63
src/pretix/base/forms/renderers.py
Normal file
63
src/pretix/base/forms/renderers.py
Normal file
@@ -0,0 +1,63 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
#
|
||||
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
||||
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
||||
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
||||
# this file, see <https://pretix.eu/about/en/license>.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
from bootstrap3.renderers import (
|
||||
FieldRenderer as BaseFieldRenderer,
|
||||
InlineFieldRenderer as BaseInlineFieldRenderer,
|
||||
)
|
||||
from django.forms import (
|
||||
CheckboxInput, CheckboxSelectMultiple, ClearableFileInput, RadioSelect,
|
||||
SelectDateWidget,
|
||||
)
|
||||
|
||||
|
||||
class FieldRenderer(BaseFieldRenderer):
|
||||
# Local application of https://github.com/zostera/django-bootstrap3/pull/859
|
||||
|
||||
def post_widget_render(self, html):
|
||||
if isinstance(self.widget, CheckboxSelectMultiple):
|
||||
html = self.list_to_class(html, "checkbox")
|
||||
elif isinstance(self.widget, RadioSelect):
|
||||
html = self.list_to_class(html, "radio")
|
||||
elif isinstance(self.widget, SelectDateWidget):
|
||||
html = self.fix_date_select_input(html)
|
||||
elif isinstance(self.widget, ClearableFileInput):
|
||||
html = self.fix_clearable_file_input(html)
|
||||
elif isinstance(self.widget, CheckboxInput):
|
||||
html = self.put_inside_label(html)
|
||||
return html
|
||||
|
||||
|
||||
class InlineFieldRenderer(BaseInlineFieldRenderer):
|
||||
# Local application of https://github.com/zostera/django-bootstrap3/pull/859
|
||||
|
||||
def post_widget_render(self, html):
|
||||
if isinstance(self.widget, CheckboxSelectMultiple):
|
||||
html = self.list_to_class(html, "checkbox")
|
||||
elif isinstance(self.widget, RadioSelect):
|
||||
html = self.list_to_class(html, "radio")
|
||||
elif isinstance(self.widget, SelectDateWidget):
|
||||
html = self.fix_date_select_input(html)
|
||||
elif isinstance(self.widget, ClearableFileInput):
|
||||
html = self.fix_clearable_file_input(html)
|
||||
elif isinstance(self.widget, CheckboxInput):
|
||||
html = self.put_inside_label(html)
|
||||
return html
|
||||
@@ -24,7 +24,7 @@ Django, for theoretically very valid reasons, creates migrations for *every sing
|
||||
we change on a model. Even the `help_text`! This makes sense, as we don't know if any
|
||||
database backend unknown to us might actually use this information for its database schema.
|
||||
|
||||
However, pretix only supports PostgreSQL, MySQL, MariaDB and SQLite and we can be pretty
|
||||
However, pretix only supports PostgreSQL and SQLite and we can be pretty
|
||||
certain that some changes to models will never require a change to the database. In this case,
|
||||
not creating a migration for certain changes will save us some performance while applying them
|
||||
*and* allow for a cleaner git history. Win-win!
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
import json
|
||||
import sys
|
||||
|
||||
import pytz
|
||||
import pytz_deprecation_shim
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.utils.timezone import override
|
||||
from django_scopes import scope
|
||||
@@ -60,7 +60,7 @@ class Command(BaseCommand):
|
||||
sys.exit(1)
|
||||
|
||||
locale = options.get("locale", None)
|
||||
timezone = pytz.timezone(options['timezone']) if options.get('timezone') else None
|
||||
timezone = pytz_deprecation_shim.timezone(options['timezone']) if options.get('timezone') else None
|
||||
|
||||
with scope(organizer=o):
|
||||
if options['event_slug']:
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
#
|
||||
from collections import OrderedDict
|
||||
from urllib.parse import urlsplit
|
||||
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
||||
|
||||
import pytz
|
||||
from django.conf import settings
|
||||
from django.http import Http404, HttpRequest, HttpResponse
|
||||
from django.middleware.common import CommonMiddleware
|
||||
@@ -98,9 +98,9 @@ class LocaleMiddleware(MiddlewareMixin):
|
||||
tzname = request.user.timezone
|
||||
if tzname:
|
||||
try:
|
||||
timezone.activate(pytz.timezone(tzname))
|
||||
timezone.activate(ZoneInfo(tzname))
|
||||
request.timezone = tzname
|
||||
except pytz.UnknownTimeZoneError:
|
||||
except ZoneInfoNotFoundError:
|
||||
pass
|
||||
else:
|
||||
timezone.deactivate()
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
# Generated by Django 1.10.4 on 2017-02-03 14:21
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import django.core.validators
|
||||
import django.db.migrations.operations.special
|
||||
import django.db.models.deletion
|
||||
@@ -26,7 +28,7 @@ def forwards42(apps, schema_editor):
|
||||
for s in EventSetting.objects.filter(key='timezone').values('object_id', 'value')
|
||||
}
|
||||
for order in Order.objects.all():
|
||||
tz = pytz.timezone(etz.get(order.event_id, 'UTC'))
|
||||
tz = ZoneInfo(etz.get(order.event_id, 'UTC'))
|
||||
order.expires = order.expires.astimezone(tz).replace(hour=23, minute=59, second=59)
|
||||
order.save()
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
# Generated by Django 1.10.2 on 2016-10-19 17:57
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import pytz
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from django.db import migrations
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
def forwards(apps, schema_editor):
|
||||
@@ -15,7 +15,7 @@ def forwards(apps, schema_editor):
|
||||
for s in EventSetting.objects.filter(key='timezone').values('object_id', 'value')
|
||||
}
|
||||
for order in Order.objects.all():
|
||||
tz = pytz.timezone(etz.get(order.event_id, 'UTC'))
|
||||
tz = ZoneInfo(etz.get(order.event_id, 'UTC'))
|
||||
order.expires = order.expires.astimezone(tz).replace(hour=23, minute=59, second=59)
|
||||
order.save()
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.db import migrations, models
|
||||
from django_mysql.checks import mysql_connections
|
||||
|
||||
|
||||
def set_attendee_name_parts(apps, schema_editor):
|
||||
@@ -24,40 +23,12 @@ def set_attendee_name_parts(apps, schema_editor):
|
||||
ia.save(update_fields=['name_parts'])
|
||||
|
||||
|
||||
def check_mysqlversion(apps, schema_editor):
|
||||
errors = []
|
||||
any_conn_works = False
|
||||
conns = list(mysql_connections())
|
||||
found = 'Unknown version'
|
||||
for alias, conn in conns:
|
||||
if hasattr(conn, 'mysql_is_mariadb') and conn.mysql_is_mariadb and hasattr(conn, 'mysql_version'):
|
||||
if conn.mysql_version >= (10, 2, 7):
|
||||
any_conn_works = True
|
||||
else:
|
||||
found = 'MariaDB ' + '.'.join(str(v) for v in conn.mysql_version)
|
||||
elif hasattr(conn, 'mysql_version'):
|
||||
if conn.mysql_version >= (5, 7):
|
||||
any_conn_works = True
|
||||
else:
|
||||
found = 'MySQL ' + '.'.join(str(v) for v in conn.mysql_version)
|
||||
|
||||
if conns and not any_conn_works:
|
||||
raise ImproperlyConfigured(
|
||||
'As of pretix 2.2, you need MySQL 5.7+ or MariaDB 10.2.7+ to run pretix. However, we detected a '
|
||||
'database connection to {}'.format(found)
|
||||
)
|
||||
return errors
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('pretixbase', '0101_auto_20181025_2255'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(
|
||||
check_mysqlversion, migrations.RunPython.noop
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='cartposition',
|
||||
old_name='attendee_name',
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
# Generated by Django 3.2.4 on 2021-09-30 10:25
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from django.db import migrations, models
|
||||
from pytz import UTC
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
@@ -15,7 +14,7 @@ class Migration(migrations.Migration):
|
||||
migrations.AddField(
|
||||
model_name='invoice',
|
||||
name='sent_to_customer',
|
||||
field=models.DateTimeField(blank=True, null=True, default=UTC.localize(datetime(1970, 1, 1, 0, 0, 0, 0))),
|
||||
field=models.DateTimeField(blank=True, null=True, default=datetime(1970, 1, 1, 0, 0, 0, 0, tzinfo=timezone.utc)),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
||||
|
||||
@@ -50,6 +50,6 @@ class Migration(migrations.Migration):
|
||||
],
|
||||
options={
|
||||
'unique_together': {('event', 'secret')},
|
||||
} if 'mysql' not in settings.DATABASES['default']['ENGINE'] else {}
|
||||
}
|
||||
),
|
||||
]
|
||||
|
||||
@@ -121,14 +121,23 @@ class Customer(LoggedModel):
|
||||
if self.email:
|
||||
self.email = self.email.lower()
|
||||
if 'update_fields' in kwargs and 'last_modified' not in kwargs['update_fields']:
|
||||
kwargs['update_fields'] = list(kwargs['update_fields']) + ['last_modified']
|
||||
kwargs['update_fields'] = {'last_modified'}.union(kwargs['update_fields'])
|
||||
if not self.identifier:
|
||||
self.assign_identifier()
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'identifier'}.union(kwargs['update_fields'])
|
||||
if self.name_parts:
|
||||
self.name_cached = self.name
|
||||
name = self.name
|
||||
if self.name_cached != name:
|
||||
self.name_cached = name
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'name_cached'}.union(kwargs['update_fields'])
|
||||
else:
|
||||
self.name_cached = ""
|
||||
self.name_parts = {}
|
||||
if self.name_cached != "" or self.name_parts != {}:
|
||||
self.name_cached = ""
|
||||
self.name_parts = {}
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'name_cached', 'name_parts'}.union(kwargs['update_fields'])
|
||||
super().save(**kwargs)
|
||||
|
||||
def anonymize(self):
|
||||
|
||||
@@ -98,6 +98,8 @@ class Gate(LoggedModel):
|
||||
if not Gate.objects.filter(organizer=self.organizer, identifier=code).exists():
|
||||
self.identifier = code
|
||||
break
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'identifier'}.union(kwargs['update_fields'])
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
|
||||
@@ -173,6 +175,8 @@ class Device(LoggedModel):
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.device_id:
|
||||
self.device_id = (self.organizer.devices.aggregate(m=Max('device_id'))['m'] or 0) + 1
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'device_id'}.union(kwargs['update_fields'])
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def permission_set(self) -> set:
|
||||
|
||||
@@ -40,8 +40,9 @@ from collections import Counter, OrderedDict, defaultdict
|
||||
from datetime import datetime, time, timedelta
|
||||
from operator import attrgetter
|
||||
from urllib.parse import urljoin
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import pytz
|
||||
import pytz_deprecation_shim
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.files.storage import default_storage
|
||||
@@ -214,7 +215,7 @@ class EventMixin:
|
||||
|
||||
@property
|
||||
def timezone(self):
|
||||
return pytz.timezone(self.settings.timezone)
|
||||
return pytz_deprecation_shim.timezone(self.settings.timezone)
|
||||
|
||||
@property
|
||||
def effective_presale_end(self):
|
||||
@@ -773,7 +774,7 @@ class Event(EventMixin, LoggedModel):
|
||||
"""
|
||||
The last datetime of payments for this event.
|
||||
"""
|
||||
tz = pytz.timezone(self.settings.timezone)
|
||||
tz = ZoneInfo(self.settings.timezone)
|
||||
return make_aware(datetime.combine(
|
||||
self.settings.get('payment_term_last', as_type=RelativeDateWrapper).datetime(self).date(),
|
||||
time(hour=23, minute=59, second=59)
|
||||
|
||||
@@ -19,10 +19,11 @@
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
import zoneinfo
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import pytz
|
||||
from dateutil.rrule import rrulestr
|
||||
from dateutil.tz import datetime_exists
|
||||
from django.conf import settings
|
||||
from django.core.serializers.json import DjangoJSONEncoder
|
||||
from django.db import models
|
||||
@@ -108,12 +109,9 @@ class AbstractScheduledExport(LoggedModel):
|
||||
self.schedule_next_run = None
|
||||
return
|
||||
|
||||
try:
|
||||
self.schedule_next_run = make_aware(datetime.combine(new_d.date(), self.schedule_rrule_time), tz)
|
||||
except pytz.exceptions.AmbiguousTimeError:
|
||||
self.schedule_next_run = make_aware(datetime.combine(new_d.date(), self.schedule_rrule_time), tz, is_dst=False)
|
||||
except pytz.exceptions.NonExistentTimeError:
|
||||
self.schedule_next_run = make_aware(datetime.combine(new_d.date(), self.schedule_rrule_time) + timedelta(hours=1), tz)
|
||||
self.schedule_next_run = make_aware(datetime.combine(new_d.date(), self.schedule_rrule_time), tz)
|
||||
if not datetime_exists(self.schedule_next_run):
|
||||
self.schedule_next_run += timedelta(hours=1)
|
||||
|
||||
|
||||
class ScheduledEventExport(AbstractScheduledExport):
|
||||
@@ -136,4 +134,4 @@ class ScheduledOrganizerExport(AbstractScheduledExport):
|
||||
|
||||
@property
|
||||
def tz(self):
|
||||
return pytz.timezone(self.timezone)
|
||||
return zoneinfo.ZoneInfo(self.timezone)
|
||||
|
||||
@@ -251,14 +251,20 @@ class Invoice(models.Model):
|
||||
raise ValueError('Every invoice needs to be connected to an order')
|
||||
if not self.event:
|
||||
self.event = self.order.event
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'event'}.union(kwargs['update_fields'])
|
||||
if not self.organizer:
|
||||
self.organizer = self.order.event.organizer
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'organizer'}.union(kwargs['update_fields'])
|
||||
if not self.prefix:
|
||||
self.prefix = self.event.settings.invoice_numbers_prefix or (self.event.slug.upper() + '-')
|
||||
if self.is_cancellation:
|
||||
self.prefix = self.event.settings.invoice_numbers_prefix_cancellations or self.prefix
|
||||
if '%' in self.prefix:
|
||||
self.prefix = self.date.strftime(self.prefix)
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'prefix'}.union(kwargs['update_fields'])
|
||||
|
||||
if not self.invoice_no:
|
||||
if self.order.testmode:
|
||||
@@ -276,8 +282,13 @@ class Invoice(models.Model):
|
||||
# Suppress duplicate key errors and try again
|
||||
if i == 9:
|
||||
raise
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'invoice_no'}.union(kwargs['update_fields'])
|
||||
|
||||
self.full_invoice_no = self.prefix + self.invoice_no
|
||||
if self.full_invoice_no != self.prefix + self.invoice_no:
|
||||
self.full_invoice_no = self.prefix + self.invoice_no
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'full_invoice_no'}.union(kwargs['update_fields'])
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
|
||||
@@ -40,9 +40,10 @@ from collections import Counter, OrderedDict
|
||||
from datetime import date, datetime, time, timedelta
|
||||
from decimal import Decimal, DecimalException
|
||||
from typing import Optional, Tuple
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import dateutil.parser
|
||||
import pytz
|
||||
from dateutil.tz import datetime_exists
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import (
|
||||
@@ -927,22 +928,22 @@ class Item(LoggedModel):
|
||||
)
|
||||
if self.validity_dynamic_duration_days:
|
||||
replace_date += timedelta(days=self.validity_dynamic_duration_days)
|
||||
valid_until = tz.localize(valid_until.replace(
|
||||
valid_until = valid_until.replace(
|
||||
year=replace_date.year,
|
||||
month=replace_date.month,
|
||||
day=replace_date.day,
|
||||
hour=23, minute=59, second=59, microsecond=0,
|
||||
tzinfo=None,
|
||||
))
|
||||
tzinfo=tz,
|
||||
)
|
||||
elif self.validity_dynamic_duration_days:
|
||||
replace_date = valid_until.date() + timedelta(days=self.validity_dynamic_duration_days - 1)
|
||||
valid_until = tz.localize(valid_until.replace(
|
||||
valid_until = valid_until.replace(
|
||||
year=replace_date.year,
|
||||
month=replace_date.month,
|
||||
day=replace_date.day,
|
||||
hour=23, minute=59, second=59, microsecond=0,
|
||||
tzinfo=None
|
||||
))
|
||||
tzinfo=tz
|
||||
)
|
||||
|
||||
if self.validity_dynamic_duration_hours:
|
||||
valid_until += timedelta(hours=self.validity_dynamic_duration_hours)
|
||||
@@ -950,6 +951,9 @@ class Item(LoggedModel):
|
||||
if self.validity_dynamic_duration_minutes:
|
||||
valid_until += timedelta(minutes=self.validity_dynamic_duration_minutes)
|
||||
|
||||
if not datetime_exists(valid_until):
|
||||
valid_until += timedelta(hours=1)
|
||||
|
||||
return requested_start, valid_until
|
||||
|
||||
else:
|
||||
@@ -1589,6 +1593,8 @@ class Question(LoggedModel):
|
||||
if not Question.objects.filter(event=self.event, identifier=code).exists():
|
||||
self.identifier = code
|
||||
break
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'identifier'}.union(kwargs['update_fields'])
|
||||
super().save(*args, **kwargs)
|
||||
if self.event:
|
||||
self.event.cache.clear()
|
||||
@@ -1678,7 +1684,7 @@ class Question(LoggedModel):
|
||||
try:
|
||||
dt = dateutil.parser.parse(answer)
|
||||
if is_naive(dt):
|
||||
dt = make_aware(dt, pytz.timezone(self.event.settings.timezone))
|
||||
dt = make_aware(dt, ZoneInfo(self.event.settings.timezone))
|
||||
except:
|
||||
raise ValidationError(_('Invalid datetime input.'))
|
||||
else:
|
||||
@@ -1736,6 +1742,8 @@ class QuestionOption(models.Model):
|
||||
if not QuestionOption.objects.filter(question__event=self.question.event, identifier=code).exists():
|
||||
self.identifier = code
|
||||
break
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'identifier'}.union(kwargs['update_fields'])
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -42,10 +42,10 @@ from collections import Counter
|
||||
from datetime import datetime, time, timedelta
|
||||
from decimal import Decimal
|
||||
from typing import Any, Dict, List, Union
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import dateutil
|
||||
import pycountry
|
||||
import pytz
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models, transaction
|
||||
@@ -461,14 +461,20 @@ class Order(LockModel, LoggedModel):
|
||||
return '{event}-{code}'.format(event=self.event.slug.upper(), code=self.code)
|
||||
|
||||
def save(self, **kwargs):
|
||||
if 'update_fields' in kwargs and 'last_modified' not in kwargs['update_fields']:
|
||||
kwargs['update_fields'] = list(kwargs['update_fields']) + ['last_modified']
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'last_modified'}.union(kwargs['update_fields'])
|
||||
if not self.code:
|
||||
self.assign_code()
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'code'}.union(kwargs['update_fields'])
|
||||
if not self.datetime:
|
||||
self.datetime = now()
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'datetime'}.union(kwargs['update_fields'])
|
||||
if not self.expires:
|
||||
self.set_expires()
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'expires'}.union(kwargs['update_fields'])
|
||||
|
||||
is_new = not self.pk
|
||||
update_fields = kwargs.get('update_fields', [])
|
||||
@@ -496,7 +502,7 @@ class Order(LockModel, LoggedModel):
|
||||
|
||||
def set_expires(self, now_dt=None, subevents=None):
|
||||
now_dt = now_dt or now()
|
||||
tz = pytz.timezone(self.event.settings.timezone)
|
||||
tz = ZoneInfo(self.event.settings.timezone)
|
||||
mode = self.event.settings.get('payment_term_mode')
|
||||
if mode == 'days':
|
||||
exp_by_date = now_dt.astimezone(tz) + timedelta(days=self.event.settings.get('payment_term_days', as_type=int))
|
||||
@@ -870,7 +876,7 @@ class Order(LockModel, LoggedModel):
|
||||
|
||||
@property
|
||||
def payment_term_last(self):
|
||||
tz = pytz.timezone(self.event.settings.timezone)
|
||||
tz = ZoneInfo(self.event.settings.timezone)
|
||||
term_last = self.event.settings.get('payment_term_last', as_type=RelativeDateWrapper)
|
||||
if term_last:
|
||||
if self.event.has_subevents:
|
||||
@@ -1230,7 +1236,7 @@ class QuestionAnswer(models.Model):
|
||||
try:
|
||||
d = dateutil.parser.parse(self.answer)
|
||||
if self.orderposition:
|
||||
tz = pytz.timezone(self.orderposition.order.event.settings.timezone)
|
||||
tz = ZoneInfo(self.orderposition.order.event.settings.timezone)
|
||||
d = d.astimezone(tz)
|
||||
return date_format(d, "SHORT_DATETIME_FORMAT")
|
||||
except ValueError:
|
||||
@@ -1442,12 +1448,20 @@ class AbstractPosition(models.Model):
|
||||
else self.variation.quotas.filter(subevent=self.subevent))
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
update_fields = kwargs.get('update_fields', [])
|
||||
update_fields = kwargs.get('update_fields', set())
|
||||
if 'attendee_name_parts' in update_fields:
|
||||
update_fields.append('attendee_name_cached')
|
||||
self.attendee_name_cached = self.attendee_name
|
||||
kwargs['update_fields'] = {'attendee_name_cached'}.union(kwargs['update_fields'])
|
||||
|
||||
name = self.attendee_name
|
||||
if name != self.attendee_name_cached:
|
||||
self.attendee_name_cached = name
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'attendee_name_cached'}.union(kwargs['update_fields'])
|
||||
|
||||
if self.attendee_name_parts is None:
|
||||
self.attendee_name_parts = {}
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'attendee_name_parts'}.union(kwargs['update_fields'])
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
@property
|
||||
@@ -1827,6 +1841,8 @@ class OrderPayment(models.Model):
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.local_id:
|
||||
self.local_id = (self.order.payments.aggregate(m=Max('local_id'))['m'] or 0) + 1
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'local_id'}.union(kwargs['update_fields'])
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def create_external_refund(self, amount=None, execution_date=None, info='{}'):
|
||||
@@ -2025,6 +2041,8 @@ class OrderRefund(models.Model):
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.local_id:
|
||||
self.local_id = (self.order.refunds.aggregate(m=Max('local_id'))['m'] or 0) + 1
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'local_id'}.union(kwargs['update_fields'])
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
|
||||
@@ -2443,14 +2461,20 @@ class OrderPosition(AbstractPosition):
|
||||
assign_ticket_secret(
|
||||
event=self.order.event, position=self, force_invalidate=True, save=False
|
||||
)
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'secret'}.union(kwargs['update_fields'])
|
||||
|
||||
if not self.blocked:
|
||||
if not self.blocked and self.blocked is not None:
|
||||
self.blocked = None
|
||||
elif not isinstance(self.blocked, list) or any(not isinstance(b, str) for b in self.blocked):
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'blocked'}.union(kwargs['update_fields'])
|
||||
elif self.blocked and (not isinstance(self.blocked, list) or any(not isinstance(b, str) for b in self.blocked)):
|
||||
raise TypeError("blocked needs to be a list of strings")
|
||||
|
||||
if not self.pseudonymization_id:
|
||||
self.assign_pseudonymization_id()
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'pseudonymization_id'}.union(kwargs['update_fields'])
|
||||
|
||||
if not self.get_deferred_fields():
|
||||
if Transaction.key(self) != self.__initial_transaction_key or self.canceled != self.__initial_canceled or not self.pk:
|
||||
@@ -2936,10 +2960,17 @@ class InvoiceAddress(models.Model):
|
||||
self.order.touch()
|
||||
|
||||
if self.name_parts:
|
||||
self.name_cached = self.name
|
||||
name = self.name
|
||||
if self.name_cached != name:
|
||||
self.name_cached = self.name
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'name_cached'}.union(kwargs['update_fields'])
|
||||
else:
|
||||
self.name_cached = ""
|
||||
self.name_parts = {}
|
||||
if self.name_cached != "" or self.name_parts != {}:
|
||||
self.name_cached = ""
|
||||
self.name_parts = {}
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'name_cached', 'name_parts'}.union(kwargs['update_fields'])
|
||||
super().save(**kwargs)
|
||||
|
||||
def describe(self):
|
||||
@@ -3085,11 +3116,7 @@ class BlockedTicketSecret(models.Model):
|
||||
updated = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
if 'mysql' not in settings.DATABASES['default']['ENGINE']:
|
||||
# MySQL does not support indexes on TextField(). Django knows this and just ignores db_index, but it will
|
||||
# not silently ignore the UNIQUE index, causing this table to fail. I'm so glad we're deprecating MySQL
|
||||
# in a few months, so we'll just live without an unique index until then.
|
||||
unique_together = (('event', 'secret'),)
|
||||
unique_together = (('event', 'secret'),)
|
||||
|
||||
|
||||
@receiver(post_delete, sender=CachedTicket)
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
import string
|
||||
from datetime import date, datetime, time
|
||||
|
||||
import pytz
|
||||
import pytz_deprecation_shim
|
||||
from django.conf import settings
|
||||
from django.core.mail import get_connection
|
||||
from django.core.validators import MinLengthValidator, RegexValidator
|
||||
@@ -102,6 +102,7 @@ class Organizer(LoggedModel):
|
||||
is_new = not self.pk
|
||||
obj = super().save(*args, **kwargs)
|
||||
if is_new:
|
||||
kwargs.pop('update_fields', None) # does not make sense here
|
||||
self.set_defaults()
|
||||
else:
|
||||
self.get_cache().clear()
|
||||
@@ -140,7 +141,7 @@ class Organizer(LoggedModel):
|
||||
|
||||
@property
|
||||
def timezone(self):
|
||||
return pytz.timezone(self.settings.timezone)
|
||||
return pytz_deprecation_shim.timezone(self.settings.timezone)
|
||||
|
||||
@cached_property
|
||||
def all_logentries_link(self):
|
||||
|
||||
@@ -502,7 +502,10 @@ class Voucher(LoggedModel):
|
||||
return seat
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.code = self.code.upper()
|
||||
if self.code != self.code.upper():
|
||||
self.code = self.code.upper()
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'code'}.union(kwargs['update_fields'])
|
||||
super().save(*args, **kwargs)
|
||||
self.event.cache.set('vouchers_exist', True)
|
||||
|
||||
|
||||
@@ -126,12 +126,19 @@ class WaitingListEntry(LoggedModel):
|
||||
raise ValidationError('Invalid input')
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
update_fields = kwargs.get('update_fields', [])
|
||||
update_fields = kwargs.get('update_fields', set())
|
||||
if 'name_parts' in update_fields:
|
||||
update_fields.append('name_cached')
|
||||
self.name_cached = self.name
|
||||
kwargs['update_fields'] = {'name_cached'}.union(kwargs['update_fields'])
|
||||
name = self.name
|
||||
if name != self.name_cached:
|
||||
self.name_cached = name
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'name_cached'}.union(kwargs['update_fields'])
|
||||
|
||||
if self.name_parts is None:
|
||||
self.name_parts = {}
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'name_parts'}.union(kwargs['update_fields'])
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
@property
|
||||
|
||||
@@ -210,7 +210,7 @@ class SubeventColumn(ImportColumn):
|
||||
for format in input_formats:
|
||||
try:
|
||||
d = datetime.datetime.strptime(value, format)
|
||||
d = self.event.timezone.localize(d)
|
||||
d = d.replace(tzinfo=self.event.timezone)
|
||||
try:
|
||||
se = self.event.subevents.get(
|
||||
active=True,
|
||||
@@ -660,7 +660,7 @@ class ValidFrom(ImportColumn):
|
||||
for format in input_formats:
|
||||
try:
|
||||
d = datetime.datetime.strptime(value, format)
|
||||
d = self.event.timezone.localize(d)
|
||||
d = d.replace(tzinfo=self.event.timezone)
|
||||
return d
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
@@ -683,7 +683,7 @@ class ValidUntil(ImportColumn):
|
||||
for format in input_formats:
|
||||
try:
|
||||
d = datetime.datetime.strptime(value, format)
|
||||
d = self.event.timezone.localize(d)
|
||||
d = d.replace(tzinfo=self.event.timezone)
|
||||
return d
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
@@ -39,8 +39,8 @@ import logging
|
||||
from collections import OrderedDict
|
||||
from decimal import ROUND_HALF_UP, Decimal
|
||||
from typing import Any, Dict, Union
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import pytz
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
@@ -518,7 +518,7 @@ class BasePaymentProvider:
|
||||
|
||||
def _is_still_available(self, now_dt=None, cart_id=None, order=None):
|
||||
now_dt = now_dt or now()
|
||||
tz = pytz.timezone(self.event.settings.timezone)
|
||||
tz = ZoneInfo(self.event.settings.timezone)
|
||||
|
||||
availability_date = self.settings.get('_availability_date', as_type=RelativeDateWrapper)
|
||||
if availability_date:
|
||||
|
||||
@@ -61,7 +61,6 @@ from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _, pgettext
|
||||
from i18nfield.strings import LazyI18nString
|
||||
from pypdf import PdfReader
|
||||
from pytz import timezone
|
||||
from reportlab.graphics import renderPDF
|
||||
from reportlab.graphics.barcode.qr import QrCodeWidget
|
||||
from reportlab.graphics.shapes import Drawing
|
||||
@@ -237,7 +236,7 @@ DEFAULT_VARIABLES = OrderedDict((
|
||||
"label": _("Event begin date and time"),
|
||||
"editor_sample": _("2017-05-31 20:00"),
|
||||
"evaluate": lambda op, order, ev: date_format(
|
||||
ev.date_from.astimezone(timezone(ev.settings.timezone)),
|
||||
ev.date_from.astimezone(ev.timezone),
|
||||
"SHORT_DATETIME_FORMAT"
|
||||
) if ev.date_from else ""
|
||||
}),
|
||||
@@ -245,7 +244,7 @@ DEFAULT_VARIABLES = OrderedDict((
|
||||
"label": _("Event begin date"),
|
||||
"editor_sample": _("2017-05-31"),
|
||||
"evaluate": lambda op, order, ev: date_format(
|
||||
ev.date_from.astimezone(timezone(ev.settings.timezone)),
|
||||
ev.date_from.astimezone(ev.timezone),
|
||||
"SHORT_DATE_FORMAT"
|
||||
) if ev.date_from else ""
|
||||
}),
|
||||
@@ -263,7 +262,7 @@ DEFAULT_VARIABLES = OrderedDict((
|
||||
"label": _("Event end date and time"),
|
||||
"editor_sample": _("2017-05-31 22:00"),
|
||||
"evaluate": lambda op, order, ev: date_format(
|
||||
ev.date_to.astimezone(timezone(ev.settings.timezone)),
|
||||
ev.date_to.astimezone(ev.timezone),
|
||||
"SHORT_DATETIME_FORMAT"
|
||||
) if ev.date_to else ""
|
||||
}),
|
||||
@@ -271,7 +270,7 @@ DEFAULT_VARIABLES = OrderedDict((
|
||||
"label": _("Event end date"),
|
||||
"editor_sample": _("2017-05-31"),
|
||||
"evaluate": lambda op, order, ev: date_format(
|
||||
ev.date_to.astimezone(timezone(ev.settings.timezone)),
|
||||
ev.date_to.astimezone(ev.timezone),
|
||||
"SHORT_DATE_FORMAT"
|
||||
) if ev.date_to else ""
|
||||
}),
|
||||
@@ -279,7 +278,7 @@ DEFAULT_VARIABLES = OrderedDict((
|
||||
"label": _("Event end time"),
|
||||
"editor_sample": _("22:00"),
|
||||
"evaluate": lambda op, order, ev: date_format(
|
||||
ev.date_to.astimezone(timezone(ev.settings.timezone)),
|
||||
ev.date_to.astimezone(ev.timezone),
|
||||
"TIME_FORMAT"
|
||||
) if ev.date_to else ""
|
||||
}),
|
||||
@@ -292,7 +291,7 @@ DEFAULT_VARIABLES = OrderedDict((
|
||||
"label": _("Event admission date and time"),
|
||||
"editor_sample": _("2017-05-31 19:00"),
|
||||
"evaluate": lambda op, order, ev: date_format(
|
||||
ev.date_admission.astimezone(timezone(ev.settings.timezone)),
|
||||
ev.date_admission.astimezone(ev.timezone),
|
||||
"SHORT_DATETIME_FORMAT"
|
||||
) if ev.date_admission else ""
|
||||
}),
|
||||
@@ -300,7 +299,7 @@ DEFAULT_VARIABLES = OrderedDict((
|
||||
"label": _("Event admission time"),
|
||||
"editor_sample": _("19:00"),
|
||||
"evaluate": lambda op, order, ev: date_format(
|
||||
ev.date_admission.astimezone(timezone(ev.settings.timezone)),
|
||||
ev.date_admission.astimezone(ev.timezone),
|
||||
"TIME_FORMAT"
|
||||
) if ev.date_admission else ""
|
||||
}),
|
||||
@@ -385,7 +384,7 @@ DEFAULT_VARIABLES = OrderedDict((
|
||||
"label": _("Printing date"),
|
||||
"editor_sample": _("2017-05-31"),
|
||||
"evaluate": lambda op, order, ev: date_format(
|
||||
now().astimezone(timezone(ev.settings.timezone)),
|
||||
now().astimezone(ev.timezone),
|
||||
"SHORT_DATE_FORMAT"
|
||||
)
|
||||
}),
|
||||
@@ -393,7 +392,7 @@ DEFAULT_VARIABLES = OrderedDict((
|
||||
"label": _("Printing date and time"),
|
||||
"editor_sample": _("2017-05-31 19:00"),
|
||||
"evaluate": lambda op, order, ev: date_format(
|
||||
now().astimezone(timezone(ev.settings.timezone)),
|
||||
now().astimezone(ev.timezone),
|
||||
"SHORT_DATETIME_FORMAT"
|
||||
)
|
||||
}),
|
||||
@@ -401,7 +400,7 @@ DEFAULT_VARIABLES = OrderedDict((
|
||||
"label": _("Printing time"),
|
||||
"editor_sample": _("19:00"),
|
||||
"evaluate": lambda op, order, ev: date_format(
|
||||
now().astimezone(timezone(ev.settings.timezone)),
|
||||
now().astimezone(ev.timezone),
|
||||
"TIME_FORMAT"
|
||||
)
|
||||
}),
|
||||
@@ -409,7 +408,7 @@ DEFAULT_VARIABLES = OrderedDict((
|
||||
"label": _("Validity start date"),
|
||||
"editor_sample": _("2017-05-31"),
|
||||
"evaluate": lambda op, order, ev: date_format(
|
||||
op.valid_from.astimezone(timezone(ev.settings.timezone)),
|
||||
op.valid_from.astimezone(ev.timezone),
|
||||
"SHORT_DATE_FORMAT"
|
||||
) if op.valid_from else ""
|
||||
}),
|
||||
@@ -417,7 +416,7 @@ DEFAULT_VARIABLES = OrderedDict((
|
||||
"label": _("Validity start date and time"),
|
||||
"editor_sample": _("2017-05-31 19:00"),
|
||||
"evaluate": lambda op, order, ev: date_format(
|
||||
op.valid_from.astimezone(timezone(ev.settings.timezone)),
|
||||
op.valid_from.astimezone(ev.timezone),
|
||||
"SHORT_DATETIME_FORMAT"
|
||||
) if op.valid_from else ""
|
||||
}),
|
||||
@@ -425,7 +424,7 @@ DEFAULT_VARIABLES = OrderedDict((
|
||||
"label": _("Validity start time"),
|
||||
"editor_sample": _("19:00"),
|
||||
"evaluate": lambda op, order, ev: date_format(
|
||||
op.valid_from.astimezone(timezone(ev.settings.timezone)),
|
||||
op.valid_from.astimezone(ev.timezone),
|
||||
"TIME_FORMAT"
|
||||
) if op.valid_from else ""
|
||||
}),
|
||||
@@ -433,7 +432,7 @@ DEFAULT_VARIABLES = OrderedDict((
|
||||
"label": _("Validity end date"),
|
||||
"editor_sample": _("2017-05-31"),
|
||||
"evaluate": lambda op, order, ev: date_format(
|
||||
op.valid_until.astimezone(timezone(ev.settings.timezone)),
|
||||
op.valid_until.astimezone(ev.timezone),
|
||||
"SHORT_DATE_FORMAT"
|
||||
) if op.valid_until else ""
|
||||
}),
|
||||
@@ -441,7 +440,7 @@ DEFAULT_VARIABLES = OrderedDict((
|
||||
"label": _("Validity end date and time"),
|
||||
"editor_sample": _("2017-05-31 19:00"),
|
||||
"evaluate": lambda op, order, ev: date_format(
|
||||
op.valid_until.astimezone(timezone(ev.settings.timezone)),
|
||||
op.valid_until.astimezone(ev.timezone),
|
||||
"SHORT_DATETIME_FORMAT"
|
||||
) if op.valid_until else ""
|
||||
}),
|
||||
@@ -449,7 +448,7 @@ DEFAULT_VARIABLES = OrderedDict((
|
||||
"label": _("Validity end time"),
|
||||
"editor_sample": _("19:00"),
|
||||
"evaluate": lambda op, order, ev: date_format(
|
||||
op.valid_until.astimezone(timezone(ev.settings.timezone)),
|
||||
op.valid_until.astimezone(ev.timezone),
|
||||
"TIME_FORMAT"
|
||||
) if op.valid_until else ""
|
||||
}),
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
import datetime
|
||||
from collections import namedtuple
|
||||
from typing import Union
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import pytz
|
||||
from dateutil import parser
|
||||
from django import forms
|
||||
from django.core.exceptions import ValidationError
|
||||
@@ -67,7 +67,7 @@ class RelativeDateWrapper:
|
||||
if self.data.minutes_before is not None:
|
||||
raise ValueError('A minute-based relative datetime can not be used as a date')
|
||||
|
||||
tz = pytz.timezone(event.settings.timezone)
|
||||
tz = ZoneInfo(event.settings.timezone)
|
||||
if isinstance(event, SubEvent):
|
||||
base_date = (
|
||||
getattr(event, self.data.base_date_name)
|
||||
@@ -86,7 +86,7 @@ class RelativeDateWrapper:
|
||||
if isinstance(self.data, (datetime.datetime, datetime.date)):
|
||||
return self.data
|
||||
else:
|
||||
tz = pytz.timezone(event.settings.timezone)
|
||||
tz = ZoneInfo(event.settings.timezone)
|
||||
if isinstance(event, SubEvent):
|
||||
base_date = (
|
||||
getattr(event, self.data.base_date_name)
|
||||
@@ -99,8 +99,7 @@ class RelativeDateWrapper:
|
||||
if self.data.minutes_before is not None:
|
||||
return base_date.astimezone(tz) - datetime.timedelta(minutes=self.data.minutes_before)
|
||||
else:
|
||||
oldoffset = base_date.astimezone(tz).utcoffset()
|
||||
new_date = base_date.astimezone(tz) - datetime.timedelta(days=self.data.days_before)
|
||||
new_date = (base_date.astimezone(tz) - datetime.timedelta(days=self.data.days_before)).astimezone(tz)
|
||||
if self.data.time:
|
||||
new_date = new_date.replace(
|
||||
hour=self.data.time.hour,
|
||||
@@ -108,8 +107,6 @@ class RelativeDateWrapper:
|
||||
second=self.data.time.second
|
||||
)
|
||||
new_date = new_date.astimezone(tz)
|
||||
new_offset = new_date.utcoffset()
|
||||
new_date += oldoffset - new_offset
|
||||
return new_date
|
||||
|
||||
def to_string(self) -> str:
|
||||
|
||||
@@ -32,12 +32,12 @@
|
||||
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations under the License.
|
||||
import os
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from functools import partial, reduce
|
||||
|
||||
import dateutil
|
||||
import dateutil.parser
|
||||
import pytz
|
||||
from dateutil.tz import datetime_exists
|
||||
from django.core.files import File
|
||||
from django.db import IntegrityError, transaction
|
||||
from django.db.models import (
|
||||
@@ -439,7 +439,7 @@ class SQLLogic:
|
||||
|
||||
if operator == 'buildTime':
|
||||
if values[0] == "custom":
|
||||
return Value(dateutil.parser.parse(values[1]).astimezone(pytz.UTC))
|
||||
return Value(dateutil.parser.parse(values[1]).astimezone(timezone.utc))
|
||||
elif values[0] == "customtime":
|
||||
parsed = dateutil.parser.parse(values[1])
|
||||
return Value(now().astimezone(self.list.event.timezone).replace(
|
||||
@@ -447,7 +447,7 @@ class SQLLogic:
|
||||
minute=parsed.minute,
|
||||
second=parsed.second,
|
||||
microsecond=parsed.microsecond,
|
||||
).astimezone(pytz.UTC))
|
||||
).astimezone(timezone.utc))
|
||||
elif values[0] == 'date_from':
|
||||
return Coalesce(
|
||||
F('subevent__date_from'),
|
||||
@@ -475,7 +475,7 @@ class SQLLogic:
|
||||
return int(values[1])
|
||||
elif operator == 'var':
|
||||
if values[0] == 'now':
|
||||
return Value(now().astimezone(pytz.UTC))
|
||||
return Value(now().astimezone(timezone.utc))
|
||||
elif values[0] == 'now_isoweekday':
|
||||
return Value(now().astimezone(self.list.event.timezone).isoweekday())
|
||||
elif values[0] == 'product':
|
||||
@@ -926,14 +926,11 @@ def process_exit_all(sender, **kwargs):
|
||||
if cl.event.settings.get(f'autocheckin_dst_hack_{cl.pk}'): # move time back if yesterday was DST switch
|
||||
d -= timedelta(hours=1)
|
||||
cl.event.settings.delete(f'autocheckin_dst_hack_{cl.pk}')
|
||||
try:
|
||||
cl.exit_all_at = make_aware(datetime.combine(d.date() + timedelta(days=1), d.time()), cl.event.timezone)
|
||||
except pytz.exceptions.AmbiguousTimeError:
|
||||
cl.exit_all_at = make_aware(datetime.combine(d.date() + timedelta(days=1), d.time()), cl.event.timezone,
|
||||
is_dst=False)
|
||||
except pytz.exceptions.NonExistentTimeError:
|
||||
|
||||
cl.exit_all_at = make_aware(datetime.combine(d.date() + timedelta(days=1), d.time().replace(fold=1)), cl.event.timezone)
|
||||
if not datetime_exists(cl.exit_all_at):
|
||||
cl.event.settings.set(f'autocheckin_dst_hack_{cl.pk}', True)
|
||||
d += timedelta(hours=1)
|
||||
cl.exit_all_at = make_aware(datetime.combine(d.date() + timedelta(days=1), d.time()), cl.event.timezone)
|
||||
cl.exit_all_at = make_aware(datetime.combine(d.date() + timedelta(days=1), d.time().replace(fold=1)), cl.event.timezone)
|
||||
# AmbiguousTimeError shouldn't be possible since d.time() includes fold=0
|
||||
cl.save(update_fields=['exit_all_at'])
|
||||
|
||||
@@ -348,7 +348,7 @@ def generate_cancellation(invoice: Invoice, trigger_pdf=True):
|
||||
cancellation.prefix = None
|
||||
cancellation.refers = invoice
|
||||
cancellation.is_cancellation = True
|
||||
cancellation.date = timezone.now().date()
|
||||
cancellation.date = timezone.now().astimezone(invoice.event.timezone).date()
|
||||
cancellation.payment_provider_text = ''
|
||||
cancellation.payment_provider_stamp = ''
|
||||
cancellation.file = None
|
||||
|
||||
@@ -45,8 +45,8 @@ from email.mime.image import MIMEImage
|
||||
from email.utils import formataddr
|
||||
from typing import Any, Dict, List, Sequence, Union
|
||||
from urllib.parse import urljoin, urlparse
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import pytz
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
from celery import chain
|
||||
@@ -226,11 +226,11 @@ def mail(email: Union[str, Sequence[str]], subject: str, template: Union[str, La
|
||||
if event:
|
||||
timezone = event.timezone
|
||||
elif user:
|
||||
timezone = pytz.timezone(user.timezone)
|
||||
timezone = ZoneInfo(user.timezone)
|
||||
elif organizer:
|
||||
timezone = organizer.timezone
|
||||
else:
|
||||
timezone = pytz.timezone(settings.TIME_ZONE)
|
||||
timezone = ZoneInfo(settings.TIME_ZONE)
|
||||
|
||||
if settings_holder:
|
||||
if settings_holder.settings.mail_bcc:
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{# this is the version from django 3.x, prior to https://github.com/django/django/commit/5942ab5eb165ee2e759174e297148a40dd855920 so that django-bootstrap3 can keep doing its magic #}
|
||||
{% with id=widget.attrs.id %}<ul{% if id %} id="{{ id }}"{% endif %}{% if widget.attrs.class %} class="{{ widget.attrs.class }}"{% endif %}>{% for group, options, index in widget.optgroups %}{% if group %}
|
||||
<li>{{ group }}<ul{% if id %} id="{{ id }}_{{ index }}"{% endif %}>{% endif %}{% for option in options %}
|
||||
<li>{% include option.template_name with widget=option %}</li>{% endfor %}{% if group %}
|
||||
</ul></li>{% endif %}{% endfor %}
|
||||
</ul>{% endwith %}
|
||||
@@ -86,6 +86,11 @@
|
||||
hyphens: auto;
|
||||
}
|
||||
|
||||
p a {
|
||||
word-wrap: anywhere;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
|
||||
@@ -20,11 +20,10 @@
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
import calendar
|
||||
from datetime import date, datetime, time, timedelta
|
||||
from datetime import date, datetime, time, timedelta, timezone
|
||||
from itertools import groupby
|
||||
from typing import Optional, Tuple
|
||||
|
||||
import pytz
|
||||
from django import forms
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.formats import date_format
|
||||
@@ -392,7 +391,7 @@ class SerializerDateFrameField(serializers.CharField):
|
||||
if data is None:
|
||||
return None
|
||||
try:
|
||||
resolve_timeframe_to_dates_inclusive(now(), data, pytz.UTC)
|
||||
resolve_timeframe_to_dates_inclusive(now(), data, timezone.utc)
|
||||
except:
|
||||
raise ValidationError("Invalid date frame")
|
||||
|
||||
|
||||
@@ -21,9 +21,9 @@
|
||||
#
|
||||
import logging
|
||||
from importlib import import_module
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import celery.exceptions
|
||||
import pytz
|
||||
from celery import states
|
||||
from celery.result import AsyncResult
|
||||
from django.conf import settings
|
||||
@@ -252,7 +252,7 @@ class AsyncFormView(AsyncMixin, FormView):
|
||||
task_self = self
|
||||
view_instance._task_self = task_self
|
||||
|
||||
with translation.override(locale), timezone.override(pytz.timezone(tz)):
|
||||
with translation.override(locale), timezone.override(ZoneInfo(tz)):
|
||||
form_class = view_instance.get_form_class()
|
||||
if form_kwargs.get('instance'):
|
||||
form_kwargs['instance'] = cls.model.objects.get(pk=form_kwargs['instance'])
|
||||
@@ -302,7 +302,7 @@ class AsyncFormView(AsyncMixin, FormView):
|
||||
'url_args': self.args,
|
||||
'url_kwargs': self.kwargs,
|
||||
'locale': get_language(),
|
||||
'tz': get_current_timezone().zone,
|
||||
'tz': str(get_current_timezone()),
|
||||
}
|
||||
if hasattr(self.request, 'organizer'):
|
||||
kwargs['organizer'] = self.request.organizer.pk
|
||||
@@ -377,7 +377,7 @@ class AsyncPostView(AsyncMixin, View):
|
||||
task_self = self
|
||||
view_instance._task_self = task_self
|
||||
|
||||
with translation.override(locale), timezone.override(pytz.timezone(tz)):
|
||||
with translation.override(locale), timezone.override(ZoneInfo(tz)):
|
||||
return view_instance.async_post(view_instance.request, *url_args, **url_kwargs)
|
||||
|
||||
cls.async_execute = app.task(
|
||||
@@ -405,7 +405,7 @@ class AsyncPostView(AsyncMixin, View):
|
||||
'locale': get_language(),
|
||||
'url_args': args,
|
||||
'url_kwargs': kwargs,
|
||||
'tz': get_current_timezone().zone,
|
||||
'tz': str(get_current_timezone()),
|
||||
}
|
||||
if hasattr(self.request, 'organizer'):
|
||||
kwargs['organizer'] = self.request.organizer.pk
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
|
||||
from decimal import Decimal
|
||||
from urllib.parse import urlencode, urlparse
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
@@ -55,7 +56,7 @@ from django_countries.fields import LazyTypedChoiceField
|
||||
from i18nfield.forms import (
|
||||
I18nForm, I18nFormField, I18nFormSetMixin, I18nTextarea, I18nTextInput,
|
||||
)
|
||||
from pytz import common_timezones, timezone
|
||||
from pytz import common_timezones
|
||||
|
||||
from pretix.base.channels import get_all_sales_channels
|
||||
from pretix.base.email import get_available_placeholders
|
||||
@@ -221,7 +222,7 @@ class EventWizardBasicsForm(I18nModelForm):
|
||||
})
|
||||
|
||||
# change timezone
|
||||
zone = timezone(data.get('timezone'))
|
||||
zone = ZoneInfo(data.get('timezone'))
|
||||
data['date_from'] = self.reset_timezone(zone, data.get('date_from'))
|
||||
data['date_to'] = self.reset_timezone(zone, data.get('date_to'))
|
||||
data['presale_start'] = self.reset_timezone(zone, data.get('presale_start'))
|
||||
@@ -230,7 +231,7 @@ class EventWizardBasicsForm(I18nModelForm):
|
||||
|
||||
@staticmethod
|
||||
def reset_timezone(tz, dt):
|
||||
return tz.localize(dt.replace(tzinfo=None)) if dt is not None else None
|
||||
return dt.replace(tzinfo=tz) if dt is not None else None
|
||||
|
||||
def clean_slug(self):
|
||||
slug = self.cleaned_data['slug']
|
||||
|
||||
@@ -747,7 +747,7 @@ class ItemVariationsFormSet(I18nFormSet):
|
||||
|
||||
def _should_delete_form(self, form):
|
||||
should_delete = super()._should_delete_form(form)
|
||||
if should_delete and (form.instance.orderposition_set.exists() or form.instance.cartposition_set.exists()):
|
||||
if should_delete and form.instance.pk and (form.instance.orderposition_set.exists() or form.instance.cartposition_set.exists()):
|
||||
form._delete_fail = True
|
||||
return False
|
||||
return form.cleaned_data.get(DELETION_FIELD_NAME, False)
|
||||
|
||||
@@ -602,7 +602,7 @@ class WebHookForm(forms.ModelForm):
|
||||
mark_safe('{} – <code>{}</code>'.format(a.verbose_name, a.action_type))
|
||||
) for a in get_all_webhook_events().values()
|
||||
]
|
||||
if self.instance:
|
||||
if self.instance and self.instance.pk:
|
||||
self.fields['events'].initial = list(self.instance.listeners.values_list('action_type', flat=True))
|
||||
|
||||
class Meta:
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
from bootstrap3.renderers import FieldRenderer, InlineFieldRenderer
|
||||
from bootstrap3.text import text_value
|
||||
from django.forms import CheckboxInput
|
||||
from django.forms.utils import flatatt
|
||||
@@ -28,6 +27,8 @@ from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import pgettext
|
||||
from i18nfield.forms import I18nFormField
|
||||
|
||||
from pretix.base.forms.renderers import FieldRenderer, InlineFieldRenderer
|
||||
|
||||
|
||||
def render_label(content, label_for=None, label_class=None, label_title='', optional=False):
|
||||
"""
|
||||
|
||||
@@ -39,7 +39,6 @@ from decimal import Decimal
|
||||
|
||||
import bleach
|
||||
import dateutil.parser
|
||||
import pytz
|
||||
from django.dispatch import receiver
|
||||
from django.urls import reverse
|
||||
from django.utils.formats import date_format
|
||||
@@ -209,7 +208,7 @@ def _display_checkin(event, logentry):
|
||||
if 'datetime' in data:
|
||||
dt = dateutil.parser.parse(data.get('datetime'))
|
||||
show_dt = abs((logentry.datetime - dt).total_seconds()) > 5 or 'forced' in data
|
||||
tz = pytz.timezone(event.settings.timezone)
|
||||
tz = event.timezone
|
||||
dt_formatted = date_format(dt.astimezone(tz), "SHORT_DATETIME_FORMAT")
|
||||
|
||||
if 'list' in data:
|
||||
@@ -627,7 +626,7 @@ def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
|
||||
if logentry.action_type == 'pretix.control.views.checkin':
|
||||
# deprecated
|
||||
dt = dateutil.parser.parse(data.get('datetime'))
|
||||
tz = pytz.timezone(sender.settings.timezone)
|
||||
tz = sender.timezone
|
||||
dt_formatted = date_format(dt.astimezone(tz), "SHORT_DATETIME_FORMAT")
|
||||
if 'list' in data:
|
||||
try:
|
||||
|
||||
@@ -423,17 +423,6 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if "mysql" in settings.DATABASES.default.ENGINE and not request.organizer %}
|
||||
<div class="alert alert-warning">
|
||||
{% blocktrans trimmed %}
|
||||
You are using MySQL or MariaDB as your database backend for pretix.
|
||||
Starting in pretix 5.0, these will no longer be supported and you will need to migrate to PostgreSQL.
|
||||
Please see the pretix administrator documentation for a migration guide, and the pretix 4.16
|
||||
release notes for more information.
|
||||
{% endblocktrans %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if debug_warning %}
|
||||
<div class="alert alert-danger">
|
||||
{% trans "pretix is running in debug mode. For security reasons, please never run debug mode on a production instance." %}
|
||||
|
||||
@@ -33,8 +33,7 @@
|
||||
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations under the License.
|
||||
|
||||
from django.conf.urls import re_path
|
||||
from django.urls import include
|
||||
from django.urls import include, re_path
|
||||
from django.views.generic.base import RedirectView
|
||||
|
||||
from pretix.control.views import (
|
||||
|
||||
@@ -19,18 +19,7 @@
|
||||
# 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/>.
|
||||
#
|
||||
|
||||
# This file is based on an earlier version of pretix which was released under the Apache License 2.0. The full text of
|
||||
# the Apache License 2.0 can be obtained at <http://www.apache.org/licenses/LICENSE-2.0>.
|
||||
#
|
||||
# This file may have since been changed and any changes are released under the terms of AGPLv3 as described above. A
|
||||
# full history of changes and contributors is available at <https://github.com/pretix/pretix>.
|
||||
#
|
||||
# This file contains Apache-licensed contributions copyrighted by: Jakob Schnell, jasonwaiting@live.hk, pajowu
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software distributed under the Apache License 2.0 is
|
||||
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations under the License.
|
||||
from datetime import timezone
|
||||
|
||||
import dateutil.parser
|
||||
from django.contrib import messages
|
||||
@@ -44,7 +33,6 @@ from django.utils.functional import cached_property
|
||||
from django.utils.timezone import is_aware, make_aware, now
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views.generic import ListView
|
||||
from pytz import UTC
|
||||
|
||||
from pretix.base.channels import get_all_sales_channels
|
||||
from pretix.base.models import Checkin, Order, OrderPosition
|
||||
@@ -60,6 +48,18 @@ from pretix.control.views import CreateView, PaginationMixin, UpdateView
|
||||
from pretix.helpers.compat import CompatDeleteView
|
||||
from pretix.helpers.models import modelcopy
|
||||
|
||||
# This file is based on an earlier version of pretix which was released under the Apache License 2.0. The full text of
|
||||
# the Apache License 2.0 can be obtained at <http://www.apache.org/licenses/LICENSE-2.0>.
|
||||
#
|
||||
# This file may have since been changed and any changes are released under the terms of AGPLv3 as described above. A
|
||||
# full history of changes and contributors is available at <https://github.com/pretix/pretix>.
|
||||
#
|
||||
# This file contains Apache-licensed contributions copyrighted by: Jakob Schnell, jasonwaiting@live.hk, pajowu
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software distributed under the Apache License 2.0 is
|
||||
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations under the License.
|
||||
|
||||
|
||||
class CheckInListQueryMixin:
|
||||
|
||||
@@ -163,20 +163,20 @@ class CheckInListShow(EventPermissionRequiredMixin, PaginationMixin, CheckInList
|
||||
if e.last_entry:
|
||||
if isinstance(e.last_entry, str):
|
||||
# Apparently only happens on SQLite
|
||||
e.last_entry_aware = make_aware(dateutil.parser.parse(e.last_entry), UTC)
|
||||
e.last_entry_aware = make_aware(dateutil.parser.parse(e.last_entry), timezone.utc)
|
||||
elif not is_aware(e.last_entry):
|
||||
# Apparently only happens on MySQL
|
||||
e.last_entry_aware = make_aware(e.last_entry, UTC)
|
||||
e.last_entry_aware = make_aware(e.last_entry, timezone.utc)
|
||||
else:
|
||||
# This would be correct, so guess on which database it works… Yes, it's PostgreSQL.
|
||||
e.last_entry_aware = e.last_entry
|
||||
if e.last_exit:
|
||||
if isinstance(e.last_exit, str):
|
||||
# Apparently only happens on SQLite
|
||||
e.last_exit_aware = make_aware(dateutil.parser.parse(e.last_exit), UTC)
|
||||
e.last_exit_aware = make_aware(dateutil.parser.parse(e.last_exit), timezone.utc)
|
||||
elif not is_aware(e.last_exit):
|
||||
# Apparently only happens on MySQL
|
||||
e.last_exit_aware = make_aware(e.last_exit, UTC)
|
||||
e.last_exit_aware = make_aware(e.last_exit, timezone.utc)
|
||||
else:
|
||||
# This would be correct, so guess on which database it works… Yes, it's PostgreSQL.
|
||||
e.last_exit_aware = e.last_exit
|
||||
|
||||
@@ -34,8 +34,8 @@
|
||||
|
||||
from datetime import timedelta
|
||||
from decimal import Decimal
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import pytz
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db.models import (
|
||||
@@ -510,7 +510,7 @@ def widgets_for_event_qs(request, qs, user, nmax, lazy=False):
|
||||
for event in events:
|
||||
if not lazy:
|
||||
tzname = event.cache.get_or_set('timezone', lambda: event.settings.timezone)
|
||||
tz = pytz.timezone(tzname)
|
||||
tz = ZoneInfo(tzname)
|
||||
if event.has_subevents:
|
||||
if event.min_from is None:
|
||||
dr = pgettext("subevent", "No dates")
|
||||
|
||||
@@ -41,6 +41,7 @@ from decimal import Decimal
|
||||
from io import BytesIO
|
||||
from itertools import groupby
|
||||
from urllib.parse import urlsplit
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import bleach
|
||||
import qrcode
|
||||
@@ -67,7 +68,6 @@ from django.views.generic.base import TemplateView, View
|
||||
from django.views.generic.detail import SingleObjectMixin
|
||||
from i18nfield.strings import LazyI18nString
|
||||
from i18nfield.utils import I18nJSONEncoder
|
||||
from pytz import timezone
|
||||
|
||||
from pretix.base.channels import get_all_sales_channels
|
||||
from pretix.base.email import get_available_placeholders
|
||||
@@ -253,7 +253,7 @@ class EventUpdate(DecoupleMixin, EventSettingsViewMixin, EventPermissionRequired
|
||||
self.item_meta_property_formset.is_valid() and self.confirm_texts_formset.is_valid() and \
|
||||
self.footer_links_formset.is_valid():
|
||||
# reset timezone
|
||||
zone = timezone(self.sform.cleaned_data['timezone'])
|
||||
zone = ZoneInfo(self.sform.cleaned_data['timezone'])
|
||||
event = form.instance
|
||||
event.date_from = self.reset_timezone(zone, event.date_from)
|
||||
event.date_to = self.reset_timezone(zone, event.date_to)
|
||||
@@ -266,7 +266,7 @@ class EventUpdate(DecoupleMixin, EventSettingsViewMixin, EventPermissionRequired
|
||||
|
||||
@staticmethod
|
||||
def reset_timezone(tz, dt):
|
||||
return tz.localize(dt.replace(tzinfo=None)) if dt is not None else None
|
||||
return dt.replace(tzinfo=tz) if dt is not None else None
|
||||
|
||||
@cached_property
|
||||
def item_meta_property_formset(self):
|
||||
|
||||
@@ -60,8 +60,7 @@ from django.utils.timezone import get_current_timezone, now
|
||||
from django.utils.translation import gettext, gettext_lazy as _
|
||||
from django.views import View
|
||||
from django.views.generic import (
|
||||
CreateView, DeleteView, DetailView, FormView, ListView, TemplateView,
|
||||
UpdateView,
|
||||
CreateView, DetailView, FormView, ListView, TemplateView, UpdateView,
|
||||
)
|
||||
|
||||
from pretix.api.models import ApiCall, WebHook
|
||||
@@ -1775,7 +1774,7 @@ class ExportView(OrganizerPermissionRequiredMixin, ExportMixin, ListView):
|
||||
instance = self.scheduled or ScheduledOrganizerExport(
|
||||
organizer=self.request.organizer,
|
||||
owner=self.request.user,
|
||||
timezone=get_current_timezone().zone,
|
||||
timezone=str(get_current_timezone()),
|
||||
)
|
||||
if not self.scheduled:
|
||||
initial = {
|
||||
@@ -1811,7 +1810,7 @@ class ExportView(OrganizerPermissionRequiredMixin, ExportMixin, ListView):
|
||||
return ctx
|
||||
|
||||
|
||||
class DeleteScheduledExportView(OrganizerPermissionRequiredMixin, ExportMixin, DeleteView):
|
||||
class DeleteScheduledExportView(OrganizerPermissionRequiredMixin, ExportMixin, CompatDeleteView):
|
||||
template_name = 'pretixcontrol/organizers/export_delete.html'
|
||||
context_object_name = 'export'
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ class OrderSearch(PaginationMixin, ListView):
|
||||
|
||||
if self.filter_form.use_query_hack():
|
||||
"""
|
||||
We need to work around a bug in PostgreSQL's (and likely MySQL's) query plan optimizer here.
|
||||
We need to work around a bug in PostgreSQL's query plan optimizer here.
|
||||
The database lacks statistical data to predict how common our search filter is and therefore
|
||||
assumes that it is cheaper to first ORDER *all* orders in the system (since we got an index on
|
||||
datetime), then filter out with a full scan until OFFSET/LIMIT condition is fulfilled. If we
|
||||
@@ -167,7 +167,7 @@ class PaymentSearch(PaginationMixin, ListView):
|
||||
|
||||
if self.filter_form.cleaned_data.get('query'):
|
||||
"""
|
||||
We need to work around a bug in PostgreSQL's (and likely MySQL's) query plan optimizer here.
|
||||
We need to work around a bug in PostgreSQL's query plan optimizer here.
|
||||
The database lacks statistical data to predict how common our search filter is and therefore
|
||||
assumes that it is cheaper to first ORDER *all* orders in the system (since we got an index on
|
||||
datetime), then filter out with a full scan until OFFSET/LIMIT condition is fulfilled. If we
|
||||
|
||||
@@ -260,7 +260,7 @@ class SubEventEditorMixin(MetaDataEditorMixin):
|
||||
form=SimpleCheckinListForm, formset=CheckinListFormSet,
|
||||
can_order=False, can_delete=True, extra=extra,
|
||||
)
|
||||
if self.object:
|
||||
if self.object and self.object.pk:
|
||||
kwargs['queryset'] = self.object.checkinlist_set.prefetch_related('limit_products')
|
||||
|
||||
return formsetclass(self.request.POST if self.request.method == "POST" else None,
|
||||
@@ -297,7 +297,7 @@ class SubEventEditorMixin(MetaDataEditorMixin):
|
||||
form=QuotaForm, formset=QuotaFormSet, min_num=1, validate_min=True,
|
||||
can_order=False, can_delete=True, extra=extra,
|
||||
)
|
||||
if self.object:
|
||||
if self.object and self.object.pk:
|
||||
kwargs['queryset'] = self.object.quotas.prefetch_related('items', 'variations')
|
||||
|
||||
return formsetclass(
|
||||
@@ -400,10 +400,10 @@ class SubEventEditorMixin(MetaDataEditorMixin):
|
||||
def itemvar_forms(self):
|
||||
se_item_instances = {
|
||||
sei.item_id: sei for sei in SubEventItem.objects.filter(subevent=self.object)
|
||||
}
|
||||
} if self.object and self.object.pk else {}
|
||||
se_var_instances = {
|
||||
sei.variation_id: sei for sei in SubEventItemVariation.objects.filter(subevent=self.object)
|
||||
}
|
||||
} if self.object and self.object.pk else {}
|
||||
|
||||
if self.copy_from:
|
||||
se_item_instances = {
|
||||
@@ -821,18 +821,18 @@ class SubEventBulkCreate(SubEventEditorMixin, EventPermissionRequiredMixin, Asyn
|
||||
for t in self.get_times():
|
||||
se = copy.copy(form.instance)
|
||||
|
||||
se.date_from = make_aware(datetime.combine(rdate, t['time_from']), tz, is_dst=False)
|
||||
se.date_from = make_aware(datetime.combine(rdate, t['time_from'].replace(fold=1)), tz)
|
||||
|
||||
if t.get('time_to'):
|
||||
se.date_to = (
|
||||
make_aware(datetime.combine(rdate, t['time_to']), tz, is_dst=False)
|
||||
make_aware(datetime.combine(rdate, t['time_to'].replace(fold=1)), tz)
|
||||
if t.get('time_to') > t.get('time_from')
|
||||
else make_aware(datetime.combine(rdate + timedelta(days=1), t['time_to']), tz, is_dst=False)
|
||||
else make_aware(datetime.combine(rdate + timedelta(days=1), t['time_to'].replace(fold=1)), tz)
|
||||
)
|
||||
else:
|
||||
se.date_to = None
|
||||
se.date_admission = (
|
||||
make_aware(datetime.combine(rdate, t['time_admission'].replace(fold=1)), tz, is_dst=False)
|
||||
make_aware(datetime.combine(rdate, t['time_admission'].replace(fold=1)), tz)
|
||||
if t.get('time_admission')
|
||||
else None
|
||||
)
|
||||
@@ -1148,6 +1148,7 @@ class SubEventBulkEdit(SubEventQueryMixin, EventPermissionRequiredMixin, FormVie
|
||||
subevents = list(self.get_queryset().prefetch_related('checkinlist_set'))
|
||||
to_save_products = []
|
||||
to_save_gates = []
|
||||
to_delete_list_ids = []
|
||||
|
||||
for f in self.list_formset.forms:
|
||||
if self.list_formset._should_delete_form(f) and f in self.list_formset.extra_forms:
|
||||
@@ -1159,7 +1160,7 @@ class SubEventBulkEdit(SubEventQueryMixin, EventPermissionRequiredMixin, FormVie
|
||||
log_entries += [
|
||||
q.log_action(action='pretix.event.checkinlist.deleted', user=self.request.user, save=False),
|
||||
]
|
||||
q.delete()
|
||||
to_delete_list_ids.append(q.pk)
|
||||
elif f in self.list_formset.extra_forms:
|
||||
change_data = {k: f.cleaned_data.get(k) for k in f.changed_data}
|
||||
for se in subevents:
|
||||
@@ -1198,6 +1199,8 @@ class SubEventBulkEdit(SubEventQueryMixin, EventPermissionRequiredMixin, FormVie
|
||||
CheckinList.limit_products.through.objects.bulk_create(to_save_products)
|
||||
if to_save_gates:
|
||||
CheckinList.gates.through.objects.bulk_create(to_save_gates)
|
||||
if to_delete_list_ids:
|
||||
CheckinList.objects.filter(id__in=to_delete_list_ids).delete()
|
||||
|
||||
def save_quota_formset(self, log_entries):
|
||||
if not self.quota_formset.has_changed():
|
||||
@@ -1224,6 +1227,7 @@ class SubEventBulkEdit(SubEventQueryMixin, EventPermissionRequiredMixin, FormVie
|
||||
|
||||
if to_delete_quota_ids:
|
||||
Quota.objects.filter(id__in=to_delete_quota_ids).delete()
|
||||
to_delete_quota_ids = []
|
||||
|
||||
for f in self.quota_formset.forms:
|
||||
if self.quota_formset._should_delete_form(f) and f in self.quota_formset.extra_forms:
|
||||
@@ -1245,7 +1249,7 @@ class SubEventBulkEdit(SubEventQueryMixin, EventPermissionRequiredMixin, FormVie
|
||||
'id': q.pk
|
||||
}, save=False)
|
||||
]
|
||||
q.delete()
|
||||
to_delete_quota_ids.append(q.pk)
|
||||
elif f in self.quota_formset.extra_forms:
|
||||
change_data = {k: f.cleaned_data.get(k) for k in f.changed_data}
|
||||
for se in subevents:
|
||||
@@ -1288,6 +1292,8 @@ class SubEventBulkEdit(SubEventQueryMixin, EventPermissionRequiredMixin, FormVie
|
||||
Quota.items.through.objects.bulk_create(to_save_items)
|
||||
if to_save_variations:
|
||||
Quota.variations.through.objects.bulk_create(to_save_variations)
|
||||
if to_delete_quota_ids:
|
||||
Quota.objects.filter(id__in=to_delete_quota_ids).delete()
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
|
||||
@@ -33,8 +33,8 @@
|
||||
# License for the specific language governing permissions and limitations under the License.
|
||||
|
||||
from datetime import datetime, time
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import pytz
|
||||
from dateutil.parser import parse
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.db.models import F, Max, Min, Q
|
||||
@@ -88,7 +88,7 @@ def serialize_event(e):
|
||||
if e.min_from is None:
|
||||
dr = pgettext('subevent', 'No dates')
|
||||
else:
|
||||
tz = pytz.timezone(e.settings.timezone)
|
||||
tz = ZoneInfo(e.settings.timezone)
|
||||
dr = _('Series:') + ' ' + daterange(
|
||||
e.min_from.astimezone(tz),
|
||||
(e.max_fromto or e.max_to or e.max_from).astimezone(tz)
|
||||
|
||||
@@ -29,7 +29,7 @@ def postgres_compile_json_path(key_transforms):
|
||||
return "{" + ','.join(key_transforms) + "}"
|
||||
|
||||
|
||||
def mysql_compile_json_path(key_transforms):
|
||||
def sqlite_compile_json_path(key_transforms):
|
||||
path = ['$']
|
||||
for key_transform in key_transforms:
|
||||
try:
|
||||
@@ -41,9 +41,6 @@ def mysql_compile_json_path(key_transforms):
|
||||
return ''.join(path)
|
||||
|
||||
|
||||
sqlite_compile_json_path = mysql_compile_json_path
|
||||
|
||||
|
||||
class JSONExtract(Expression):
|
||||
def __init__(self, expression, *path, output_field=JSONField(), **extra):
|
||||
super().__init__(output_field=output_field)
|
||||
@@ -66,14 +63,6 @@ class JSONExtract(Expression):
|
||||
params.append(json_path)
|
||||
template = '{} #> %s'.format(arg_sql)
|
||||
return template, params
|
||||
elif '.mysql' in connection.settings_dict['ENGINE']:
|
||||
params = []
|
||||
arg_sql, arg_params = compiler.compile(self.source_expression)
|
||||
params.extend(arg_params)
|
||||
json_path = mysql_compile_json_path(self.path)
|
||||
params.append(json_path)
|
||||
template = 'JSON_EXTRACT({}, %s)'.format(arg_sql)
|
||||
return template, params
|
||||
elif '.sqlite' in connection.settings_dict['ENGINE']:
|
||||
params = []
|
||||
arg_sql, arg_params = compiler.compile(self.source_expression)
|
||||
@@ -84,7 +73,7 @@ class JSONExtract(Expression):
|
||||
return template, params
|
||||
else:
|
||||
raise NotSupportedError(
|
||||
'Functions on JSONFields are only supported on SQLite, PostgreSQL, and MySQL at the moment.'
|
||||
'Functions on JSONFields are only supported on SQLite and PostgreSQL at the moment.'
|
||||
)
|
||||
|
||||
def copy(self):
|
||||
|
||||
@@ -5,7 +5,7 @@ msgstr ""
|
||||
"Project-Id-Version: 1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-05-31 09:01+0000\n"
|
||||
"PO-Revision-Date: 2023-05-31 10:41+0000\n"
|
||||
"PO-Revision-Date: 2023-06-05 16:00+0000\n"
|
||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||
"Language-Team: German <https://translate.pretix.eu/projects/pretix/pretix/de/"
|
||||
">\n"
|
||||
@@ -24748,8 +24748,8 @@ msgid ""
|
||||
"The order has been canceled. You can now select how you want to transfer the "
|
||||
"money back to the user."
|
||||
msgstr ""
|
||||
"Die Bestellung wurde als erstattet markiert. Sie können nun auswählen, wie "
|
||||
"Sie das Geld zurückerstatten möchten."
|
||||
"Die Bestellung wurde storniert. Sie können nun auswählen, wie Sie das Geld "
|
||||
"zurückerstatten möchten."
|
||||
|
||||
#: pretix/control/views/orders.py:1362 pretix/control/views/orders.py:1366
|
||||
msgid "No VAT ID specified."
|
||||
@@ -30492,8 +30492,8 @@ msgstr ""
|
||||
#: pretix/presale/templates/pretixpresale/event/order.html:43
|
||||
msgid "Please note that we still await your payment to complete the process."
|
||||
msgstr ""
|
||||
"Bitte beachten Sie, dass die Bestellung erst mit Eingang der Zahlung "
|
||||
"vollständig abgeschlossen ist."
|
||||
"Bitte beachten Sie, dass wir noch Ihre Zahlung erwarten, um den Prozess "
|
||||
"abzuschließen."
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/order.html:55
|
||||
msgid ""
|
||||
|
||||
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Project-Id-Version: 1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-05-31 09:01+0000\n"
|
||||
"PO-Revision-Date: 2023-05-31 10:41+0000\n"
|
||||
"PO-Revision-Date: 2023-06-05 16:00+0000\n"
|
||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||
"Language-Team: German (informal) <https://translate.pretix.eu/projects/"
|
||||
"pretix/pretix/de_Informal/>\n"
|
||||
@@ -24704,8 +24704,8 @@ msgid ""
|
||||
"The order has been canceled. You can now select how you want to transfer the "
|
||||
"money back to the user."
|
||||
msgstr ""
|
||||
"Die Bestellung wurde als erstattet markiert. Du kannst nun auswählen, wie du "
|
||||
"das Geld zurückerstatten möchtest."
|
||||
"Die Bestellung wurde storniert. Du kannst nun auswählen, wie du das Geld "
|
||||
"zurückerstatten möchtest."
|
||||
|
||||
#: pretix/control/views/orders.py:1362 pretix/control/views/orders.py:1366
|
||||
msgid "No VAT ID specified."
|
||||
@@ -30433,8 +30433,8 @@ msgstr ""
|
||||
#: pretix/presale/templates/pretixpresale/event/order.html:43
|
||||
msgid "Please note that we still await your payment to complete the process."
|
||||
msgstr ""
|
||||
"Bitte beachte, dass der Bestellvorgang erst mit Eingang deiner Zahlung "
|
||||
"abgeschlossen ist."
|
||||
"Bitte beachten Sie, dass wir noch deine Zahlung erwarten, um den Prozess "
|
||||
"abzuschließen."
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/order.html:55
|
||||
msgid ""
|
||||
|
||||
@@ -8,8 +8,8 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-05-31 09:01+0000\n"
|
||||
"PO-Revision-Date: 2023-06-02 17:12+0000\n"
|
||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||
"PO-Revision-Date: 2023-06-04 16:06+0000\n"
|
||||
"Last-Translator: Yucheng Lin <yuchenglinedu@gmail.com>\n"
|
||||
"Language-Team: Chinese (Traditional) <https://translate.pretix.eu/projects/"
|
||||
"pretix/pretix/zh_Hant/>\n"
|
||||
"Language: zh_Hant\n"
|
||||
@@ -10048,7 +10048,7 @@ msgstr "發票位址"
|
||||
msgid ""
|
||||
"This will remove all invoice addresses from orders, as well as logged "
|
||||
"changes to them."
|
||||
msgstr ""
|
||||
msgstr "這將從訂單中刪除所有發票位址,以及記錄的更改。"
|
||||
|
||||
#: pretix/base/shredder.py:362
|
||||
msgid "Question answers"
|
||||
@@ -10070,18 +10070,19 @@ msgstr ""
|
||||
|
||||
#: pretix/base/shredder.py:426
|
||||
msgid "Cached ticket files"
|
||||
msgstr ""
|
||||
msgstr "票證的緩存檔"
|
||||
|
||||
#: pretix/base/shredder.py:428
|
||||
msgid "This will remove all cached ticket files. No download will be offered."
|
||||
msgstr ""
|
||||
msgstr "這將刪除所有票證的緩存檔。不提供下載。"
|
||||
|
||||
#: pretix/base/shredder.py:443
|
||||
msgid ""
|
||||
"This will remove payment-related information. Depending on the payment "
|
||||
"method, all data will be removed or personal data only. No download will be "
|
||||
"offered."
|
||||
msgstr ""
|
||||
msgstr "這將刪除與付款相關的資訊。根據付款方式的不同,所有數據或僅個人數據都將被刪除"
|
||||
"。不提供下載。"
|
||||
|
||||
#: pretix/base/templates/400.html:4 pretix/base/templates/400.html:8
|
||||
msgid "Bad Request"
|
||||
|
||||
@@ -22,8 +22,7 @@
|
||||
import importlib.util
|
||||
|
||||
from django.apps import apps
|
||||
from django.conf.urls import re_path
|
||||
from django.urls import include
|
||||
from django.urls import include, re_path
|
||||
|
||||
from pretix.multidomain.plugin_handler import plugin_event_urls
|
||||
from pretix.presale.urls import event_patterns, locale_patterns
|
||||
|
||||
@@ -35,8 +35,7 @@
|
||||
import importlib.util
|
||||
|
||||
from django.apps import apps
|
||||
from django.conf.urls import re_path
|
||||
from django.urls import include
|
||||
from django.urls import include, re_path
|
||||
from django.views.generic import TemplateView
|
||||
|
||||
from pretix.multidomain.plugin_handler import plugin_event_urls
|
||||
|
||||
@@ -42,7 +42,9 @@ from django.contrib.sessions.middleware import (
|
||||
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.middleware.csrf import (
|
||||
CSRF_SESSION_KEY, CsrfViewMiddleware as BaseCsrfMiddleware,
|
||||
)
|
||||
from django.shortcuts import render
|
||||
from django.urls import set_urlconf
|
||||
from django.utils.cache import patch_vary_headers
|
||||
@@ -189,35 +191,25 @@ class CsrfViewMiddleware(BaseCsrfMiddleware):
|
||||
a custom domain.
|
||||
"""
|
||||
|
||||
def process_response(self, request, response):
|
||||
if getattr(response, 'csrf_processing_done', False):
|
||||
return response
|
||||
|
||||
# If CSRF_COOKIE is unset, then CsrfViewMiddleware.process_view was
|
||||
# never called, probably because a request middleware returned a response
|
||||
# (for example, contrib.auth redirecting to a login page).
|
||||
if request.META.get("CSRF_COOKIE") is None:
|
||||
return response
|
||||
|
||||
if not request.META.get("CSRF_COOKIE_USED", False):
|
||||
return response
|
||||
|
||||
# Set the CSRF cookie even if it's already set, so we renew
|
||||
# the expiry timer.
|
||||
set_cookie_without_samesite(
|
||||
request, response,
|
||||
settings.CSRF_COOKIE_NAME,
|
||||
request.META["CSRF_COOKIE"],
|
||||
max_age=settings.CSRF_COOKIE_AGE,
|
||||
domain=get_cookie_domain(request),
|
||||
path=settings.CSRF_COOKIE_PATH,
|
||||
secure=request.scheme == 'https',
|
||||
httponly=settings.CSRF_COOKIE_HTTPONLY
|
||||
)
|
||||
# Content varies with the CSRF cookie, so set the Vary header.
|
||||
patch_vary_headers(response, ('Cookie',))
|
||||
response.csrf_processing_done = True
|
||||
return response
|
||||
def _set_csrf_cookie(self, request, response):
|
||||
if settings.CSRF_USE_SESSIONS:
|
||||
if request.session.get(CSRF_SESSION_KEY) != request.META["CSRF_COOKIE"]:
|
||||
request.session[CSRF_SESSION_KEY] = request.META["CSRF_COOKIE"]
|
||||
else:
|
||||
# Set the CSRF cookie even if it's already set, so we renew
|
||||
# the expiry timer.
|
||||
set_cookie_without_samesite(
|
||||
request, response,
|
||||
settings.CSRF_COOKIE_NAME,
|
||||
request.META["CSRF_COOKIE"],
|
||||
max_age=settings.CSRF_COOKIE_AGE,
|
||||
domain=get_cookie_domain(request),
|
||||
path=settings.CSRF_COOKIE_PATH,
|
||||
secure=request.scheme == 'https',
|
||||
httponly=settings.CSRF_COOKIE_HTTPONLY
|
||||
)
|
||||
# Content varies with the CSRF cookie, so set the Vary header.
|
||||
patch_vary_headers(response, ('Cookie',))
|
||||
|
||||
|
||||
def get_cookie_domain(request):
|
||||
|
||||
@@ -22,8 +22,7 @@
|
||||
import importlib.util
|
||||
|
||||
from django.apps import apps
|
||||
from django.conf.urls import re_path
|
||||
from django.urls import include
|
||||
from django.urls import include, re_path
|
||||
|
||||
from pretix.multidomain.plugin_handler import plugin_event_urls
|
||||
from pretix.presale.urls import (
|
||||
|
||||
@@ -45,6 +45,9 @@ from .models import KnownDomain
|
||||
|
||||
def get_event_domain(event, fallback=False, return_info=False):
|
||||
assert isinstance(event, Event)
|
||||
if not event.pk:
|
||||
# Can happen on the "event deleted" response
|
||||
return (None, None) if return_info else None
|
||||
suffix = ('_fallback' if fallback else '') + ('_info' if return_info else '')
|
||||
domain = getattr(event, '_cached_domain' + suffix, None) or event.cache.get('domain' + suffix)
|
||||
if domain is None:
|
||||
|
||||
@@ -51,7 +51,7 @@ from django.db.models import Case, Exists, OuterRef, Q, Subquery, When
|
||||
from django.db.models.functions import Cast, Coalesce
|
||||
from django.utils.timezone import make_aware
|
||||
from django.utils.translation import gettext as _, gettext_lazy, pgettext_lazy
|
||||
from pypdf import PdfMerger, PdfReader, PdfWriter, Transformation
|
||||
from pypdf import PdfReader, PdfWriter, Transformation
|
||||
from pypdf.generic import RectangleObject
|
||||
from reportlab.lib import pagesizes
|
||||
from reportlab.lib.units import mm
|
||||
@@ -196,7 +196,7 @@ def render_pdf(event, positions, opt):
|
||||
raise ExportError(_("None of the selected products is configured to print badges."))
|
||||
|
||||
# render each badge on its own page first
|
||||
merger = PdfMerger()
|
||||
merger = PdfWriter()
|
||||
merger.add_metadata({
|
||||
'/Title': 'Badges',
|
||||
'/Creator': 'pretix',
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
# 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/>.
|
||||
#
|
||||
from django.conf.urls import re_path
|
||||
from django.urls import re_path
|
||||
|
||||
from pretix.api.urls import event_router
|
||||
from pretix.plugins.badges.api import BadgeItemViewSet, BadgeLayoutViewSet
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
# 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/>.
|
||||
#
|
||||
from django.conf.urls import re_path
|
||||
from django.urls import re_path
|
||||
|
||||
from pretix.api.urls import orga_router
|
||||
from pretix.plugins.banktransfer.api import BankImportJobViewSet
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
# License for the specific language governing permissions and limitations under the License.
|
||||
|
||||
from collections import OrderedDict
|
||||
from datetime import timezone
|
||||
|
||||
import bleach
|
||||
import dateutil.parser
|
||||
@@ -47,7 +48,6 @@ from django.utils.timezone import is_aware, make_aware, now
|
||||
from django.utils.translation import (
|
||||
gettext as _, gettext_lazy, pgettext, pgettext_lazy,
|
||||
)
|
||||
from pytz import UTC
|
||||
from reportlab.lib.units import mm
|
||||
from reportlab.platypus import Flowable, Paragraph, Spacer, Table, TableStyle
|
||||
|
||||
@@ -513,7 +513,7 @@ class CSVCheckinList(CheckInListMixin, ListExporter):
|
||||
elif op.last_checked_in:
|
||||
last_checked_in = op.last_checked_in
|
||||
if last_checked_in and not is_aware(last_checked_in):
|
||||
last_checked_in = make_aware(last_checked_in, UTC)
|
||||
last_checked_in = make_aware(last_checked_in, timezone.utc)
|
||||
|
||||
last_checked_out = None
|
||||
if isinstance(op.last_checked_out, str): # SQLite
|
||||
@@ -521,7 +521,7 @@ class CSVCheckinList(CheckInListMixin, ListExporter):
|
||||
elif op.last_checked_out:
|
||||
last_checked_out = op.last_checked_out
|
||||
if last_checked_out and not is_aware(last_checked_out):
|
||||
last_checked_out = make_aware(last_checked_out, UTC)
|
||||
last_checked_out = make_aware(last_checked_out, timezone.utc)
|
||||
|
||||
row = [
|
||||
op.order.code,
|
||||
|
||||
@@ -19,8 +19,7 @@
|
||||
# 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/>.
|
||||
#
|
||||
from django.conf.urls import re_path
|
||||
from django.urls import include
|
||||
from django.urls import include, re_path
|
||||
|
||||
from .views import abort, oauth_disconnect, redirect_view, success
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ from django.http import HttpRequest, HttpResponse
|
||||
from django.template.loader import get_template
|
||||
from django.urls import resolve
|
||||
from django.utils.crypto import get_random_string
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.utils.translation import gettext_lazy as _, pgettext_lazy
|
||||
|
||||
from pretix.base.forms import SecretKeySettingsField
|
||||
from pretix.base.middleware import _merge_csp, _parse_csp, _render_csp
|
||||
@@ -61,7 +61,10 @@ def pretixcontrol_logentry_display(sender, logentry, **kwargs):
|
||||
'PAYMENT.SALE.REFUNDED': _('Payment refunded.'),
|
||||
'PAYMENT.SALE.REVERSED': _('Payment reversed.'),
|
||||
'PAYMENT.SALE.PENDING': _('Payment pending.'),
|
||||
'CHECKOUT.ORDER.APPROVED': _('Order approved.'),
|
||||
'CHECKOUT.ORDER.APPROVED': pgettext_lazy('paypal', 'Order approved.'),
|
||||
'CHECKOUT.ORDER.COMPLETED': pgettext_lazy('paypal', 'Order completed.'),
|
||||
'PAYMENT.CAPTURE.COMPLETED': pgettext_lazy('paypal', 'Capture completed.'),
|
||||
'PAYMENT.CAPTURE.PENDING': pgettext_lazy('paypal', 'Capture pending.'),
|
||||
}
|
||||
|
||||
if event_type in plains:
|
||||
|
||||
@@ -19,8 +19,7 @@
|
||||
# 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/>.
|
||||
#
|
||||
from django.conf.urls import re_path
|
||||
from django.urls import include
|
||||
from django.urls import include, re_path
|
||||
|
||||
from .views import (
|
||||
PayView, XHRView, abort, isu_disconnect, isu_return, redirect_view,
|
||||
|
||||
@@ -37,7 +37,6 @@ import tempfile
|
||||
from collections import OrderedDict, defaultdict
|
||||
from decimal import Decimal
|
||||
|
||||
import pytz
|
||||
from dateutil.parser import parse
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
@@ -123,7 +122,7 @@ class ReportlabExportMixin:
|
||||
return 'report-%s.pdf' % self.event.slug, 'application/pdf', self.create(form_data)
|
||||
|
||||
def get_filename(self):
|
||||
tz = pytz.timezone(self.event.settings.timezone)
|
||||
tz = self.event.timezone
|
||||
return "%s-%s.pdf" % (self.name, now().astimezone(tz).strftime("%Y-%m-%d-%H-%M-%S"))
|
||||
|
||||
@staticmethod
|
||||
@@ -491,7 +490,7 @@ class OrderTaxListReportPDF(Report):
|
||||
headlinestyle = self.get_style()
|
||||
headlinestyle.fontSize = 15
|
||||
headlinestyle.fontName = 'OpenSansBd'
|
||||
tz = pytz.timezone(self.event.settings.timezone)
|
||||
tz = self.event.timezone
|
||||
|
||||
tax_rates = set(
|
||||
a for a
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
# 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/>.
|
||||
#
|
||||
from django.conf.urls import re_path
|
||||
from django.urls import re_path
|
||||
|
||||
from .views import ReturnSettings
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#
|
||||
from datetime import datetime, time, timedelta
|
||||
|
||||
from dateutil.tz import datetime_exists
|
||||
from django.db import models
|
||||
from django.db.models import Exists, OuterRef, Q
|
||||
from django.utils import timezone
|
||||
@@ -69,6 +70,9 @@ class ScheduledMail(models.Model):
|
||||
def save(self, **kwargs):
|
||||
if not self.computed_datetime:
|
||||
self.recompute()
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'computed_datetime', 'last_computed', 'state'}.union(kwargs['update_fields'])
|
||||
|
||||
super().save(**kwargs)
|
||||
|
||||
def recompute(self):
|
||||
@@ -88,10 +92,14 @@ class ScheduledMail(models.Model):
|
||||
base_time = (e.date_to or e.date_from) if self.rule.offset_to_event_end else e.date_from
|
||||
d = base_time.astimezone(self.event.timezone).date() + offset
|
||||
self.computed_datetime = make_aware(
|
||||
datetime.combine(d, time(hour=st.hour, minute=st.minute, second=st.second, microsecond=0)),
|
||||
datetime.combine(d, time(hour=st.hour, minute=st.minute, second=st.second, microsecond=0, fold=1)),
|
||||
self.event.timezone,
|
||||
is_dst=False, # prevent AmbiguousTimeError
|
||||
)
|
||||
if not datetime_exists(self.computed_datetime):
|
||||
self.computed_datetime = make_aware(
|
||||
datetime.combine(d, time(hour=st.hour, minute=st.minute, second=st.second, microsecond=0)) + timedelta(hours=1),
|
||||
self.event.timezone,
|
||||
)
|
||||
|
||||
if self.computed_datetime > timezone.now() and self.state == self.STATE_MISSED:
|
||||
self.state = self.STATE_SCHEDULED
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations under the License.
|
||||
|
||||
from django.conf.urls import re_path
|
||||
from django.urls import re_path
|
||||
|
||||
from pretix.api.urls import event_router
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
# 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/>.
|
||||
#
|
||||
from django.conf.urls import re_path
|
||||
from django.urls import re_path
|
||||
|
||||
from . import views
|
||||
|
||||
|
||||
@@ -19,8 +19,7 @@
|
||||
# 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/>.
|
||||
#
|
||||
from django.conf.urls import re_path
|
||||
from django.urls import include
|
||||
from django.urls import include, re_path
|
||||
|
||||
from pretix.multidomain import event_url
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ from django.db.models import Case, OuterRef, Q, Subquery, When
|
||||
from django.db.models.functions import Cast, Coalesce
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext as _, gettext_lazy, pgettext_lazy
|
||||
from pypdf import PdfMerger
|
||||
from pypdf import PdfWriter
|
||||
|
||||
from pretix.base.exporter import BaseExporter
|
||||
from pretix.base.i18n import language
|
||||
@@ -117,7 +117,7 @@ class AllTicketsPDF(BaseExporter):
|
||||
return d
|
||||
|
||||
def render(self, form_data):
|
||||
merger = PdfMerger()
|
||||
merger = PdfWriter()
|
||||
qs = OrderPosition.objects.filter(
|
||||
order__event__in=self.events
|
||||
).prefetch_related(
|
||||
|
||||
@@ -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 pypdf import PdfMerger
|
||||
from pypdf import PdfWriter
|
||||
|
||||
from pretix.base.i18n import language
|
||||
from pretix.base.models import Order, OrderPosition
|
||||
@@ -113,7 +113,7 @@ class PdfTicketOutput(BaseTicketOutput):
|
||||
return renderer.render_background(buffer, _('Ticket'))
|
||||
|
||||
def generate_order(self, order: Order):
|
||||
merger = PdfMerger()
|
||||
merger = PdfWriter()
|
||||
with language(order.locale, self.event.settings.region):
|
||||
for op in order.positions_with_tickets:
|
||||
layout = override_layout.send_chained(
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
# 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/>.
|
||||
#
|
||||
from django.conf.urls import re_path
|
||||
from django.urls import re_path
|
||||
|
||||
from pretix.api.urls import event_router
|
||||
from pretix.plugins.ticketoutputpdf.api import (
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
# 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/>.
|
||||
#
|
||||
from django.conf.urls import re_path
|
||||
from django.urls import re_path
|
||||
|
||||
from .views import IndexView
|
||||
|
||||
|
||||
@@ -514,7 +514,6 @@ class AddOnsStep(CartMixin, AsyncAction, TemplateFlowStep):
|
||||
'variation': cartpos.variation,
|
||||
'categories': []
|
||||
}
|
||||
formset.append(formsetentry)
|
||||
|
||||
current_addon_products = defaultdict(list)
|
||||
for a in cartpos.addons.all():
|
||||
@@ -592,6 +591,8 @@ class AddOnsStep(CartMixin, AsyncAction, TemplateFlowStep):
|
||||
'iao': iao,
|
||||
'items': items
|
||||
})
|
||||
if formsetentry['categories']:
|
||||
formset.append(formsetentry)
|
||||
return formset
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
from bootstrap3.renderers import FieldRenderer
|
||||
from bootstrap3.text import text_value
|
||||
from bootstrap3.utils import add_css_class
|
||||
from django.forms import CheckboxInput, CheckboxSelectMultiple, RadioSelect
|
||||
@@ -28,6 +27,8 @@ from django.utils.html import escape, format_html, strip_tags
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import pgettext
|
||||
|
||||
from pretix.base.forms.renderers import FieldRenderer
|
||||
|
||||
|
||||
def render_label(content, label_for=None, label_class=None, label_title='', label_id='', optional=False, is_valid=None, attrs=None):
|
||||
"""
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
import datetime
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import pytz
|
||||
import vobject
|
||||
from django.conf import settings
|
||||
from django.utils.formats import date_format
|
||||
@@ -41,11 +40,11 @@ def get_public_ical(events):
|
||||
"""
|
||||
cal = vobject.iCalendar()
|
||||
cal.add('prodid').value = '-//pretix//{}//'.format(settings.PRETIX_INSTANCE_NAME.replace(" ", "_"))
|
||||
creation_time = datetime.datetime.now(pytz.utc)
|
||||
creation_time = datetime.datetime.now(datetime.timezone.utc)
|
||||
|
||||
for ev in events:
|
||||
event = ev if isinstance(ev, Event) else ev.event
|
||||
tz = pytz.timezone(event.settings.timezone)
|
||||
tz = event.timezone
|
||||
if isinstance(ev, Event):
|
||||
url = build_absolute_uri(event, 'presale:event.index')
|
||||
else:
|
||||
@@ -114,9 +113,9 @@ def get_private_icals(event, positions):
|
||||
- It would be pretty hard to implement it in a way that doesn't require us to use distinct
|
||||
settings fields for emails to customers and to attendees, which feels like an overcomplication.
|
||||
"""
|
||||
tz = pytz.timezone(event.settings.timezone)
|
||||
tz = event.timezone
|
||||
|
||||
creation_time = datetime.datetime.now(pytz.utc)
|
||||
creation_time = datetime.datetime.now(datetime.timezone.utc)
|
||||
calobjects = []
|
||||
|
||||
evs = set(p.subevent or event for p in positions)
|
||||
|
||||
@@ -26,6 +26,11 @@
|
||||
</summary>
|
||||
<div id="cp{{ form.pos.pk }}">
|
||||
<div class="panel-body">
|
||||
{% if form.pos.seat %}
|
||||
<p>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="16" height="14" viewBox="2 0 16 14"><path d="M7.713 3.573c-.787-.124-1.677.472-1.511 1.529l.857 3.473c.116.579.578 1.086 1.317 1.086h3.166v3.504c0 1.108 1.556 1.113 1.556.019V8.682c0-.536-.376-1.116-1.099-1.116L9.52 7.563l-.752-2.936c-.147-.648-.583-.981-1.055-1.055v.001Z"/><path d="M4.48 5.835a.6.6 0 0 0-.674.725l.71 3.441c.287 1.284 1.39 2.114 2.495 2.114h2.273c.807 0 .811-1.215-.01-1.215H7.188c-.753 0-1.375-.45-1.563-1.289l-.672-3.293c-.062-.3-.26-.452-.474-.483ZM7.433.102a1.468 1.468 0 1 0 0 2.937 1.469 1.469 0 1 0 0-2.937Z"/></svg>{{ form.pos.seat }}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% if form.pos.subevent %}
|
||||
<p>
|
||||
<span class="fa fa-calendar" aria-hidden="true"></span>
|
||||
|
||||
@@ -177,7 +177,7 @@
|
||||
{% if var.cached_availability.0 == 100 or var.initial %}
|
||||
<div class="col-md-2 col-sm-3 col-xs-6 availability-box available">
|
||||
{% if c.max_count == 1 or not c.multi_allowed %}
|
||||
<label class="item-checkbox-label">
|
||||
<label class="btn btn-default btn-checkbox">
|
||||
<input type="checkbox" value="1"
|
||||
{% if var.initial %}checked="checked"{% endif %}
|
||||
{% if item.free_price %}
|
||||
@@ -187,7 +187,8 @@
|
||||
name="cp_{{ form.pos.pk }}_variation_{{ item.id }}_{{ var.id }}"
|
||||
data-exclusive-prefix="cp_{{ form.pos.pk }}_variation_{{ item.id }}_"
|
||||
aria-label="{% blocktrans with item=item.name var=var %}Add {{ item }}, {{ var }} to cart{% endblocktrans %}">
|
||||
<i class="fa fa-cart-plus fa-lg" aria-hidden="true"></i>
|
||||
<i class="fa fa-shopping-cart" aria-hidden="true"></i>
|
||||
{% trans "Select" context "checkbox" %}
|
||||
</label>
|
||||
{% else %}
|
||||
<div class="input-item-count-group">
|
||||
@@ -305,7 +306,7 @@
|
||||
{% if item.cached_availability.0 == 100 or item.initial %}
|
||||
<div class="col-md-2 col-sm-3 col-xs-6 availability-box available">
|
||||
{% if c.max_count == 1 or not c.multi_allowed %}
|
||||
<label class="item-checkbox-label">
|
||||
<label class="btn btn-default btn-checkbox">
|
||||
<input type="checkbox" value="1"
|
||||
{% if item.free_price %}
|
||||
data-checked-onchange="price-item-{{ form.pos.pk }}-{{ item.pk }}"
|
||||
@@ -320,7 +321,8 @@
|
||||
id="cp_{{ form.pos.pk }}_item_{{ item.id }}"
|
||||
aria-label="{% blocktrans with item=item.name %}Add {{ item }} to cart{% endblocktrans %}"
|
||||
{% if item.description %} aria-describedby="cp-{{ form.pos.pk }}-item-{{ item.id }}-description"{% endif %}>
|
||||
<i class="fa fa-cart-plus fa-lg" aria-hidden="true"></i>
|
||||
<i class="fa fa-shopping-cart" aria-hidden="true"></i>
|
||||
{% trans "Select" context "checkbox" %}
|
||||
</label>
|
||||
{% else %}
|
||||
<div class="input-item-count-group">
|
||||
|
||||
@@ -184,7 +184,7 @@
|
||||
{% elif var.cached_availability.0 == 100 %}
|
||||
<div class="col-md-2 col-sm-3 col-xs-6 availability-box available">
|
||||
{% if var.order_max == 1 %}
|
||||
<label class="item-checkbox-label">
|
||||
<label class="btn btn-default btn-checkbox">
|
||||
<input type="checkbox" value="1"
|
||||
{% if item.free_price %}
|
||||
data-checked-onchange="price-variation-{{ item.pk }}-{{ var.pk }}"
|
||||
@@ -194,7 +194,8 @@
|
||||
name="variation_{{ item.id }}_{{ var.id }}"
|
||||
aria-label="{% blocktrans with item=item.name var=var %}Add {{ item }}, {{ var }} to cart{% endblocktrans %}"
|
||||
{% if var.description %} aria-describedby="item-{{ item.pk }}-{{ var.pk }}-description"{% endif %}>
|
||||
<i class="fa fa-cart-plus fa-lg" aria-hidden="true"></i>
|
||||
<i class="fa fa-shopping-cart" aria-hidden="true"></i>
|
||||
{% trans "Select" context "checkbox" %}
|
||||
</label>
|
||||
{% else %}
|
||||
<div class="input-item-count-group">
|
||||
@@ -328,7 +329,7 @@
|
||||
{% elif item.cached_availability.0 == 100 %}
|
||||
<div class="col-md-2 col-sm-3 col-xs-6 availability-box available">
|
||||
{% if item.order_max == 1 %}
|
||||
<label class="item-checkbox-label">
|
||||
<label class="btn btn-default btn-checkbox">
|
||||
<input type="checkbox" value="1" {% if itemnum == 1 %}checked{% endif %}
|
||||
{% if item.free_price %}
|
||||
data-checked-onchange="price-item-{{ item.pk }}"
|
||||
@@ -337,7 +338,8 @@
|
||||
name="item_{{ item.id }}" id="item_{{ item.id }}"
|
||||
aria-label="{% blocktrans with item=item.name %}Add {{ item }} to cart{% endblocktrans %}"
|
||||
{% if item.description %} aria-describedby="item-{{ item.id }}-description"{% endif %}>
|
||||
<i class="fa fa-cart-plus fa-lg" aria-hidden="true"></i>
|
||||
<i class="fa fa-shopping-cart" aria-hidden="true"></i>
|
||||
{% trans "Select" context "checkbox" %}
|
||||
</label>
|
||||
{% else %}
|
||||
<div class="input-item-count-group">
|
||||
|
||||
@@ -243,7 +243,7 @@
|
||||
<div class="col-md-2 col-sm-3 col-xs-6 availability-box available radio-box">
|
||||
{% if max_times > 1 %}
|
||||
{% if var.order_max == 1 %}
|
||||
<label class="item-checkbox-label">
|
||||
<label class="btn btn-default btn-checkbox">
|
||||
<input type="checkbox"
|
||||
value="1"
|
||||
id="variation_{{ item.id }}_{{ var.id }}"
|
||||
@@ -251,7 +251,8 @@
|
||||
{% if options == 1 %}checked{% endif %}
|
||||
aria-label="{% blocktrans with item=item.name var=var %}Add {{ item }}, {{ var }} to cart{% endblocktrans %}"
|
||||
{% if var.description %} aria-describedby="item-{{ item.pk }}-{{ var.pk }}-description"{% endif %}>
|
||||
<i class="fa fa-cart-plus fa-lg" aria-hidden="true"></i>
|
||||
<i class="fa fa-shopping-cart" aria-hidden="true"></i>
|
||||
{% trans "Select" context "checkbox" %}
|
||||
</label>
|
||||
{% else %}
|
||||
<div class="input-item-count-group">
|
||||
@@ -385,7 +386,7 @@
|
||||
<div class="col-md-2 col-sm-3 col-xs-6 availability-box available radio-box">
|
||||
{% if max_times > 1 %}
|
||||
{% if item.order_max == 1 %}
|
||||
<label class="item-checkbox-label">
|
||||
<label class="btn btn-default btn-checkbox">
|
||||
<input type="checkbox"
|
||||
value="1"
|
||||
id="item_{{ item.id }}"
|
||||
@@ -393,7 +394,8 @@
|
||||
{% if options == 1 %}checked{% endif %}
|
||||
aria-label="{% blocktrans with item=item.name %}Add {{ item }} to cart{% endblocktrans %}"
|
||||
{% if item.description %} aria-describedby="item-{{ item.id }}-description"{% endif %}>
|
||||
<i class="fa fa-cart-plus fa-lg" aria-hidden="true"></i>
|
||||
<i class="fa fa-shopping-cart" aria-hidden="true"></i>
|
||||
{% trans "Select" context "checkbox" %}
|
||||
</label>
|
||||
{% else %}
|
||||
<div class="input-item-count-group">
|
||||
|
||||
@@ -32,8 +32,7 @@
|
||||
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations under the License.
|
||||
|
||||
from django.conf.urls import re_path
|
||||
from django.urls import include
|
||||
from django.urls import include, re_path
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
|
||||
import pretix.presale.views.cart
|
||||
|
||||
@@ -29,7 +29,6 @@ from urllib.parse import parse_qs, urlencode, urlparse, urlunparse
|
||||
from Crypto.PublicKey import RSA
|
||||
from django.db import transaction
|
||||
from django.http import Http404, HttpResponse, JsonResponse
|
||||
from django.middleware.csrf import _compare_masked_tokens
|
||||
from django.shortcuts import redirect, render
|
||||
from django.utils.crypto import get_random_string
|
||||
from django.utils.decorators import method_decorator
|
||||
@@ -103,8 +102,9 @@ class AuthorizeView(View):
|
||||
return self._process_auth_request(request, request.GET)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
request_token = CsrfViewMiddleware(lambda: None)._get_token(request)
|
||||
if not request_token or not _compare_masked_tokens(request.POST.get('csrfmiddlewaretoken', ''), request_token):
|
||||
try:
|
||||
CsrfViewMiddleware(lambda: None)._check_token(request)
|
||||
except:
|
||||
# External request, we prefer GET and will redirect to prevent confusion with our login form
|
||||
return redirect(request.path + '?' + request.POST.urlencode())
|
||||
return self._process_auth_request(request, request.GET)
|
||||
|
||||
@@ -38,10 +38,10 @@ from collections import defaultdict
|
||||
from datetime import date, datetime, time, timedelta
|
||||
from functools import reduce
|
||||
from urllib.parse import quote, urlencode
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import dateutil
|
||||
import isoweek
|
||||
import pytz
|
||||
from django.conf import settings
|
||||
from django.core.cache import caches
|
||||
from django.db.models import Exists, Max, Min, OuterRef, Prefetch, Q
|
||||
@@ -392,7 +392,7 @@ class OrganizerIndex(OrganizerViewMixin, EventListMixin, ListView):
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
for event in ctx['events']:
|
||||
event.tzname = pytz.timezone(event.cache.get_or_set('timezone', lambda: event.settings.timezone))
|
||||
event.tzname = ZoneInfo(event.cache.get_or_set('timezone', lambda: event.settings.timezone))
|
||||
if event.has_subevents:
|
||||
event.daterange = daterange(
|
||||
event.min_from.astimezone(event.tzname),
|
||||
@@ -506,7 +506,7 @@ def add_subevents_for_days(qs, before, after, ebd, timezones, event=None, cart_n
|
||||
continue
|
||||
|
||||
timezones.add(s.timezone)
|
||||
tz = pytz.timezone(s.timezone)
|
||||
tz = ZoneInfo(s.timezone)
|
||||
datetime_from = se.date_from.astimezone(tz)
|
||||
date_from = datetime_from.date()
|
||||
if s.show_date_to and se.date_to:
|
||||
@@ -623,8 +623,8 @@ class CalendarView(OrganizerViewMixin, EventListMixin, TemplateView):
|
||||
raise Http404()
|
||||
|
||||
tz = get_current_timezone()
|
||||
before = tz.localize(datetime(self.year, self.month, 1, 0, 0, 0)) - timedelta(days=1)
|
||||
after = tz.localize(datetime(self.year, self.month, ndays, 0, 0, 0)) + timedelta(days=1)
|
||||
before = datetime(self.year, self.month, 1, 0, 0, 0, tzinfo=tz) - timedelta(days=1)
|
||||
after = datetime(self.year, self.month, ndays, 0, 0, 0, tzinfo=tz) + timedelta(days=1)
|
||||
|
||||
ctx['date'] = date(self.year, self.month, 1)
|
||||
ctx['before'] = before
|
||||
@@ -700,12 +700,12 @@ class WeekCalendarView(OrganizerViewMixin, EventListMixin, TemplateView):
|
||||
|
||||
tz = get_current_timezone()
|
||||
week = isoweek.Week(self.year, self.week)
|
||||
before = tz.localize(datetime(
|
||||
week.monday().year, week.monday().month, week.monday().day, 0, 0, 0,
|
||||
)) - timedelta(days=1)
|
||||
after = tz.localize(datetime(
|
||||
week.sunday().year, week.sunday().month, week.sunday().day, 0, 0, 0,
|
||||
)) + timedelta(days=1)
|
||||
before = datetime(
|
||||
week.monday().year, week.monday().month, week.monday().day, 0, 0, 0, tzinfo=tz,
|
||||
) - timedelta(days=1)
|
||||
after = datetime(
|
||||
week.sunday().year, week.sunday().month, week.sunday().day, 0, 0, 0, tzinfo=tz,
|
||||
) + timedelta(days=1)
|
||||
|
||||
ctx['date'] = week.monday()
|
||||
ctx['before'] = before
|
||||
@@ -827,12 +827,12 @@ class DayCalendarView(OrganizerViewMixin, EventListMixin, TemplateView):
|
||||
ctx = super().get_context_data()
|
||||
|
||||
tz = get_current_timezone()
|
||||
before = tz.localize(datetime(
|
||||
self.date.year, self.date.month, self.date.day, 0, 0, 0,
|
||||
)) - timedelta(days=1)
|
||||
after = tz.localize(datetime(
|
||||
self.date.year, self.date.month, self.date.day, 0, 0, 0,
|
||||
)) + timedelta(days=1)
|
||||
before = datetime(
|
||||
self.date.year, self.date.month, self.date.day, 0, 0, 0, tzinfo=tz,
|
||||
) - timedelta(days=1)
|
||||
after = datetime(
|
||||
self.date.year, self.date.month, self.date.day, 0, 0, 0, tzinfo=tz,
|
||||
) + timedelta(days=1)
|
||||
|
||||
ctx['date'] = self.date
|
||||
ctx['cal_tz'] = self.tz
|
||||
|
||||
@@ -26,9 +26,9 @@ import logging
|
||||
from collections import defaultdict
|
||||
from datetime import date, datetime, timedelta
|
||||
from urllib.parse import urljoin
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import isoweek
|
||||
import pytz
|
||||
from compressor.filters.jsmin import rJSMinFilter
|
||||
from django.conf import settings
|
||||
from django.contrib.staticfiles import finders
|
||||
@@ -438,7 +438,7 @@ class WidgetAPIProductList(EventListMixin, View):
|
||||
event = ev.event
|
||||
else:
|
||||
event = ev
|
||||
tz = pytz.timezone(e['timezone'])
|
||||
tz = ZoneInfo(e['timezone'])
|
||||
time = date_format(ev.date_from.astimezone(tz), 'TIME_FORMAT') if e.get('time') and event.settings.show_times else None
|
||||
if time and ev.date_to and ev.date_from.astimezone(tz).date() == ev.date_to.astimezone(tz).date() and event.settings.show_date_to:
|
||||
time += ' – ' + date_format(ev.date_to.astimezone(tz), 'TIME_FORMAT')
|
||||
@@ -626,7 +626,7 @@ class WidgetAPIProductList(EventListMixin, View):
|
||||
data['events'] = []
|
||||
qs = self._get_event_queryset()
|
||||
for event in qs:
|
||||
tz = pytz.timezone(event.cache.get_or_set('timezone', lambda: event.settings.timezone))
|
||||
tz = ZoneInfo(event.cache.get_or_set('timezone', lambda: event.settings.timezone))
|
||||
if event.has_subevents:
|
||||
dr = daterange(
|
||||
event.min_from.astimezone(tz),
|
||||
|
||||
@@ -110,16 +110,11 @@ PRETIX_AUTH_BACKENDS = config.get('pretix', 'auth_backends', fallback='pretix.ba
|
||||
db_backend = config.get('database', 'backend', fallback='sqlite3')
|
||||
if db_backend == 'postgresql_psycopg2':
|
||||
db_backend = 'postgresql'
|
||||
DATABASE_IS_GALERA = config.getboolean('database', 'galera', fallback=False)
|
||||
if DATABASE_IS_GALERA and 'mysql' in db_backend:
|
||||
db_options = {
|
||||
'init_command': 'SET SESSION wsrep_sync_wait = 1;'
|
||||
}
|
||||
else:
|
||||
db_options = {}
|
||||
elif 'mysql' in db_backend:
|
||||
print("pretix does no longer support running on MySQL/MariaDB")
|
||||
sys.exit(1)
|
||||
|
||||
if 'mysql' in db_backend:
|
||||
db_options['charset'] = 'utf8mb4'
|
||||
db_options = {}
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
@@ -132,10 +127,7 @@ DATABASES = {
|
||||
'CONN_MAX_AGE': 0 if db_backend == 'sqlite3' else 120,
|
||||
'CONN_HEALTH_CHECKS': db_backend != 'sqlite3', # Will only be used from Django 4.1 onwards
|
||||
'OPTIONS': db_options,
|
||||
'TEST': {
|
||||
'CHARSET': 'utf8mb4',
|
||||
'COLLATION': 'utf8mb4_unicode_ci',
|
||||
} if 'mysql' in db_backend else {}
|
||||
'TEST': {}
|
||||
}
|
||||
}
|
||||
DATABASE_REPLICA = 'default'
|
||||
@@ -150,10 +142,7 @@ if config.has_section('replica'):
|
||||
'PORT': config.get('replica', 'port', fallback=DATABASES['default']['PORT']),
|
||||
'CONN_MAX_AGE': 0 if db_backend == 'sqlite3' else 120,
|
||||
'OPTIONS': db_options,
|
||||
'TEST': {
|
||||
'CHARSET': 'utf8mb4',
|
||||
'COLLATION': 'utf8mb4_unicode_ci',
|
||||
} if 'mysql' in db_backend else {}
|
||||
'TEST': {}
|
||||
}
|
||||
DATABASE_ROUTERS = ['pretix.helpers.database.ReplicaRouter']
|
||||
|
||||
@@ -174,7 +163,7 @@ SITE_URL = config.get('pretix', 'url', fallback='http://localhost:8000')
|
||||
if SITE_URL.endswith('/'):
|
||||
SITE_URL = SITE_URL[:-1]
|
||||
|
||||
CSRF_TRUSTED_ORIGINS = [urlparse(SITE_URL).hostname]
|
||||
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)
|
||||
@@ -638,8 +627,8 @@ CELERY_TASK_ROUTES = ([
|
||||
BOOTSTRAP3 = {
|
||||
'success_css_class': '',
|
||||
'field_renderers': {
|
||||
'default': 'bootstrap3.renderers.FieldRenderer',
|
||||
'inline': 'bootstrap3.renderers.InlineFieldRenderer',
|
||||
'default': 'pretix.base.forms.renderers.FieldRenderer',
|
||||
'inline': 'pretix.base.forms.renderers.InlineFieldRenderer',
|
||||
'control': 'pretix.control.forms.renderers.ControlFieldRenderer',
|
||||
'bulkedit': 'pretix.control.forms.renderers.BulkEditFieldRenderer',
|
||||
'bulkedit_inline': 'pretix.control.forms.renderers.InlineBulkEditFieldRenderer',
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user