Compare commits

...
Author SHA1 Message Date
Richard SchreiberandGitHub 1897bd4b26 Cart: make single-select checkbox look like a button 2023-06-06 08:53:35 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
fd6843822b Update pytest-xdist requirement from ==3.2.* to ==3.3.* (#3388)
Updates the requirements on [pytest-xdist](https://github.com/pytest-dev/pytest-xdist) to permit the latest version.
- [Changelog](https://github.com/pytest-dev/pytest-xdist/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-xdist/compare/v3.2.0...v3.3.1)

---
updated-dependencies:
- dependency-name: pytest-xdist
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-05 22:33:09 +02:00
Raphael MichelandRaphael Michel ee1644e037 Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (5314 of 5314 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/de_Informal/

powered by weblate
2023-06-05 18:34:11 +02:00
Raphael MichelandRaphael Michel a6c1486650 Translations: Update German
Currently translated at 100.0% (5314 of 5314 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/de/

powered by weblate
2023-06-05 18:34:11 +02:00
Raphael MichelandGitHub f4b437e92b Remove MariaDB support (#3381) 2023-06-05 18:25:20 +02:00
Raphael Michel 446c55dc89 Silence deprecation warning caused by pycountry 2023-06-05 18:24:57 +02:00
Raphael Michel 0990eeeea0 Fix deprecation warning 2023-06-05 18:24:51 +02:00
Raphael Michel 591fe23a99 Invoices: Fix timezone when calculating date of cancellation 2023-06-05 15:49:39 +02:00
Raphael Michel ad70765287 Fix event creation after Django 4.1 upgrade 2023-06-05 13:00:32 +02:00
Richard SchreiberandGitHub c59d29493c Checkout: Hide empty add-on forms and show seat above add-on form 2023-06-05 10:08:47 +02:00
Raphael MichelandGitHub bd32b33ba9 Bump Django to 4.1.* (#2989) 2023-06-05 09:56:31 +02:00
Raphael MichelandRaphael Michel 3a8556bb78 Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (5314 of 5314 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/de_Informal/

powered by weblate
2023-06-05 09:35:44 +02:00
Raphael MichelandRaphael Michel c972d24ce7 Translations: Update German
Currently translated at 100.0% (5314 of 5314 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/de/

powered by weblate
2023-06-05 09:35:44 +02:00
Yucheng LinandRaphael Michel 647e68ef01 Translations: Update Chinese (Traditional)
Currently translated at 63.7% (3388 of 5314 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/zh_Hant/

powered by weblate
2023-06-05 09:35:44 +02:00
Yucheng LinandRaphael Michel f439a591df Translations: Update Chinese (Traditional)
Currently translated at 63.6% (3385 of 5314 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/zh_Hant/

powered by weblate
2023-06-05 09:35:44 +02:00
Richard SchreiberandGitHub 8f17b338d1 Replace deprecated pypdf.PdfMerger with pypdf.PdfWriter (#3383) 2023-06-05 09:32:03 +02:00
Raphael Michel 35350a13d6 Fix #3360 -- Allow to revoke devices before initialized 2023-06-04 18:06:00 +02:00
Raphael Michel 0d93f7f52f Fix crash in name rendering (PRETIXEU-8GS) 2023-06-03 21:49:14 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
170dcf93e7 Update pypdf requirement from ==3.8.* to ==3.9.* (#3377)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-02 21:26:17 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
9319202213 Bump @babel/core from 7.21.5 to 7.22.1 in /src/pretix/static/npm_dir (#3373)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-02 21:25:26 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Raphael Michel
bfd0eee2c1 Update mt-940 requirement from ==4.23.* to ==4.30.* (#3345)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Raphael Michel <mail@raphaelmichel.de>
2023-06-02 21:25:15 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
8570f53ed0 Update django-otp requirement from ==1.1.* to ==1.2.* (#3338)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-02 20:08:56 +02:00
Raphael Michel f56f6dd628 Voucher: Add link to order in voucher history 2023-06-02 20:07:12 +02:00
Richard SchreiberandGitHub 413fabd821 Product list: add border to disabled spinner buttons (#3359) 2023-06-02 20:04:42 +02:00
Julian RotherandGitHub 9813e59210 API: Fix crash when creating addons with order change endpoint (#3363) 2023-06-02 20:00:40 +02:00
Richard SchreiberandGitHub d91d942eac Invoicing: Add order-code to organizer CC mail (Z#23123051) (#3370) 2023-06-02 19:59:31 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
22104f79bd Bump @rollup/plugin-node-resolve from 15.0.2 to 15.1.0 in /src/pretix/static/npm_dir (#3374)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-02 19:59:12 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
f289ad9e4f Bump @babel/preset-env from 7.21.5 to 7.22.4 in /src/pretix/static/npm_dir (#3375)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-02 19:59:00 +02:00
Raphael MichelandRaphael Michel f81a734716 Translations: Update Chinese (Traditional)
Currently translated at 63.6% (3384 of 5314 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/zh_Hant/

powered by weblate
2023-06-02 19:55:04 +02:00
Raphael MichelandRaphael Michel 7a27a42e79 Translations: Update Chinese (Traditional)
Currently translated at 63.6% (3384 of 5314 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/zh_Hant/

powered by weblate
2023-06-02 19:55:04 +02:00
Yucheng LinandRaphael Michel 65a2bab9bb Translations: Update Chinese (Traditional)
Currently translated at 63.6% (3384 of 5314 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/zh_Hant/

powered by weblate
2023-06-02 19:55:04 +02:00
Yucheng LinandRaphael Michel a26f46b619 Translations: Update Chinese (Traditional)
Currently translated at 63.2% (3359 of 5314 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/zh_Hant/

powered by weblate
2023-06-02 19:55:04 +02:00
Hans FraipontsandRaphael Michel 5c37c85415 Translations: Update Dutch
Currently translated at 85.2% (4531 of 5314 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/nl/

powered by weblate
2023-06-02 19:55:04 +02:00
Yucheng LinandRaphael Michel 8ddba36690 Translations: Update Chinese (Traditional)
Currently translated at 61.6% (3275 of 5314 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/zh_Hant/

powered by weblate
2023-06-02 19:55:04 +02:00
Thomas VrankenandRaphael Michel f9bf05e09b Translations: Update Dutch
Currently translated at 85.2% (4530 of 5314 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/nl/

powered by weblate
2023-06-02 19:55:04 +02:00
Raphael Michel 8471422bba Fix grammer error in settings help text 2023-06-02 19:08:21 +02:00
Raphael Michel ee9acebe03 Devices: Fix crash in form validation 2023-06-02 17:19:25 +02:00
Raphael Michel 35d2a73f75 Voucher creation: Fix crash in validation (PRETIXEU-8GF) 2023-06-02 17:19:25 +02:00
Richard Schreiber eb3eca45b5 Checkout/Addon: fix spinner button class name 2023-06-01 16:12:54 +02:00
Martin Gross f7816924b0 Add Chinese (Traditional) (zh_Hant) to list of available languages. 2023-05-31 13:06:31 +02:00
Raphael Michel 12c3fef390 Docs: Add missing navigation node 2023-05-31 12:58:54 +02:00
Raphael Michel 8e39aaa292 Bump version to 4.21.0.dev0 2023-05-31 12:45:24 +02:00
162 changed files with 1954 additions and 1624 deletions
+2 -2
View File
@@ -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
+3 -13
View File
@@ -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
+1 -2
View File
@@ -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
+1 -9
View File
@@ -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
+1 -4
View File
@@ -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 -3
View File
@@ -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
+1
View File
@@ -18,6 +18,7 @@ at :ref:`plugin-docs`.
item_variations
item_bundles
item_add-ons
item_meta_properties
questions
question_options
quotas
+3 -3
View File
@@ -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
+3 -3
View File
@@ -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 = [
+7 -8
View File
@@ -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,9 +49,8 @@ dependencies = [
"django-libsass==0.9",
"django-localflavor==4.0",
"django-markup",
"django-mysql",
"django-oauth-toolkit==2.2.*",
"django-otp==1.1.*",
"django-otp==1.2.*",
"django-phonenumber-field==7.1.*",
"django-redis==5.2.*",
"django-scopes==2.0.*",
@@ -68,7 +67,7 @@ dependencies = [
"lxml",
"markdown==3.4.3", # 3.3.5 requires importlib-metadata>=4.4, but django-bootstrap3 requires importlib-metadata<3.
# We can upgrade markdown again once django-bootstrap3 upgrades or once we drop Python 3.6 and 3.7
"mt-940==4.23.*",
"mt-940==4.30.*",
"oauthlib==3.2.*",
"openpyxl==3.1.*",
"packaging",
@@ -82,11 +81,12 @@ dependencies = [
"pycountry",
"pycparser==2.21",
"pycryptodome==3.18.*",
"pypdf==3.8.*",
"pypdf==3.9.*",
"python-bidi==0.4.*", # Support for Arabic in reportlab
"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",
]
+1 -1
View File
@@ -19,4 +19,4 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
__version__ = "4.20.0"
__version__ = "4.21.0.dev0"
+4 -1
View File
@@ -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 = [
@@ -80,6 +80,7 @@ ALL_LANGUAGES = [
('de-informal', _('German (informal)')),
('ar', _('Arabic')),
('zh-hans', _('Chinese (simplified)')),
('zh-hant', _('Chinese (traditional)')),
('cs', _('Czech')),
('da', _('Danish')),
('nl', _('Dutch')),
@@ -179,6 +180,8 @@ TEMPLATES = [
},
]
FORM_RENDERER = "django.forms.renderers.TemplatesSetting"
STATIC_ROOT = os.path.join(os.path.dirname(__file__), 'static.dist')
STATICFILES_FINDERS = (
+9 -5
View File
@@ -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'])
@@ -70,6 +70,8 @@ class OrderPositionCreateForExistingOrderSerializer(OrderPositionCreateSerialize
def validate(self, data):
data = super().validate(data)
if 'order' in self.context:
data['order'] = self.context['order']
if data.get('addon_to'):
try:
data['addon_to'] = data['order'].positions.get(positionid=data['addon_to'])
+1 -2
View File
@@ -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
+3
View File
@@ -93,6 +93,9 @@ class InitializeView(APIView):
if device.initialized:
raise ValidationError({'token': ['This initialization token has already been used.']})
if device.revoked:
raise ValidationError({'token': ['This initialization token has been revoked.']})
device.initialized = now()
device.hardware_brand = serializer.validated_data.get('hardware_brand')
device.hardware_model = serializer.validated_data.get('hardware_model')
+2 -2
View File
@@ -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)
+2 -2
View File
@@ -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
+6 -6
View File
@@ -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,
+2 -2
View File
@@ -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 = [
+1
View File
@@ -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):
+2 -2
View File
@@ -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
View 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']:
+3 -3
View File
@@ -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 {}
}
),
]
+1 -1
View File
@@ -178,7 +178,7 @@ class LoggedModel(models.Model, LoggingMixin):
return LogEntry.objects.filter(
content_type=self.logs_content_type, object_id=self.pk
).select_related('user', 'event', 'oauth_application', 'api_token', 'device')
).select_related('user', 'event', 'event__organizer', 'oauth_application', 'api_token', 'device')
class LockModel:
+13 -4
View File
@@ -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):
+4
View File
@@ -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:
+4 -3
View File
@@ -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)
+6 -8
View File
@@ -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)
+12 -1
View File
@@ -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):
+16 -8
View File
@@ -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
+46 -19
View File
@@ -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)
+3 -2
View File
@@ -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):
+4 -1
View File
@@ -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)
+10 -3
View File
@@ -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
+3 -3
View File
@@ -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
+2 -2
View File
@@ -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:
+16 -17
View File
@@ -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 ""
}),
+4 -7
View File
@@ -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:
+9 -12
View File
@@ -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'])
+4 -3
View File
@@ -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
@@ -510,7 +510,7 @@ def send_invoices_to_organizer(sender, **kwargs):
with transaction.atomic():
qs = Invoice.objects.filter(
sent_to_organizer__isnull=True
).prefetch_related('event').select_for_update(of=OF_SELF, skip_locked=connection.features.has_select_for_update_skip_locked)
).prefetch_related('event', 'order').select_for_update(of=OF_SELF, skip_locked=connection.features.has_select_for_update_skip_locked)
for i in qs[:batch_size]:
if i.event.settings.invoice_email_organizer:
with language(i.event.settings.locale):
@@ -519,11 +519,12 @@ def send_invoices_to_organizer(sender, **kwargs):
subject=_('New invoice: {number}').format(number=i.number),
template=LazyI18nString.from_gettext(_(
'Hello,\n\n'
'a new invoice for {event} has been created, see attached.\n\n'
'a new invoice for order {order} at {event} has been created, see attached.\n\n'
'We are sending this email because you configured us to do so in your event settings.'
)),
context={
'event': str(i.event),
'order': str(i.order),
},
locale=i.event.settings.locale,
event=i.event,
+3 -3
View File
@@ -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:
+2 -2
View File
@@ -1409,7 +1409,7 @@ DEFAULTS = {
'form_class': forms.BooleanField,
'form_kwargs': dict(
label=_("Show number of check-ins to customer"),
help_text=_('With this option enabled, your customers will be able how many times they entered '
help_text=_('With this option enabled, your customers will be able to see how many times they entered '
'the event. This is usually not necessary, but might be useful in combination with tickets '
'that are usable a specific number of times, so customers can see how many times they have '
'already been used. Exits or failed scans will not be counted, and the user will not see '
@@ -3256,7 +3256,7 @@ def concatenation_for_salutation(d):
def get_name_parts_localized(name_parts, key):
value = name_parts.get(key, "")
if key == "salutation":
if key == "salutation" and value:
return pgettext_lazy("person_name_salutation", value)
return value
@@ -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;
+2 -3
View File
@@ -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")
+5 -5
View File
@@ -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
+4 -3
View File
@@ -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']
+1 -1
View File
@@ -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)
+2 -2
View File
@@ -273,7 +273,7 @@ class DeviceForm(forms.ModelForm):
def clean(self):
d = super().clean()
if not d['all_events'] and not d['limit_events']:
if not d['all_events'] and not d.get('limit_events'):
raise ValidationError(_('Your device will not have access to anything, please select some events.'))
return d
@@ -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:
+2 -1
View File
@@ -19,7 +19,6 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
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):
"""
+11 -10
View File
@@ -386,17 +386,18 @@ class VoucherBulkForm(VoucherForm):
def clean(self):
data = super().clean()
vouchers = self.instance.event.vouchers.annotate(
code_upper=Upper('code')
).filter(code_upper__in=[c.upper() for c in data['codes']])
if vouchers.exists():
raise ValidationError(_('A voucher with one of these codes already exists.'))
if 'codes' in data:
vouchers = self.instance.event.vouchers.annotate(
code_upper=Upper('code')
).filter(code_upper__in=[c.upper() for c in data['codes']])
if vouchers.exists():
raise ValidationError(_('A voucher with one of these codes already exists.'))
codes_seen = set()
for c in data['codes']:
if c in codes_seen:
raise ValidationError(_('The voucher code {code} appears in your list twice.').format(code=c))
codes_seen.add(c)
codes_seen = set()
for c in data['codes']:
if c in codes_seen:
raise ValidationError(_('The voucher code {code} appears in your list twice.').format(code=c))
codes_seen.add(c)
if data.get('send') and not all([data.get('send_subject'), data.get('send_message'), data.get('send_recipients')]):
raise ValidationError(_('If vouchers should be sent by email, subject, message and recipients need to be specified.'))
+13 -3
View File
@@ -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:
@@ -571,6 +570,17 @@ def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
else:
data['value'] = LazyI18nString(data['value'])
if logentry.action_type == "pretix.voucher.redeemed":
data = defaultdict(lambda: '?', data)
url = reverse('control:event.order', kwargs={
'event': logentry.event.slug,
'organizer': logentry.event.organizer.slug,
'code': data['order_code']
})
return mark_safe(plains[logentry.action_type].format(
order_code='<a href="{}">{}</a>'.format(url, data['order_code']),
))
if logentry.action_type in plains:
data = defaultdict(lambda: '?', data)
return plains[logentry.action_type].format_map(data)
@@ -616,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." %}
@@ -162,16 +162,19 @@
<a href="{% url "control:organizer.device.connect" organizer=request.organizer.slug device=d.id %}"
class="btn btn-primary btn-sm"><i class="fa fa-link"></i>
{% trans "Connect" %}</a>
{% elif d.api_token %}
{% endif %}
{% if not d.initialized or d.api_token %}
<a href="{% url "control:organizer.device.revoke" organizer=request.organizer.slug device=d.id %}"
class="btn btn-default btn-sm">
{% trans "Revoke access" %}</a>
{% endif %}
<a href="{% url "control:organizer.device.logs" organizer=request.organizer.slug device=d.id %}"
class="btn btn-default btn-sm">
<span class="fa fa-list-alt"></span>
{% trans "Logs" %}
</a>
{% if d.initialized %}
<a href="{% url "control:organizer.device.logs" organizer=request.organizer.slug device=d.id %}"
class="btn btn-default btn-sm">
<span class="fa fa-list-alt"></span>
{% trans "Logs" %}
</a>
{% endif %}
<a href="{% url "control:organizer.device.edit" organizer=request.organizer.slug device=d.id %}"
class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
</td>
+1 -2
View File
@@ -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 (
+17 -17
View File
@@ -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
+2 -2
View File
@@ -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")
+3 -3
View File
@@ -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):
+4 -5
View File
@@ -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
@@ -1184,7 +1183,7 @@ class DeviceRevokeView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixi
def get(self, request, *args, **kwargs):
self.object = self.get_object()
if not self.object.api_token:
if self.object.revoked:
messages.success(request, _('This device currently does not have access.'))
return redirect(reverse('control:organizer.devices', kwargs={
'organizer': self.request.organizer.slug,
@@ -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'
+2 -2
View File
@@ -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
+16 -10
View File
@@ -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)
+2 -2
View File
@@ -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)
+2 -13
View File
@@ -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 -5
View File
@@ -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 ""
+10 -14
View File
@@ -7,16 +7,16 @@ 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-04-27 01:00+0000\n"
"Last-Translator: Julian Geraerds <julian.geraerds@itisours.nl>\n"
"Language-Team: Dutch <https://translate.pretix.eu/projects/pretix/pretix/nl/"
">\n"
"PO-Revision-Date: 2023-06-02 13:44+0000\n"
"Last-Translator: Hans Fraiponts <fraiponts@gmail.com>\n"
"Language-Team: Dutch <https://translate.pretix.eu/projects/pretix/pretix/nl/>"
"\n"
"Language: nl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.16.4\n"
"X-Generator: Weblate 4.17\n"
#: htmlcov/pretix_control_views_dashboards_py.html:963
#: pretix/control/templates/pretixcontrol/events/index.html:140
@@ -21804,7 +21804,7 @@ msgstr "Ingetrokken"
#: pretix/control/templates/pretixcontrol/organizers/devices.html:164
msgid "Connect"
msgstr "Verbinden"
msgstr "Verbind"
#: pretix/control/templates/pretixcontrol/organizers/devices.html:173
#: pretix/control/templates/pretixcontrol/organizers/webhooks.html:76
@@ -29586,7 +29586,7 @@ msgstr "Factuurinstellingen"
#: pretix/presale/templates/pretixpresale/event/base.html:179
#: pretix/presale/templates/pretixpresale/organizers/base.html:97
msgid "Imprint"
msgstr "Afdruk"
msgstr "Info organisator"
#: pretix/presale/templates/pretixpresale/event/checkout_addons.html:10
msgid ""
@@ -29687,7 +29687,7 @@ msgstr "Factuurgegevens bewerken"
#: pretix/presale/templates/pretixpresale/event/checkout_questions.html:23
#: pretix/presale/templates/pretixpresale/event/order_modify.html:33
msgid "Contact information"
msgstr "Contactgegevens"
msgstr "Contact informatie"
#: pretix/presale/templates/pretixpresale/event/checkout_confirm.html:128
msgid "Modify contact information"
@@ -31385,10 +31385,8 @@ msgstr "Bevestig de volgende betalingsdetails."
#: pretix/presale/templates/pretixpresale/event/resend_link.html:4
#: pretix/presale/templates/pretixpresale/event/resend_link.html:11
#, fuzzy
#| msgid "Resend order link"
msgid "Resend order links"
msgstr "Bestellink opnieuw versturen"
msgstr "Bestellingslinks opnieuw versturen"
#: pretix/presale/templates/pretixpresale/event/resend_link.html:15
msgid ""
@@ -31508,10 +31506,8 @@ msgstr "Kalender"
#: pretix/presale/templates/pretixpresale/fragment_calendar.html:52
#: pretix/presale/templates/pretixpresale/fragment_day_calendar.html:65
#: pretix/presale/templates/pretixpresale/fragment_week_calendar.html:37
#, fuzzy
#| msgid "Continue"
msgid "(continued)"
msgstr "Doorgaan"
msgstr "(gaat verder)"
#: pretix/presale/templates/pretixpresale/fragment_calendar.html:61
#: pretix/presale/templates/pretixpresale/fragment_week_calendar.html:46
+128 -124
View File
@@ -8,7 +8,7 @@ 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-05-30 21:00+0000\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"
@@ -40,7 +40,7 @@ msgstr "預售結束"
#: pretix/control/templates/pretixcontrol/organizers/detail.html:108
#: pretix/control/templates/pretixcontrol/subevents/index.html:159
msgid "Presale not started"
msgstr ""
msgstr "預售尚未開始"
#: htmlcov/pretix_control_views_dashboards_py.html:969
#: pretix/control/templates/pretixcontrol/events/index.html:146
@@ -4674,16 +4674,15 @@ msgid "Subevent cannot be null for event series."
msgstr "對於事件系列,子事件不能為空白。"
#: pretix/base/models/items.py:2005
#, fuzzy
#| msgid "Required for events"
msgid "Required for products"
msgstr "活動所需"
msgstr "商品所需"
#: pretix/base/models/items.py:2006
msgid ""
"If checked, this property must be set in each product. Does not apply if a "
"default value is set."
msgstr ""
msgstr "如果已選取,則必須在每個商品中設定該屬性。假如已設定該屬性為預設值,則不適用"
"。"
#: pretix/base/models/log.py:159
#, python-brace-format
@@ -7883,21 +7882,22 @@ msgstr ""
#: pretix/base/settings.py:607
msgid "Minimum length of invoice number after prefix"
msgstr ""
msgstr "發票前綴編號的最小長度"
#: pretix/base/settings.py:608
msgid ""
"The part of your invoice number after your prefix will be filled up with "
"leading zeros up to this length, e.g. INV-001 or INV-00001."
msgstr ""
msgstr "發票編號中前綴後面的部分將用前導零填充,直至此長度,例如 INV-001 或 "
"INV-00001。"
#: pretix/base/settings.py:619
msgid "Generate invoices with consecutive numbers"
msgstr ""
msgstr "生成具有連續編號的發票"
#: pretix/base/settings.py:620
msgid "If deactivated, the order code will be used in the invoice number."
msgstr ""
msgstr "如果停用,訂單代碼將在發票編號中使用。"
#: pretix/base/settings.py:629
msgid "Invoice number prefix"
@@ -7919,21 +7919,21 @@ msgstr ""
"(帶世紀)%y(不帶世紀)插入發票的年份,或使用 %m 和 %d 插入月份中的某一天。"
#: pretix/base/settings.py:642 pretix/base/settings.py:664
#, fuzzy, python-brace-format
#| msgid "Please do not use special characters in names."
#, python-brace-format
msgid "Please only use the characters {allowed} in this field."
msgstr "請不要在名稱中使用特殊字元。"
msgstr "請僅在此欄位中使用字元{allowed}。"
#: pretix/base/settings.py:655
msgid "Invoice number prefix for cancellations"
msgstr ""
msgstr "取消的發票編號前綴"
#: pretix/base/settings.py:656
msgid ""
"This will be prepended to invoice numbers of cancellations. If you leave "
"this field empty, the same numbering scheme will be used that you configured "
"for regular invoices."
msgstr ""
msgstr "這將附加到取消的發票編號前面。如果將此欄位留空,則將使用你為常規發票設定的相"
"同編號方案。"
#: pretix/base/settings.py:677
msgid "Highlight order code to make it stand out visibly"
@@ -7950,7 +7950,7 @@ msgstr "字體"
#: pretix/base/settings.py:714
msgid "Length of ticket codes"
msgstr ""
msgstr "票證代碼的長度"
#: pretix/base/settings.py:741
msgid "Reservation period"
@@ -7959,16 +7959,16 @@ msgstr "保留期"
#: pretix/base/settings.py:743
msgid ""
"The number of minutes the items in a user's cart are reserved for this user."
msgstr ""
msgstr "使用者購物車中的商品為該使用者保留的數分鐘。"
#: pretix/base/settings.py:752
msgid ""
"Directly redirect to check-out after a product has been added to the cart."
msgstr ""
msgstr "將商品添加到購物車後直接重定向至結帳。"
#: pretix/base/settings.py:761
msgid "End of presale text"
msgstr ""
msgstr "結束預售文件"
#: pretix/base/settings.py:764
msgid ""
@@ -7987,7 +7987,7 @@ msgstr "指導文件"
msgid ""
"This text will be shown above the payment options. You can explain the "
"choices to the user here, if you want."
msgstr ""
msgstr "此文字將顯示在付款選項上方。如果需要,可以在此處向用戶解釋選擇。"
#: pretix/base/settings.py:790 pretix/base/settings.py:799
msgid "in days"
@@ -8005,11 +8005,12 @@ msgstr "設定付款期限"
msgid ""
"If using days, the order will expire at the end of the last day. Using "
"minutes is more exact, but should only be used for real-time payment methods."
msgstr ""
msgstr "如果使用天數,訂單將在最後一天結束時過期。使用分鐘更準確,但這應用只用於即時"
"付款方式。"
#: pretix/base/settings.py:812
msgid "Payment term in days"
msgstr ""
msgstr "付款期限(天)"
#: pretix/base/settings.py:819
msgid ""
@@ -8024,7 +8025,7 @@ msgstr ""
#: pretix/base/settings.py:837
msgid "Only end payment terms on weekdays"
msgstr ""
msgstr "只在工作日結束付款條件"
#: pretix/base/settings.py:838
msgid ""
@@ -8188,7 +8189,7 @@ msgstr "國內稅號ID"
#: pretix/base/settings.py:1073
msgid "e.g. tax number in Germany, ABN in Australia, …"
msgstr ""
msgstr "例如德國的稅號,澳大利亞的ABN,…"
#: pretix/base/settings.py:1082
msgid "EU VAT ID"
@@ -8196,7 +8197,7 @@ msgstr "歐盟增值稅ID"
#: pretix/base/settings.py:1095
msgid "e.g. With this document, we sent you the invoice for your ticket order."
msgstr ""
msgstr "例如,透過此文件,我們向你發送機票訂單的發票。"
#: pretix/base/settings.py:1098
msgid "Introductory text"
@@ -8204,7 +8205,7 @@ msgstr "介紹文件"
#: pretix/base/settings.py:1099
msgid "Will be printed on every invoice above the invoice rows."
msgstr ""
msgstr "將列印在發票行上方的每張發票上。"
#: pretix/base/settings.py:1112
msgid ""
@@ -8218,13 +8219,13 @@ msgstr "額外文件"
#: pretix/base/settings.py:1116
msgid "Will be printed on every invoice below the invoice total."
msgstr ""
msgstr "將會列印在每張發票總額以下的發票上。"
#: pretix/base/settings.py:1129
msgid ""
"e.g. your bank details, legal details like your VAT ID, registration "
"numbers, etc."
msgstr ""
msgstr "例如,你的銀行詳細資訊、增值稅號等法律詳細資訊、註冊號等。"
#: pretix/base/settings.py:1132
msgid "Footer"
@@ -8234,11 +8235,11 @@ msgstr "腳註"
msgid ""
"Will be printed centered and in a smaller font at the end of every invoice "
"page."
msgstr ""
msgstr "將在每張發票頁面的末尾以較小的字體居中列印。"
#: pretix/base/settings.py:1146
msgid "Attach invoices to emails"
msgstr ""
msgstr "將發票夾帶到電子郵件"
#: pretix/base/settings.py:1147
msgid ""
@@ -8253,7 +8254,7 @@ msgstr ""
#: pretix/base/settings.py:1159
msgid "Email address to receive a copy of each invoice"
msgstr ""
msgstr "用於接收每張發票副本的電子郵件位址"
#: pretix/base/settings.py:1160
msgid ""
@@ -8266,12 +8267,12 @@ msgstr ""
#: pretix/base/settings.py:1171
msgid "Show items outside presale period"
msgstr ""
msgstr "顯示預售期以外的商品"
#: pretix/base/settings.py:1172
msgid ""
"Show item details before presale has started and after presale has ended"
msgstr ""
msgstr "在預售開始前和預售結束後顯示商品詳細資訊"
#: pretix/base/settings.py:1192
msgid "Available languages"
@@ -8297,7 +8298,7 @@ msgstr ""
#: pretix/base/settings.py:1231
msgid "Show event times and dates on the ticket shop"
msgstr ""
msgstr "在售票處顯示起始時間和日期"
#: pretix/base/settings.py:1232
msgid ""
@@ -8309,37 +8310,37 @@ msgstr ""
#: pretix/base/settings.py:1242
msgid "Show event end date"
msgstr ""
msgstr "顯示活動結束日期"
#: pretix/base/settings.py:1243
msgid "If disabled, only event's start date will be displayed to the public."
msgstr ""
msgstr "如果禁用,則只向公眾顯示活動的開始日期。"
#: pretix/base/settings.py:1252
msgid "Show dates with time"
msgstr ""
msgstr "顯示日期與時間"
#: pretix/base/settings.py:1253
msgid ""
"If disabled, the event's start and end date will be displayed without the "
"time of day."
msgstr ""
msgstr "如果禁用,將顯示事件的開始和結束日期,而不顯示一天中的時間。"
#: pretix/base/settings.py:1262
msgid "Hide all products that are sold out"
msgstr ""
msgstr "隱藏所有已售罄的產品"
#: pretix/base/settings.py:1272 pretix/control/forms/event.py:1547
msgid "Publicly show how many tickets of a certain type are still available."
msgstr ""
msgstr "公開顯示某種類型的票證數量仍然可用。"
#: pretix/base/settings.py:1281
msgid "Ask search engines not to index the ticket shop"
msgstr ""
msgstr "要求搜尋引擎不要索引售票處"
#: pretix/base/settings.py:1290
msgid "Show variations of a product expanded by default"
msgstr ""
msgstr "顯示預設展示的商品的類型"
#: pretix/base/settings.py:1299
msgid "Enable waiting list"
@@ -8358,7 +8359,7 @@ msgstr ""
#: pretix/base/settings.py:1311
msgid "Automatic waiting list assignments"
msgstr ""
msgstr "自動候補名單分配"
#: pretix/base/settings.py:1312
msgid ""
@@ -8374,7 +8375,7 @@ msgstr ""
#: pretix/base/settings.py:1328
msgid "Waiting list response time"
msgstr ""
msgstr "等待名單回應時間"
#: pretix/base/settings.py:1331
msgid ""
@@ -8387,11 +8388,11 @@ msgstr ""
#: pretix/base/settings.py:1342
msgid "Ask for a name"
msgstr ""
msgstr "查詢姓名"
#: pretix/base/settings.py:1343
msgid "Ask for a name when signing up to the waiting list."
msgstr ""
msgstr "註冊等候名單時查詢姓名。"
#: pretix/base/settings.py:1352
msgid "Require name"
@@ -8399,15 +8400,15 @@ msgstr "需要姓名"
#: pretix/base/settings.py:1353
msgid "Require a name when signing up to the waiting list.."
msgstr ""
msgstr "註冊等候名單時需要姓名。"
#: pretix/base/settings.py:1363
msgid "Ask for a phone number"
msgstr ""
msgstr "詢問電話號碼"
#: pretix/base/settings.py:1364
msgid "Ask for a phone number when signing up to the waiting list."
msgstr ""
msgstr "註冊等候名單時詢問電話號碼。"
#: pretix/base/settings.py:1373
msgid "Require phone number"
@@ -8415,25 +8416,25 @@ msgstr "需要電話號碼"
#: pretix/base/settings.py:1374
msgid "Require a phone number when signing up to the waiting list.."
msgstr ""
msgstr "註冊等候名單時需要電話號碼。"
#: pretix/base/settings.py:1384
msgid "Phone number explanation"
msgstr ""
msgstr "電話號碼說明"
#: pretix/base/settings.py:1387
msgid ""
"If you ask for a phone number, explain why you do so and what you will use "
"the phone number for."
msgstr ""
msgstr "如果你要求提供電話號碼,請解釋你這樣做的原因,以及你將使用該電話號碼的目的。"
#: pretix/base/settings.py:1399
msgid "Maximum number of entries per email address for the same product"
msgstr ""
msgstr "同一商品的每件電子郵件位址的最大條目數"
#: pretix/base/settings.py:1411
msgid "Show number of check-ins to customer"
msgstr ""
msgstr "對客戶顯示簽到次數"
#: pretix/base/settings.py:1412
msgid ""
@@ -8450,11 +8451,11 @@ msgstr ""
#: pretix/base/settings.py:1425
msgid "Allow users to download tickets"
msgstr ""
msgstr "允許用戶下載票證"
#: pretix/base/settings.py:1426
msgid "If this is off, nobody can download a ticket."
msgstr ""
msgstr "如果關閉此功能,則沒有人可以下載票證。"
#: pretix/base/settings.py:1435
msgid "Download date"
@@ -8471,7 +8472,7 @@ msgstr ""
#: pretix/base/settings.py:1447
msgid "Generate tickets for add-on products and bundled products"
msgstr ""
msgstr "為附加商品和捆綁商品產生票證"
#: pretix/base/settings.py:1448
msgid ""
@@ -8484,28 +8485,29 @@ msgstr ""
#: pretix/base/settings.py:1461
msgid "Generate tickets for all products"
msgstr ""
msgstr "為所有商品產生門票"
#: pretix/base/settings.py:1462
msgid ""
"If turned off, tickets are only issued for products that are marked as an "
"\"admission ticket\"in the product settings. You can also turn off ticket "
"issuing in every product separately."
msgstr ""
msgstr "如果關閉,則只對在產品設定中,標記為「入場券」的產商品出產生票。你還可以單獨"
"關閉為每項產品的產生票。"
#: pretix/base/settings.py:1474
msgid "Generate tickets for pending orders"
msgstr ""
msgstr "為訂單產生票"
#: pretix/base/settings.py:1475
msgid ""
"If turned off, ticket downloads are only possible after an order has been "
"marked as paid."
msgstr ""
msgstr "如果關閉,則只有在訂單標記為已付款後,才能下載門票。"
#: pretix/base/settings.py:1486
msgid "Do not issue ticket before email address is validated"
msgstr ""
msgstr "在驗證電子郵件位址前,不要產生票證"
#: pretix/base/settings.py:1487
msgid ""
@@ -8521,7 +8523,7 @@ msgstr ""
#: pretix/base/settings.py:1503
msgid "Low availability threshold"
msgstr ""
msgstr "可用性門檻低"
#: pretix/base/settings.py:1504
msgid ""
@@ -8535,7 +8537,7 @@ msgstr ""
#: pretix/base/settings.py:1518
msgid "Show availability in event overviews"
msgstr ""
msgstr "在活動概覽中顯示可用性"
#: pretix/base/settings.py:1519
msgid ""
@@ -8561,35 +8563,35 @@ msgstr "月日曆"
#: pretix/base/settings.py:1538
msgid "Default overview style"
msgstr ""
msgstr "預設值概覽樣式"
#: pretix/base/settings.py:1544
msgid ""
"If your event series has more than 50 dates in the future, only the month or "
"week calendar can be used."
msgstr ""
msgstr "如果你的事件系列在未來的日期超過 50則,那只能使用月曆或周曆。"
#: pretix/base/settings.py:1553
msgid "Hide all unavailable dates from calendar or list views"
msgstr ""
msgstr "從日曆或列表檢視中隱藏所有不可用的日期"
#: pretix/base/settings.py:1554 pretix/base/settings.py:1565
msgid ""
"This option currently only affects the calendar of this event series, not "
"the organizer-wide calendar."
msgstr ""
msgstr "此當前選項只影響此事件系列的日曆,而不會影響召集人範圍的日曆。"
#: pretix/base/settings.py:1564
msgid "Hide all past dates from calendar"
msgstr ""
msgstr "從日曆中隱藏所有過去的日期"
#: pretix/base/settings.py:1575
msgid "Allow customers to modify their information after they checked in."
msgstr ""
msgstr "允許客戶在辦理登機手續後修改其資訊。"
#: pretix/base/settings.py:1584
msgid "Last date of modifications"
msgstr ""
msgstr "最後修改日期"
#: pretix/base/settings.py:1585
msgid ""
@@ -8603,43 +8605,43 @@ msgstr ""
#: pretix/base/settings.py:1596
msgid "Customers can change the variation of the products they purchased"
msgstr ""
msgstr "客戶可以更改他們購買的產品的類別"
#: pretix/base/settings.py:1605
msgid "Customers can change their selected add-on products"
msgstr ""
msgstr "客戶可以更改其所選的附加產品"
#: pretix/base/settings.py:1615 pretix/base/settings.py:1624
msgid ""
"Only allow changes if the resulting price is higher or equal than the "
"previous price."
msgstr ""
msgstr "只當成最後價格高於或等於上一項價格時,才允許更改。"
#: pretix/base/settings.py:1616 pretix/base/settings.py:1625
msgid ""
"Only allow changes if the resulting price is higher than the previous price."
msgstr ""
msgstr "僅當最後價格高於之前的價格時,才允許更改。"
#: pretix/base/settings.py:1617 pretix/base/settings.py:1626
msgid ""
"Only allow changes if the resulting price is equal to the previous price."
msgstr ""
msgstr "只有當最後價格等於之前的價格時,才允許更改。"
#: pretix/base/settings.py:1618 pretix/base/settings.py:1627
msgid "Allow changes regardless of price, even if this results in a refund."
msgstr ""
msgstr "允許更改,無論價格如何,即使這會導致退款。"
#: pretix/base/settings.py:1622
msgid "Requirement for changed prices"
msgstr ""
msgstr "更改價格的要求"
#: pretix/base/settings.py:1638
msgid "Do not allow changes after"
msgstr ""
msgstr "不允許在事後進行更改"
#: pretix/base/settings.py:1647
msgid "Allow change even though the ticket has already been checked in"
msgstr ""
msgstr "即使機票已登記,也允許更改"
#: pretix/base/settings.py:1648
msgid ""
@@ -8655,7 +8657,7 @@ msgstr ""
#: pretix/base/settings.py:1660
msgid "Allow individual attendees to change their ticket"
msgstr ""
msgstr "允許個人出席者更改其門票"
#: pretix/base/settings.py:1661
msgid ""
@@ -8671,11 +8673,11 @@ msgstr ""
#: pretix/base/settings.py:1673
msgid "Customers can cancel their unpaid orders"
msgstr ""
msgstr "客戶可以取消未付款的訂單"
#: pretix/base/settings.py:1685
msgid "Charge a fixed cancellation fee"
msgstr ""
msgstr "收取固定取消費用"
#: pretix/base/settings.py:1686 pretix/base/settings.py:1697
#: pretix/base/settings.py:1711
@@ -8689,57 +8691,58 @@ msgstr ""
#: pretix/base/settings.py:1696
msgid "Charge payment, shipping and service fees"
msgstr ""
msgstr "收取付款、運費和服務費"
#: pretix/base/settings.py:1710
msgid "Charge a percentual cancellation fee"
msgstr ""
msgstr "收取百分比取消費用"
#: pretix/base/settings.py:1721 pretix/base/settings.py:1860
msgid "Do not allow cancellations after"
msgstr ""
msgstr "不允許事後取消"
#: pretix/base/settings.py:1730
msgid "Customers can cancel their paid orders"
msgstr ""
msgstr "客戶可以取消已付的訂單"
#: pretix/base/settings.py:1731
msgid ""
"Paid money will be automatically paid back if the payment method allows it. "
"Otherwise, a manual refund will be created for you to process manually."
msgstr ""
msgstr "如果付款方式允許,已支付的款項將自動退還。否則,我們將建立手動退款,供你手動"
"處理。"
#: pretix/base/settings.py:1744 pretix/control/forms/orders.py:822
msgid "Keep a fixed cancellation fee"
msgstr ""
msgstr "保留固定的取消費用"
#: pretix/base/settings.py:1753
msgid "Keep payment, shipping and service fees"
msgstr ""
msgstr "保留付款、運費和服務費"
#: pretix/base/settings.py:1765 pretix/control/forms/orders.py:833
msgid "Keep a percentual cancellation fee"
msgstr ""
msgstr "保留百分比取消費用"
#: pretix/base/settings.py:1774
msgid "Allow customers to voluntarily choose a lower refund"
msgstr ""
msgstr "允許客戶自願選擇較低的退款"
#: pretix/base/settings.py:1775
msgid ""
"With this option enabled, your customers can choose to get a smaller refund "
"to support you."
msgstr ""
msgstr "啟用此選項後,你的客戶可以選擇,拿更少的退款來幫助你。"
#: pretix/base/settings.py:1780
msgid ""
"However, if you want us to help keep the lights on here, please consider "
"using the slider below to request a smaller refund. Thank you!"
msgstr ""
msgstr "但是,如果你希望我們繼續營運,請考慮使用下面的滑動條申請較少的退款。謝謝!"
#: pretix/base/settings.py:1787
msgid "Voluntary lower refund explanation"
msgstr ""
msgstr "自願退款說明"
#: pretix/base/settings.py:1790
msgid ""
@@ -8753,42 +8756,43 @@ msgstr ""
#: pretix/base/settings.py:1805
msgid "Step size for reduction amount"
msgstr ""
msgstr "減少步長"
#: pretix/base/settings.py:1806
msgid ""
"By default, customers can choose an arbitrary amount for you to keep. If you "
"set this to e.g. 10, they will only be able to choose values in increments "
"of 10."
msgstr ""
msgstr "預設值情況下,客戶可以選擇任意金額讓你保留。如果你將其設定為例如 10,"
"客戶只能以 10 為增量的選擇值。"
#: pretix/base/settings.py:1816
msgid ""
"Customers can only request a cancellation that needs to be approved by the "
"event organizer before the order is canceled and a refund is issued."
msgstr ""
msgstr "客戶只能在取消訂單及退款前,請求取消需要活動召集人批准。"
#: pretix/base/settings.py:1826
msgid ""
"Do not show the cancellation fee to users when they request cancellation."
msgstr ""
msgstr "當使用者請求取消時,請勿向使用者顯示其取消費用。"
#: pretix/base/settings.py:1835 pretix/base/settings.py:1845
msgid "All refunds are issued to the original payment method"
msgstr ""
msgstr "所有退款均以原始付款方式退還"
#: pretix/base/settings.py:1836 pretix/base/settings.py:1846
msgid ""
"Customers can choose between a gift card and a refund to their payment method"
msgstr ""
msgstr "客戶可以選擇禮品卡或退款為他們的支付方式"
#: pretix/base/settings.py:1837 pretix/base/settings.py:1847
msgid "All refunds are issued as gift cards"
msgstr ""
msgstr "所有退款均以禮品卡形式退還"
#: pretix/base/settings.py:1838 pretix/base/settings.py:1848
msgid "Do not handle refunds automatically at all"
msgstr ""
msgstr "全然不自動處理退款"
#: pretix/base/settings.py:1843
#: pretix/control/templates/pretixcontrol/order/index.html:141
@@ -8801,7 +8805,7 @@ msgstr "聯絡地址"
#: pretix/base/settings.py:1870 pretix/control/forms/event.py:1577
msgid "We'll show this publicly to allow attendees to contact you."
msgstr ""
msgstr "我們將公開顯示此資訊,以便出席者與你聯繫。"
#: pretix/base/settings.py:1878 pretix/control/forms/event.py:1569
msgid "Imprint URL"
@@ -8811,44 +8815,44 @@ msgstr "版本說明網址"
msgid ""
"This should point e.g. to a part of your website that has your contact "
"details and legal information."
msgstr ""
msgstr "例如,這應該導向你網站上,包括你部分的聯絡方式及法律資訊的。"
#: pretix/base/settings.py:1889
msgid "Privacy Policy URL"
msgstr ""
msgstr "隱私政策網址"
#: pretix/base/settings.py:1890
msgid ""
"This should point e.g. to a part of your website that explains how you use "
"data gathered in your ticket shop."
msgstr ""
msgstr "例如,這應該導向你網站的一部分,該部分解釋了你如何使用在售票處收集的數據。"
#: pretix/base/settings.py:1912
msgid "Attach ticket files"
msgstr ""
msgstr "附加票證檔"
#: pretix/base/settings.py:1914
#, python-brace-format
msgid ""
"Tickets will never be attached if they're larger than {size} to avoid email "
"delivery problems."
msgstr ""
msgstr "如果票證大於 {size},則永遠不會附加票證,以避免電子郵件傳送問題。"
#: pretix/base/settings.py:1925 pretix/plugins/sendmail/forms.py:222
#: pretix/plugins/sendmail/models.py:215
#: pretix/plugins/sendmail/templates/pretixplugins/sendmail/send_form.html:66
msgid "Attach calendar files"
msgstr ""
msgstr "附加日曆檔"
#: pretix/base/settings.py:1926
msgid ""
"If enabled, we will attach an .ics calendar file to order confirmation "
"emails."
msgstr ""
msgstr "如果啟用,我們將在訂單中確認電子郵件附加的一項.ics日曆檔。"
#: pretix/base/settings.py:1935
msgid "Attach calendar files only after order has been paid"
msgstr ""
msgstr "只有在訂單付款後附加日曆檔"
#: pretix/base/settings.py:1936
msgid ""
@@ -8883,7 +8887,8 @@ msgstr "主題前綴"
msgid ""
"This will be prepended to the subject of all outgoing emails, formatted as "
"[prefix]. Choose, for example, a short form of your event name."
msgstr ""
msgstr "這將附加到所有傳出電子郵件的主題前面,格式為 "
"[前綴]。例如,選擇事件名稱的縮寫形式。"
#: pretix/base/settings.py:1976 pretix/control/forms/mailsetup.py:35
#: pretix/control/forms/mailsetup.py:108
@@ -8895,7 +8900,7 @@ msgstr "寄件者位址"
#: pretix/base/settings.py:1977 pretix/control/forms/mailsetup.py:36
#: pretix/control/forms/mailsetup.py:109
msgid "Sender address for outgoing emails"
msgstr ""
msgstr "傳出電子郵件的寄件者位址"
#: pretix/base/settings.py:1986
msgid "Sender name"
@@ -8905,20 +8910,20 @@ msgstr "寄件者姓名"
msgid ""
"Sender name used in conjunction with the sender address for outgoing emails. "
"Defaults to your event name."
msgstr ""
msgstr "與傳出電子郵件的寄件者位址一起使用的寄件者姓名。預設值為事件名稱。"
#: pretix/base/settings.py:2005 pretix/base/settings.py:2062
#: pretix/base/settings.py:2079 pretix/base/settings.py:2097
#, python-brace-format
msgid "Your order: {code}"
msgstr ""
msgstr "你的訂單: {code}"
#: pretix/base/settings.py:2009 pretix/base/settings.py:2042
#: pretix/base/settings.py:2144 pretix/base/settings.py:2359
#: pretix/base/settings.py:2396
#, python-brace-format
msgid "Your event registration: {code}"
msgstr ""
msgstr "你的活動註冊:{code}"
#: pretix/base/settings.py:2013
#, python-brace-format
@@ -10043,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"
@@ -10065,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"
@@ -18780,15 +18786,13 @@ msgid "Mark as done"
msgstr ""
#: pretix/control/templates/pretixcontrol/order/refund_process.html:22
#, fuzzy, python-format
#, python-format
msgid ""
"We received notice that <strong>%(amount)s</strong> have been refunded via "
"<strong>%(method)s</strong>. If this refund is processed, the order will be "
"underpaid by <strong>%(pending)s</strong>. The order total is "
"<strong>%(total)s</strong>."
msgstr ""
"我們收到通知, <strong>%(amount)s</strong> 退款。如果處理此退款,訂單將少付 "
"<strong>%(pending)s</strong>。訂單總數為 <strong>%(total)s</strong>"
#: pretix/control/templates/pretixcontrol/order/refund_process.html:30
msgid "Since the order is already canceled, this will not affect its state."
@@ -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
+1 -2
View File
@@ -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
+22 -30
View File
@@ -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 (
+3
View File
@@ -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:
+2 -2
View File
@@ -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',
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+3 -3
View File
@@ -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,
+1 -2
View File
@@ -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
+5 -2
View File
@@ -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:
+1 -2
View File
@@ -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,
+2 -3
View File
@@ -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
+1 -1
View File
@@ -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
+10 -2
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -2
View File
@@ -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(
+1 -1
View File
@@ -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 (
+1 -1
View File
@@ -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
+2 -1
View File
@@ -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):
+2 -1
View File
@@ -19,7 +19,6 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
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):
"""
+4 -5
View File
@@ -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)

Some files were not shown because too many files have changed in this diff Show More