mirror of
https://github.com/pretix/pretix.git
synced 2026-08-05 09:57:49 +00:00
Compare commits
42
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1897bd4b26 | ||
|
|
fd6843822b | ||
|
|
ee1644e037 | ||
|
|
a6c1486650 | ||
|
|
f4b437e92b | ||
|
|
446c55dc89 | ||
|
|
0990eeeea0 | ||
|
|
591fe23a99 | ||
|
|
ad70765287 | ||
|
|
c59d29493c | ||
|
|
bd32b33ba9 | ||
|
|
3a8556bb78 | ||
|
|
c972d24ce7 | ||
|
|
647e68ef01 | ||
|
|
f439a591df | ||
|
|
8f17b338d1 | ||
|
|
35350a13d6 | ||
|
|
0d93f7f52f | ||
|
|
170dcf93e7 | ||
|
|
9319202213 | ||
|
|
bfd0eee2c1 | ||
|
|
8570f53ed0 | ||
|
|
f56f6dd628 | ||
|
|
413fabd821 | ||
|
|
9813e59210 | ||
|
|
d91d942eac | ||
|
|
22104f79bd | ||
|
|
f289ad9e4f | ||
|
|
f81a734716 | ||
|
|
7a27a42e79 | ||
|
|
65a2bab9bb | ||
|
|
a26f46b619 | ||
|
|
5c37c85415 | ||
|
|
8ddba36690 | ||
|
|
f9bf05e09b | ||
|
|
8471422bba | ||
|
|
ee9acebe03 | ||
|
|
35d2a73f75 | ||
|
|
eb3eca45b5 | ||
|
|
f7816924b0 | ||
|
|
12c3fef390 | ||
|
|
8e39aaa292 |
@@ -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
|
||||
|
||||
@@ -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
@@ -3,7 +3,6 @@ FROM python:3.11-bullseye
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
libmariadb-dev \
|
||||
gettext \
|
||||
git \
|
||||
libffi-dev \
|
||||
@@ -58,7 +57,7 @@ RUN pip3 install -U \
|
||||
wheel && \
|
||||
cd /pretix && \
|
||||
PRETIX_DOCKER_BUILD=TRUE pip3 install \
|
||||
-e ".[memcached,mysql]" \
|
||||
-e ".[memcached]" \
|
||||
gunicorn django-extensions ipython && \
|
||||
rm -rf ~/.cache/pip
|
||||
|
||||
|
||||
@@ -154,23 +154,15 @@ Example::
|
||||
port=3306
|
||||
|
||||
``backend``
|
||||
One of ``mysql`` (deprecated), ``sqlite3`` and ``postgresql``.
|
||||
One of ``sqlite3`` and ``postgresql``.
|
||||
Default: ``sqlite3``.
|
||||
|
||||
If you use MySQL, be sure to create your database using
|
||||
``CREATE DATABASE <dbname> CHARACTER SET utf8;``. Otherwise, Unicode
|
||||
support will not properly work.
|
||||
|
||||
``name``
|
||||
The database's name. Default: ``db.sqlite3``.
|
||||
|
||||
``user``, ``password``, ``host``, ``port``
|
||||
Connection details for the database connection. Empty by default.
|
||||
|
||||
``galera``
|
||||
(Deprecated) Indicates if the database backend is a MySQL/MariaDB Galera cluster and
|
||||
turns on some optimizations/special case handlers. Default: ``False``
|
||||
|
||||
.. _`config-replica`:
|
||||
|
||||
Database replica settings
|
||||
|
||||
@@ -16,14 +16,11 @@ To use pretix, you will need the following things:
|
||||
* A periodic task runner, e.g. ``cron``
|
||||
|
||||
* **A database**. This needs to be a SQL-based that is supported by Django. We highly recommend to either
|
||||
go for **PostgreSQL** or **MySQL/MariaDB**. If you do not provide one, pretix will run on SQLite, which is useful
|
||||
go for **PostgreSQL**. If you do not provide one, pretix will run on SQLite, which is useful
|
||||
for evaluation and development purposes.
|
||||
|
||||
.. warning:: Do not ever use SQLite in production. It will break.
|
||||
|
||||
.. warning:: We recommend **PostgreSQL**. If you go for MySQL, make sure you run **MySQL 5.7 or newer** or
|
||||
**MariaDB 10.2.7 or newer**.
|
||||
|
||||
* A **reverse proxy**. pretix needs to deliver some static content to your users (e.g. CSS, images, ...). While pretix
|
||||
is capable of doing this, having this handled by a proper web server like **nginx** or **Apache** will be much
|
||||
faster. Also, you need a proxying web server in front to provide SSL encryption.
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
Migrating from MySQL/MariaDB to PostgreSQL
|
||||
==========================================
|
||||
|
||||
Our recommended database for all production installations is PostgreSQL. Support for MySQL/MariaDB will be removed in
|
||||
pretix 5.0.
|
||||
Our recommended database for all production installations is PostgreSQL. Support for MySQL/MariaDB has been removed
|
||||
in newer pretix releases.
|
||||
|
||||
In order to follow this guide, your pretix installation needs to be a version that fully supports MySQL/MariaDB. If you
|
||||
already upgraded to pretix 5.0, downgrade back to the last 4.x release using ``pip``.
|
||||
already upgraded to pretix 5.0 or later, downgrade back to the last 4.x release using ``pip``.
|
||||
|
||||
.. note:: We have tested this guide carefully, but we can't assume any liability for its correctness. The data loss
|
||||
risk should be low as long as pretix is not running while you do the migration. If you are a pretix Enterprise
|
||||
|
||||
@@ -18,6 +18,7 @@ at :ref:`plugin-docs`.
|
||||
item_variations
|
||||
item_bundles
|
||||
item_add-ons
|
||||
item_meta_properties
|
||||
questions
|
||||
question_options
|
||||
quotas
|
||||
|
||||
@@ -18,13 +18,13 @@ If you want to add a custom view to the control area of an event, just register
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from django.conf.urls import url
|
||||
from django.urls import re_path
|
||||
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/mypluginname/',
|
||||
views.admin_view, name='backend'),
|
||||
re_path(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/mypluginname/',
|
||||
views.admin_view, name='backend'),
|
||||
]
|
||||
|
||||
It is required that your URL parameters are called ``organizer`` and ``event``. If you want to
|
||||
|
||||
@@ -35,13 +35,13 @@ automatically and should be provided by any plugin that provides any view.
|
||||
A very basic example that provides one view in the admin panel and one view in the frontend
|
||||
could look like this::
|
||||
|
||||
from django.conf.urls import url
|
||||
from django.urls import re_path
|
||||
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/mypluginname/',
|
||||
views.AdminView.as_view(), name='backend'),
|
||||
re_path(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/mypluginname/',
|
||||
views.AdminView.as_view(), name='backend'),
|
||||
]
|
||||
|
||||
event_patterns = [
|
||||
|
||||
+7
-8
@@ -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",
|
||||
]
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 = (
|
||||
|
||||
@@ -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'])
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -23,9 +23,9 @@ import datetime
|
||||
import mimetypes
|
||||
import os
|
||||
from decimal import Decimal
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import django_filters
|
||||
import pytz
|
||||
from django.db import transaction
|
||||
from django.db.models import (
|
||||
Exists, F, OuterRef, Prefetch, Q, Subquery, prefetch_related_objects,
|
||||
@@ -612,7 +612,7 @@ class OrderViewSet(viewsets.ModelViewSet):
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
tz = pytz.timezone(self.request.event.settings.timezone)
|
||||
tz = ZoneInfo(self.request.event.settings.timezone)
|
||||
new_date = make_aware(datetime.datetime.combine(
|
||||
new_date,
|
||||
datetime.time(hour=23, minute=59, second=59)
|
||||
|
||||
@@ -37,8 +37,8 @@ import tempfile
|
||||
from collections import OrderedDict, namedtuple
|
||||
from decimal import Decimal
|
||||
from typing import Optional, Tuple
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import pytz
|
||||
from defusedcsv import csv
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
@@ -68,7 +68,7 @@ class BaseExporter:
|
||||
self.events = event
|
||||
self.event = None
|
||||
e = self.events.first()
|
||||
self.timezone = e.timezone if e else pytz.timezone(settings.TIME_ZONE)
|
||||
self.timezone = e.timezone if e else ZoneInfo(settings.TIME_ZONE)
|
||||
else:
|
||||
self.events = Event.objects.filter(pk=event.pk)
|
||||
self.timezone = event.timezone
|
||||
|
||||
@@ -34,8 +34,8 @@
|
||||
|
||||
from collections import OrderedDict
|
||||
from decimal import Decimal
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import pytz
|
||||
from django import forms
|
||||
from django.db.models import (
|
||||
Case, CharField, Count, DateTimeField, F, IntegerField, Max, Min, OuterRef,
|
||||
@@ -326,7 +326,7 @@ class OrderListExporter(MultiSheetListExporter):
|
||||
|
||||
yield self.ProgressSetTotal(total=qs.count())
|
||||
for order in qs.order_by('datetime').iterator():
|
||||
tz = pytz.timezone(self.event_object_cache[order.event_id].settings.timezone)
|
||||
tz = ZoneInfo(self.event_object_cache[order.event_id].settings.timezone)
|
||||
|
||||
row = [
|
||||
self.event_object_cache[order.event_id].slug,
|
||||
@@ -459,7 +459,7 @@ class OrderListExporter(MultiSheetListExporter):
|
||||
yield self.ProgressSetTotal(total=qs.count())
|
||||
for op in qs.order_by('order__datetime').iterator():
|
||||
order = op.order
|
||||
tz = pytz.timezone(order.event.settings.timezone)
|
||||
tz = ZoneInfo(order.event.settings.timezone)
|
||||
row = [
|
||||
self.event_object_cache[order.event_id].slug,
|
||||
order.code,
|
||||
@@ -631,7 +631,7 @@ class OrderListExporter(MultiSheetListExporter):
|
||||
|
||||
for op in ops:
|
||||
order = op.order
|
||||
tz = pytz.timezone(self.event_object_cache[order.event_id].settings.timezone)
|
||||
tz = ZoneInfo(self.event_object_cache[order.event_id].settings.timezone)
|
||||
row = [
|
||||
self.event_object_cache[order.event_id].slug,
|
||||
order.code,
|
||||
@@ -1024,7 +1024,7 @@ class PaymentListExporter(ListExporter):
|
||||
|
||||
yield self.ProgressSetTotal(total=len(objs))
|
||||
for obj in objs:
|
||||
tz = pytz.timezone(obj.order.event.settings.timezone)
|
||||
tz = ZoneInfo(obj.order.event.settings.timezone)
|
||||
if isinstance(obj, OrderPayment) and obj.payment_date:
|
||||
d2 = obj.payment_date.astimezone(tz).date().strftime('%Y-%m-%d')
|
||||
elif isinstance(obj, OrderRefund) and obj.execution_date:
|
||||
@@ -1203,7 +1203,7 @@ class GiftcardRedemptionListExporter(ListExporter):
|
||||
yield headers
|
||||
|
||||
for obj in objs:
|
||||
tz = pytz.timezone(obj.order.event.settings.timezone)
|
||||
tz = ZoneInfo(obj.order.event.settings.timezone)
|
||||
gc = GiftCard.objects.get(pk=obj.info_data.get('gift_card'))
|
||||
row = [
|
||||
obj.order.event.slug,
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
from collections import OrderedDict
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import pytz
|
||||
from django import forms
|
||||
from django.db.models import F, Q
|
||||
from django.dispatch import receiver
|
||||
@@ -137,7 +137,7 @@ class WaitingListExporter(ListExporter):
|
||||
|
||||
# which event should be used to output dates in columns "Start date" and "End date"
|
||||
event_for_date_columns = entry.subevent if entry.subevent else entry.event
|
||||
tz = pytz.timezone(entry.event.settings.timezone)
|
||||
tz = ZoneInfo(entry.event.settings.timezone)
|
||||
datetime_format = '%Y-%m-%d %H:%M:%S'
|
||||
|
||||
row = [
|
||||
|
||||
@@ -167,6 +167,7 @@ class SettingsForm(i18nfield.forms.I18nFormMixin, HierarkeyForm):
|
||||
|
||||
class PrefixForm(forms.Form):
|
||||
prefix = forms.CharField(widget=forms.HiddenInput)
|
||||
template_name = "django/forms/table.html"
|
||||
|
||||
|
||||
class SafeSessionWizardView(SessionWizardView):
|
||||
|
||||
@@ -38,10 +38,10 @@ import logging
|
||||
from datetime import timedelta
|
||||
from decimal import Decimal
|
||||
from io import BytesIO
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import dateutil.parser
|
||||
import pycountry
|
||||
import pytz
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
@@ -733,7 +733,7 @@ class BaseQuestionsForm(forms.Form):
|
||||
initial = answers[0]
|
||||
else:
|
||||
initial = None
|
||||
tz = pytz.timezone(event.settings.timezone)
|
||||
tz = ZoneInfo(event.settings.timezone)
|
||||
help_text = rich_text(q.help_text)
|
||||
label = escape(q.question) # django-bootstrap3 calls mark_safe
|
||||
required = q.required and not self.all_optional
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
#
|
||||
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
||||
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
||||
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
||||
# this file, see <https://pretix.eu/about/en/license>.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
from bootstrap3.renderers import (
|
||||
FieldRenderer as BaseFieldRenderer,
|
||||
InlineFieldRenderer as BaseInlineFieldRenderer,
|
||||
)
|
||||
from django.forms import (
|
||||
CheckboxInput, CheckboxSelectMultiple, ClearableFileInput, RadioSelect,
|
||||
SelectDateWidget,
|
||||
)
|
||||
|
||||
|
||||
class FieldRenderer(BaseFieldRenderer):
|
||||
# Local application of https://github.com/zostera/django-bootstrap3/pull/859
|
||||
|
||||
def post_widget_render(self, html):
|
||||
if isinstance(self.widget, CheckboxSelectMultiple):
|
||||
html = self.list_to_class(html, "checkbox")
|
||||
elif isinstance(self.widget, RadioSelect):
|
||||
html = self.list_to_class(html, "radio")
|
||||
elif isinstance(self.widget, SelectDateWidget):
|
||||
html = self.fix_date_select_input(html)
|
||||
elif isinstance(self.widget, ClearableFileInput):
|
||||
html = self.fix_clearable_file_input(html)
|
||||
elif isinstance(self.widget, CheckboxInput):
|
||||
html = self.put_inside_label(html)
|
||||
return html
|
||||
|
||||
|
||||
class InlineFieldRenderer(BaseInlineFieldRenderer):
|
||||
# Local application of https://github.com/zostera/django-bootstrap3/pull/859
|
||||
|
||||
def post_widget_render(self, html):
|
||||
if isinstance(self.widget, CheckboxSelectMultiple):
|
||||
html = self.list_to_class(html, "checkbox")
|
||||
elif isinstance(self.widget, RadioSelect):
|
||||
html = self.list_to_class(html, "radio")
|
||||
elif isinstance(self.widget, SelectDateWidget):
|
||||
html = self.fix_date_select_input(html)
|
||||
elif isinstance(self.widget, ClearableFileInput):
|
||||
html = self.fix_clearable_file_input(html)
|
||||
elif isinstance(self.widget, CheckboxInput):
|
||||
html = self.put_inside_label(html)
|
||||
return html
|
||||
@@ -24,7 +24,7 @@ Django, for theoretically very valid reasons, creates migrations for *every sing
|
||||
we change on a model. Even the `help_text`! This makes sense, as we don't know if any
|
||||
database backend unknown to us might actually use this information for its database schema.
|
||||
|
||||
However, pretix only supports PostgreSQL, MySQL, MariaDB and SQLite and we can be pretty
|
||||
However, pretix only supports PostgreSQL and SQLite and we can be pretty
|
||||
certain that some changes to models will never require a change to the database. In this case,
|
||||
not creating a migration for certain changes will save us some performance while applying them
|
||||
*and* allow for a cleaner git history. Win-win!
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
import json
|
||||
import sys
|
||||
|
||||
import pytz
|
||||
import pytz_deprecation_shim
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.utils.timezone import override
|
||||
from django_scopes import scope
|
||||
@@ -60,7 +60,7 @@ class Command(BaseCommand):
|
||||
sys.exit(1)
|
||||
|
||||
locale = options.get("locale", None)
|
||||
timezone = pytz.timezone(options['timezone']) if options.get('timezone') else None
|
||||
timezone = pytz_deprecation_shim.timezone(options['timezone']) if options.get('timezone') else None
|
||||
|
||||
with scope(organizer=o):
|
||||
if options['event_slug']:
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
#
|
||||
from collections import OrderedDict
|
||||
from urllib.parse import urlsplit
|
||||
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
||||
|
||||
import pytz
|
||||
from django.conf import settings
|
||||
from django.http import Http404, HttpRequest, HttpResponse
|
||||
from django.middleware.common import CommonMiddleware
|
||||
@@ -98,9 +98,9 @@ class LocaleMiddleware(MiddlewareMixin):
|
||||
tzname = request.user.timezone
|
||||
if tzname:
|
||||
try:
|
||||
timezone.activate(pytz.timezone(tzname))
|
||||
timezone.activate(ZoneInfo(tzname))
|
||||
request.timezone = tzname
|
||||
except pytz.UnknownTimeZoneError:
|
||||
except ZoneInfoNotFoundError:
|
||||
pass
|
||||
else:
|
||||
timezone.deactivate()
|
||||
|
||||
+3
-1
@@ -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 {}
|
||||
}
|
||||
),
|
||||
]
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -121,14 +121,23 @@ class Customer(LoggedModel):
|
||||
if self.email:
|
||||
self.email = self.email.lower()
|
||||
if 'update_fields' in kwargs and 'last_modified' not in kwargs['update_fields']:
|
||||
kwargs['update_fields'] = list(kwargs['update_fields']) + ['last_modified']
|
||||
kwargs['update_fields'] = {'last_modified'}.union(kwargs['update_fields'])
|
||||
if not self.identifier:
|
||||
self.assign_identifier()
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'identifier'}.union(kwargs['update_fields'])
|
||||
if self.name_parts:
|
||||
self.name_cached = self.name
|
||||
name = self.name
|
||||
if self.name_cached != name:
|
||||
self.name_cached = name
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'name_cached'}.union(kwargs['update_fields'])
|
||||
else:
|
||||
self.name_cached = ""
|
||||
self.name_parts = {}
|
||||
if self.name_cached != "" or self.name_parts != {}:
|
||||
self.name_cached = ""
|
||||
self.name_parts = {}
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'name_cached', 'name_parts'}.union(kwargs['update_fields'])
|
||||
super().save(**kwargs)
|
||||
|
||||
def anonymize(self):
|
||||
|
||||
@@ -98,6 +98,8 @@ class Gate(LoggedModel):
|
||||
if not Gate.objects.filter(organizer=self.organizer, identifier=code).exists():
|
||||
self.identifier = code
|
||||
break
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'identifier'}.union(kwargs['update_fields'])
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
|
||||
@@ -173,6 +175,8 @@ class Device(LoggedModel):
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.device_id:
|
||||
self.device_id = (self.organizer.devices.aggregate(m=Max('device_id'))['m'] or 0) + 1
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'device_id'}.union(kwargs['update_fields'])
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def permission_set(self) -> set:
|
||||
|
||||
@@ -40,8 +40,9 @@ from collections import Counter, OrderedDict, defaultdict
|
||||
from datetime import datetime, time, timedelta
|
||||
from operator import attrgetter
|
||||
from urllib.parse import urljoin
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import pytz
|
||||
import pytz_deprecation_shim
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.files.storage import default_storage
|
||||
@@ -214,7 +215,7 @@ class EventMixin:
|
||||
|
||||
@property
|
||||
def timezone(self):
|
||||
return pytz.timezone(self.settings.timezone)
|
||||
return pytz_deprecation_shim.timezone(self.settings.timezone)
|
||||
|
||||
@property
|
||||
def effective_presale_end(self):
|
||||
@@ -773,7 +774,7 @@ class Event(EventMixin, LoggedModel):
|
||||
"""
|
||||
The last datetime of payments for this event.
|
||||
"""
|
||||
tz = pytz.timezone(self.settings.timezone)
|
||||
tz = ZoneInfo(self.settings.timezone)
|
||||
return make_aware(datetime.combine(
|
||||
self.settings.get('payment_term_last', as_type=RelativeDateWrapper).datetime(self).date(),
|
||||
time(hour=23, minute=59, second=59)
|
||||
|
||||
@@ -19,10 +19,11 @@
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
import zoneinfo
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import pytz
|
||||
from dateutil.rrule import rrulestr
|
||||
from dateutil.tz import datetime_exists
|
||||
from django.conf import settings
|
||||
from django.core.serializers.json import DjangoJSONEncoder
|
||||
from django.db import models
|
||||
@@ -108,12 +109,9 @@ class AbstractScheduledExport(LoggedModel):
|
||||
self.schedule_next_run = None
|
||||
return
|
||||
|
||||
try:
|
||||
self.schedule_next_run = make_aware(datetime.combine(new_d.date(), self.schedule_rrule_time), tz)
|
||||
except pytz.exceptions.AmbiguousTimeError:
|
||||
self.schedule_next_run = make_aware(datetime.combine(new_d.date(), self.schedule_rrule_time), tz, is_dst=False)
|
||||
except pytz.exceptions.NonExistentTimeError:
|
||||
self.schedule_next_run = make_aware(datetime.combine(new_d.date(), self.schedule_rrule_time) + timedelta(hours=1), tz)
|
||||
self.schedule_next_run = make_aware(datetime.combine(new_d.date(), self.schedule_rrule_time), tz)
|
||||
if not datetime_exists(self.schedule_next_run):
|
||||
self.schedule_next_run += timedelta(hours=1)
|
||||
|
||||
|
||||
class ScheduledEventExport(AbstractScheduledExport):
|
||||
@@ -136,4 +134,4 @@ class ScheduledOrganizerExport(AbstractScheduledExport):
|
||||
|
||||
@property
|
||||
def tz(self):
|
||||
return pytz.timezone(self.timezone)
|
||||
return zoneinfo.ZoneInfo(self.timezone)
|
||||
|
||||
@@ -251,14 +251,20 @@ class Invoice(models.Model):
|
||||
raise ValueError('Every invoice needs to be connected to an order')
|
||||
if not self.event:
|
||||
self.event = self.order.event
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'event'}.union(kwargs['update_fields'])
|
||||
if not self.organizer:
|
||||
self.organizer = self.order.event.organizer
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'organizer'}.union(kwargs['update_fields'])
|
||||
if not self.prefix:
|
||||
self.prefix = self.event.settings.invoice_numbers_prefix or (self.event.slug.upper() + '-')
|
||||
if self.is_cancellation:
|
||||
self.prefix = self.event.settings.invoice_numbers_prefix_cancellations or self.prefix
|
||||
if '%' in self.prefix:
|
||||
self.prefix = self.date.strftime(self.prefix)
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'prefix'}.union(kwargs['update_fields'])
|
||||
|
||||
if not self.invoice_no:
|
||||
if self.order.testmode:
|
||||
@@ -276,8 +282,13 @@ class Invoice(models.Model):
|
||||
# Suppress duplicate key errors and try again
|
||||
if i == 9:
|
||||
raise
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'invoice_no'}.union(kwargs['update_fields'])
|
||||
|
||||
self.full_invoice_no = self.prefix + self.invoice_no
|
||||
if self.full_invoice_no != self.prefix + self.invoice_no:
|
||||
self.full_invoice_no = self.prefix + self.invoice_no
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'full_invoice_no'}.union(kwargs['update_fields'])
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
|
||||
@@ -40,9 +40,10 @@ from collections import Counter, OrderedDict
|
||||
from datetime import date, datetime, time, timedelta
|
||||
from decimal import Decimal, DecimalException
|
||||
from typing import Optional, Tuple
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import dateutil.parser
|
||||
import pytz
|
||||
from dateutil.tz import datetime_exists
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import (
|
||||
@@ -927,22 +928,22 @@ class Item(LoggedModel):
|
||||
)
|
||||
if self.validity_dynamic_duration_days:
|
||||
replace_date += timedelta(days=self.validity_dynamic_duration_days)
|
||||
valid_until = tz.localize(valid_until.replace(
|
||||
valid_until = valid_until.replace(
|
||||
year=replace_date.year,
|
||||
month=replace_date.month,
|
||||
day=replace_date.day,
|
||||
hour=23, minute=59, second=59, microsecond=0,
|
||||
tzinfo=None,
|
||||
))
|
||||
tzinfo=tz,
|
||||
)
|
||||
elif self.validity_dynamic_duration_days:
|
||||
replace_date = valid_until.date() + timedelta(days=self.validity_dynamic_duration_days - 1)
|
||||
valid_until = tz.localize(valid_until.replace(
|
||||
valid_until = valid_until.replace(
|
||||
year=replace_date.year,
|
||||
month=replace_date.month,
|
||||
day=replace_date.day,
|
||||
hour=23, minute=59, second=59, microsecond=0,
|
||||
tzinfo=None
|
||||
))
|
||||
tzinfo=tz
|
||||
)
|
||||
|
||||
if self.validity_dynamic_duration_hours:
|
||||
valid_until += timedelta(hours=self.validity_dynamic_duration_hours)
|
||||
@@ -950,6 +951,9 @@ class Item(LoggedModel):
|
||||
if self.validity_dynamic_duration_minutes:
|
||||
valid_until += timedelta(minutes=self.validity_dynamic_duration_minutes)
|
||||
|
||||
if not datetime_exists(valid_until):
|
||||
valid_until += timedelta(hours=1)
|
||||
|
||||
return requested_start, valid_until
|
||||
|
||||
else:
|
||||
@@ -1589,6 +1593,8 @@ class Question(LoggedModel):
|
||||
if not Question.objects.filter(event=self.event, identifier=code).exists():
|
||||
self.identifier = code
|
||||
break
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'identifier'}.union(kwargs['update_fields'])
|
||||
super().save(*args, **kwargs)
|
||||
if self.event:
|
||||
self.event.cache.clear()
|
||||
@@ -1678,7 +1684,7 @@ class Question(LoggedModel):
|
||||
try:
|
||||
dt = dateutil.parser.parse(answer)
|
||||
if is_naive(dt):
|
||||
dt = make_aware(dt, pytz.timezone(self.event.settings.timezone))
|
||||
dt = make_aware(dt, ZoneInfo(self.event.settings.timezone))
|
||||
except:
|
||||
raise ValidationError(_('Invalid datetime input.'))
|
||||
else:
|
||||
@@ -1736,6 +1742,8 @@ class QuestionOption(models.Model):
|
||||
if not QuestionOption.objects.filter(question__event=self.question.event, identifier=code).exists():
|
||||
self.identifier = code
|
||||
break
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'identifier'}.union(kwargs['update_fields'])
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -42,10 +42,10 @@ from collections import Counter
|
||||
from datetime import datetime, time, timedelta
|
||||
from decimal import Decimal
|
||||
from typing import Any, Dict, List, Union
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import dateutil
|
||||
import pycountry
|
||||
import pytz
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models, transaction
|
||||
@@ -461,14 +461,20 @@ class Order(LockModel, LoggedModel):
|
||||
return '{event}-{code}'.format(event=self.event.slug.upper(), code=self.code)
|
||||
|
||||
def save(self, **kwargs):
|
||||
if 'update_fields' in kwargs and 'last_modified' not in kwargs['update_fields']:
|
||||
kwargs['update_fields'] = list(kwargs['update_fields']) + ['last_modified']
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'last_modified'}.union(kwargs['update_fields'])
|
||||
if not self.code:
|
||||
self.assign_code()
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'code'}.union(kwargs['update_fields'])
|
||||
if not self.datetime:
|
||||
self.datetime = now()
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'datetime'}.union(kwargs['update_fields'])
|
||||
if not self.expires:
|
||||
self.set_expires()
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'expires'}.union(kwargs['update_fields'])
|
||||
|
||||
is_new = not self.pk
|
||||
update_fields = kwargs.get('update_fields', [])
|
||||
@@ -496,7 +502,7 @@ class Order(LockModel, LoggedModel):
|
||||
|
||||
def set_expires(self, now_dt=None, subevents=None):
|
||||
now_dt = now_dt or now()
|
||||
tz = pytz.timezone(self.event.settings.timezone)
|
||||
tz = ZoneInfo(self.event.settings.timezone)
|
||||
mode = self.event.settings.get('payment_term_mode')
|
||||
if mode == 'days':
|
||||
exp_by_date = now_dt.astimezone(tz) + timedelta(days=self.event.settings.get('payment_term_days', as_type=int))
|
||||
@@ -870,7 +876,7 @@ class Order(LockModel, LoggedModel):
|
||||
|
||||
@property
|
||||
def payment_term_last(self):
|
||||
tz = pytz.timezone(self.event.settings.timezone)
|
||||
tz = ZoneInfo(self.event.settings.timezone)
|
||||
term_last = self.event.settings.get('payment_term_last', as_type=RelativeDateWrapper)
|
||||
if term_last:
|
||||
if self.event.has_subevents:
|
||||
@@ -1230,7 +1236,7 @@ class QuestionAnswer(models.Model):
|
||||
try:
|
||||
d = dateutil.parser.parse(self.answer)
|
||||
if self.orderposition:
|
||||
tz = pytz.timezone(self.orderposition.order.event.settings.timezone)
|
||||
tz = ZoneInfo(self.orderposition.order.event.settings.timezone)
|
||||
d = d.astimezone(tz)
|
||||
return date_format(d, "SHORT_DATETIME_FORMAT")
|
||||
except ValueError:
|
||||
@@ -1442,12 +1448,20 @@ class AbstractPosition(models.Model):
|
||||
else self.variation.quotas.filter(subevent=self.subevent))
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
update_fields = kwargs.get('update_fields', [])
|
||||
update_fields = kwargs.get('update_fields', set())
|
||||
if 'attendee_name_parts' in update_fields:
|
||||
update_fields.append('attendee_name_cached')
|
||||
self.attendee_name_cached = self.attendee_name
|
||||
kwargs['update_fields'] = {'attendee_name_cached'}.union(kwargs['update_fields'])
|
||||
|
||||
name = self.attendee_name
|
||||
if name != self.attendee_name_cached:
|
||||
self.attendee_name_cached = name
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'attendee_name_cached'}.union(kwargs['update_fields'])
|
||||
|
||||
if self.attendee_name_parts is None:
|
||||
self.attendee_name_parts = {}
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'attendee_name_parts'}.union(kwargs['update_fields'])
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
@property
|
||||
@@ -1827,6 +1841,8 @@ class OrderPayment(models.Model):
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.local_id:
|
||||
self.local_id = (self.order.payments.aggregate(m=Max('local_id'))['m'] or 0) + 1
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'local_id'}.union(kwargs['update_fields'])
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def create_external_refund(self, amount=None, execution_date=None, info='{}'):
|
||||
@@ -2025,6 +2041,8 @@ class OrderRefund(models.Model):
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.local_id:
|
||||
self.local_id = (self.order.refunds.aggregate(m=Max('local_id'))['m'] or 0) + 1
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'local_id'}.union(kwargs['update_fields'])
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
|
||||
@@ -2443,14 +2461,20 @@ class OrderPosition(AbstractPosition):
|
||||
assign_ticket_secret(
|
||||
event=self.order.event, position=self, force_invalidate=True, save=False
|
||||
)
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'secret'}.union(kwargs['update_fields'])
|
||||
|
||||
if not self.blocked:
|
||||
if not self.blocked and self.blocked is not None:
|
||||
self.blocked = None
|
||||
elif not isinstance(self.blocked, list) or any(not isinstance(b, str) for b in self.blocked):
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'blocked'}.union(kwargs['update_fields'])
|
||||
elif self.blocked and (not isinstance(self.blocked, list) or any(not isinstance(b, str) for b in self.blocked)):
|
||||
raise TypeError("blocked needs to be a list of strings")
|
||||
|
||||
if not self.pseudonymization_id:
|
||||
self.assign_pseudonymization_id()
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'pseudonymization_id'}.union(kwargs['update_fields'])
|
||||
|
||||
if not self.get_deferred_fields():
|
||||
if Transaction.key(self) != self.__initial_transaction_key or self.canceled != self.__initial_canceled or not self.pk:
|
||||
@@ -2936,10 +2960,17 @@ class InvoiceAddress(models.Model):
|
||||
self.order.touch()
|
||||
|
||||
if self.name_parts:
|
||||
self.name_cached = self.name
|
||||
name = self.name
|
||||
if self.name_cached != name:
|
||||
self.name_cached = self.name
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'name_cached'}.union(kwargs['update_fields'])
|
||||
else:
|
||||
self.name_cached = ""
|
||||
self.name_parts = {}
|
||||
if self.name_cached != "" or self.name_parts != {}:
|
||||
self.name_cached = ""
|
||||
self.name_parts = {}
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'name_cached', 'name_parts'}.union(kwargs['update_fields'])
|
||||
super().save(**kwargs)
|
||||
|
||||
def describe(self):
|
||||
@@ -3085,11 +3116,7 @@ class BlockedTicketSecret(models.Model):
|
||||
updated = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
if 'mysql' not in settings.DATABASES['default']['ENGINE']:
|
||||
# MySQL does not support indexes on TextField(). Django knows this and just ignores db_index, but it will
|
||||
# not silently ignore the UNIQUE index, causing this table to fail. I'm so glad we're deprecating MySQL
|
||||
# in a few months, so we'll just live without an unique index until then.
|
||||
unique_together = (('event', 'secret'),)
|
||||
unique_together = (('event', 'secret'),)
|
||||
|
||||
|
||||
@receiver(post_delete, sender=CachedTicket)
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
import string
|
||||
from datetime import date, datetime, time
|
||||
|
||||
import pytz
|
||||
import pytz_deprecation_shim
|
||||
from django.conf import settings
|
||||
from django.core.mail import get_connection
|
||||
from django.core.validators import MinLengthValidator, RegexValidator
|
||||
@@ -102,6 +102,7 @@ class Organizer(LoggedModel):
|
||||
is_new = not self.pk
|
||||
obj = super().save(*args, **kwargs)
|
||||
if is_new:
|
||||
kwargs.pop('update_fields', None) # does not make sense here
|
||||
self.set_defaults()
|
||||
else:
|
||||
self.get_cache().clear()
|
||||
@@ -140,7 +141,7 @@ class Organizer(LoggedModel):
|
||||
|
||||
@property
|
||||
def timezone(self):
|
||||
return pytz.timezone(self.settings.timezone)
|
||||
return pytz_deprecation_shim.timezone(self.settings.timezone)
|
||||
|
||||
@cached_property
|
||||
def all_logentries_link(self):
|
||||
|
||||
@@ -502,7 +502,10 @@ class Voucher(LoggedModel):
|
||||
return seat
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.code = self.code.upper()
|
||||
if self.code != self.code.upper():
|
||||
self.code = self.code.upper()
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'code'}.union(kwargs['update_fields'])
|
||||
super().save(*args, **kwargs)
|
||||
self.event.cache.set('vouchers_exist', True)
|
||||
|
||||
|
||||
@@ -126,12 +126,19 @@ class WaitingListEntry(LoggedModel):
|
||||
raise ValidationError('Invalid input')
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
update_fields = kwargs.get('update_fields', [])
|
||||
update_fields = kwargs.get('update_fields', set())
|
||||
if 'name_parts' in update_fields:
|
||||
update_fields.append('name_cached')
|
||||
self.name_cached = self.name
|
||||
kwargs['update_fields'] = {'name_cached'}.union(kwargs['update_fields'])
|
||||
name = self.name
|
||||
if name != self.name_cached:
|
||||
self.name_cached = name
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'name_cached'}.union(kwargs['update_fields'])
|
||||
|
||||
if self.name_parts is None:
|
||||
self.name_parts = {}
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'name_parts'}.union(kwargs['update_fields'])
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
@property
|
||||
|
||||
@@ -210,7 +210,7 @@ class SubeventColumn(ImportColumn):
|
||||
for format in input_formats:
|
||||
try:
|
||||
d = datetime.datetime.strptime(value, format)
|
||||
d = self.event.timezone.localize(d)
|
||||
d = d.replace(tzinfo=self.event.timezone)
|
||||
try:
|
||||
se = self.event.subevents.get(
|
||||
active=True,
|
||||
@@ -660,7 +660,7 @@ class ValidFrom(ImportColumn):
|
||||
for format in input_formats:
|
||||
try:
|
||||
d = datetime.datetime.strptime(value, format)
|
||||
d = self.event.timezone.localize(d)
|
||||
d = d.replace(tzinfo=self.event.timezone)
|
||||
return d
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
@@ -683,7 +683,7 @@ class ValidUntil(ImportColumn):
|
||||
for format in input_formats:
|
||||
try:
|
||||
d = datetime.datetime.strptime(value, format)
|
||||
d = self.event.timezone.localize(d)
|
||||
d = d.replace(tzinfo=self.event.timezone)
|
||||
return d
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
@@ -39,8 +39,8 @@ import logging
|
||||
from collections import OrderedDict
|
||||
from decimal import ROUND_HALF_UP, Decimal
|
||||
from typing import Any, Dict, Union
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import pytz
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
@@ -518,7 +518,7 @@ class BasePaymentProvider:
|
||||
|
||||
def _is_still_available(self, now_dt=None, cart_id=None, order=None):
|
||||
now_dt = now_dt or now()
|
||||
tz = pytz.timezone(self.event.settings.timezone)
|
||||
tz = ZoneInfo(self.event.settings.timezone)
|
||||
|
||||
availability_date = self.settings.get('_availability_date', as_type=RelativeDateWrapper)
|
||||
if availability_date:
|
||||
|
||||
+16
-17
@@ -61,7 +61,6 @@ from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _, pgettext
|
||||
from i18nfield.strings import LazyI18nString
|
||||
from pypdf import PdfReader
|
||||
from pytz import timezone
|
||||
from reportlab.graphics import renderPDF
|
||||
from reportlab.graphics.barcode.qr import QrCodeWidget
|
||||
from reportlab.graphics.shapes import Drawing
|
||||
@@ -237,7 +236,7 @@ DEFAULT_VARIABLES = OrderedDict((
|
||||
"label": _("Event begin date and time"),
|
||||
"editor_sample": _("2017-05-31 20:00"),
|
||||
"evaluate": lambda op, order, ev: date_format(
|
||||
ev.date_from.astimezone(timezone(ev.settings.timezone)),
|
||||
ev.date_from.astimezone(ev.timezone),
|
||||
"SHORT_DATETIME_FORMAT"
|
||||
) if ev.date_from else ""
|
||||
}),
|
||||
@@ -245,7 +244,7 @@ DEFAULT_VARIABLES = OrderedDict((
|
||||
"label": _("Event begin date"),
|
||||
"editor_sample": _("2017-05-31"),
|
||||
"evaluate": lambda op, order, ev: date_format(
|
||||
ev.date_from.astimezone(timezone(ev.settings.timezone)),
|
||||
ev.date_from.astimezone(ev.timezone),
|
||||
"SHORT_DATE_FORMAT"
|
||||
) if ev.date_from else ""
|
||||
}),
|
||||
@@ -263,7 +262,7 @@ DEFAULT_VARIABLES = OrderedDict((
|
||||
"label": _("Event end date and time"),
|
||||
"editor_sample": _("2017-05-31 22:00"),
|
||||
"evaluate": lambda op, order, ev: date_format(
|
||||
ev.date_to.astimezone(timezone(ev.settings.timezone)),
|
||||
ev.date_to.astimezone(ev.timezone),
|
||||
"SHORT_DATETIME_FORMAT"
|
||||
) if ev.date_to else ""
|
||||
}),
|
||||
@@ -271,7 +270,7 @@ DEFAULT_VARIABLES = OrderedDict((
|
||||
"label": _("Event end date"),
|
||||
"editor_sample": _("2017-05-31"),
|
||||
"evaluate": lambda op, order, ev: date_format(
|
||||
ev.date_to.astimezone(timezone(ev.settings.timezone)),
|
||||
ev.date_to.astimezone(ev.timezone),
|
||||
"SHORT_DATE_FORMAT"
|
||||
) if ev.date_to else ""
|
||||
}),
|
||||
@@ -279,7 +278,7 @@ DEFAULT_VARIABLES = OrderedDict((
|
||||
"label": _("Event end time"),
|
||||
"editor_sample": _("22:00"),
|
||||
"evaluate": lambda op, order, ev: date_format(
|
||||
ev.date_to.astimezone(timezone(ev.settings.timezone)),
|
||||
ev.date_to.astimezone(ev.timezone),
|
||||
"TIME_FORMAT"
|
||||
) if ev.date_to else ""
|
||||
}),
|
||||
@@ -292,7 +291,7 @@ DEFAULT_VARIABLES = OrderedDict((
|
||||
"label": _("Event admission date and time"),
|
||||
"editor_sample": _("2017-05-31 19:00"),
|
||||
"evaluate": lambda op, order, ev: date_format(
|
||||
ev.date_admission.astimezone(timezone(ev.settings.timezone)),
|
||||
ev.date_admission.astimezone(ev.timezone),
|
||||
"SHORT_DATETIME_FORMAT"
|
||||
) if ev.date_admission else ""
|
||||
}),
|
||||
@@ -300,7 +299,7 @@ DEFAULT_VARIABLES = OrderedDict((
|
||||
"label": _("Event admission time"),
|
||||
"editor_sample": _("19:00"),
|
||||
"evaluate": lambda op, order, ev: date_format(
|
||||
ev.date_admission.astimezone(timezone(ev.settings.timezone)),
|
||||
ev.date_admission.astimezone(ev.timezone),
|
||||
"TIME_FORMAT"
|
||||
) if ev.date_admission else ""
|
||||
}),
|
||||
@@ -385,7 +384,7 @@ DEFAULT_VARIABLES = OrderedDict((
|
||||
"label": _("Printing date"),
|
||||
"editor_sample": _("2017-05-31"),
|
||||
"evaluate": lambda op, order, ev: date_format(
|
||||
now().astimezone(timezone(ev.settings.timezone)),
|
||||
now().astimezone(ev.timezone),
|
||||
"SHORT_DATE_FORMAT"
|
||||
)
|
||||
}),
|
||||
@@ -393,7 +392,7 @@ DEFAULT_VARIABLES = OrderedDict((
|
||||
"label": _("Printing date and time"),
|
||||
"editor_sample": _("2017-05-31 19:00"),
|
||||
"evaluate": lambda op, order, ev: date_format(
|
||||
now().astimezone(timezone(ev.settings.timezone)),
|
||||
now().astimezone(ev.timezone),
|
||||
"SHORT_DATETIME_FORMAT"
|
||||
)
|
||||
}),
|
||||
@@ -401,7 +400,7 @@ DEFAULT_VARIABLES = OrderedDict((
|
||||
"label": _("Printing time"),
|
||||
"editor_sample": _("19:00"),
|
||||
"evaluate": lambda op, order, ev: date_format(
|
||||
now().astimezone(timezone(ev.settings.timezone)),
|
||||
now().astimezone(ev.timezone),
|
||||
"TIME_FORMAT"
|
||||
)
|
||||
}),
|
||||
@@ -409,7 +408,7 @@ DEFAULT_VARIABLES = OrderedDict((
|
||||
"label": _("Validity start date"),
|
||||
"editor_sample": _("2017-05-31"),
|
||||
"evaluate": lambda op, order, ev: date_format(
|
||||
op.valid_from.astimezone(timezone(ev.settings.timezone)),
|
||||
op.valid_from.astimezone(ev.timezone),
|
||||
"SHORT_DATE_FORMAT"
|
||||
) if op.valid_from else ""
|
||||
}),
|
||||
@@ -417,7 +416,7 @@ DEFAULT_VARIABLES = OrderedDict((
|
||||
"label": _("Validity start date and time"),
|
||||
"editor_sample": _("2017-05-31 19:00"),
|
||||
"evaluate": lambda op, order, ev: date_format(
|
||||
op.valid_from.astimezone(timezone(ev.settings.timezone)),
|
||||
op.valid_from.astimezone(ev.timezone),
|
||||
"SHORT_DATETIME_FORMAT"
|
||||
) if op.valid_from else ""
|
||||
}),
|
||||
@@ -425,7 +424,7 @@ DEFAULT_VARIABLES = OrderedDict((
|
||||
"label": _("Validity start time"),
|
||||
"editor_sample": _("19:00"),
|
||||
"evaluate": lambda op, order, ev: date_format(
|
||||
op.valid_from.astimezone(timezone(ev.settings.timezone)),
|
||||
op.valid_from.astimezone(ev.timezone),
|
||||
"TIME_FORMAT"
|
||||
) if op.valid_from else ""
|
||||
}),
|
||||
@@ -433,7 +432,7 @@ DEFAULT_VARIABLES = OrderedDict((
|
||||
"label": _("Validity end date"),
|
||||
"editor_sample": _("2017-05-31"),
|
||||
"evaluate": lambda op, order, ev: date_format(
|
||||
op.valid_until.astimezone(timezone(ev.settings.timezone)),
|
||||
op.valid_until.astimezone(ev.timezone),
|
||||
"SHORT_DATE_FORMAT"
|
||||
) if op.valid_until else ""
|
||||
}),
|
||||
@@ -441,7 +440,7 @@ DEFAULT_VARIABLES = OrderedDict((
|
||||
"label": _("Validity end date and time"),
|
||||
"editor_sample": _("2017-05-31 19:00"),
|
||||
"evaluate": lambda op, order, ev: date_format(
|
||||
op.valid_until.astimezone(timezone(ev.settings.timezone)),
|
||||
op.valid_until.astimezone(ev.timezone),
|
||||
"SHORT_DATETIME_FORMAT"
|
||||
) if op.valid_until else ""
|
||||
}),
|
||||
@@ -449,7 +448,7 @@ DEFAULT_VARIABLES = OrderedDict((
|
||||
"label": _("Validity end time"),
|
||||
"editor_sample": _("19:00"),
|
||||
"evaluate": lambda op, order, ev: date_format(
|
||||
op.valid_until.astimezone(timezone(ev.settings.timezone)),
|
||||
op.valid_until.astimezone(ev.timezone),
|
||||
"TIME_FORMAT"
|
||||
) if op.valid_until else ""
|
||||
}),
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
import datetime
|
||||
from collections import namedtuple
|
||||
from typing import Union
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import pytz
|
||||
from dateutil import parser
|
||||
from django import forms
|
||||
from django.core.exceptions import ValidationError
|
||||
@@ -67,7 +67,7 @@ class RelativeDateWrapper:
|
||||
if self.data.minutes_before is not None:
|
||||
raise ValueError('A minute-based relative datetime can not be used as a date')
|
||||
|
||||
tz = pytz.timezone(event.settings.timezone)
|
||||
tz = ZoneInfo(event.settings.timezone)
|
||||
if isinstance(event, SubEvent):
|
||||
base_date = (
|
||||
getattr(event, self.data.base_date_name)
|
||||
@@ -86,7 +86,7 @@ class RelativeDateWrapper:
|
||||
if isinstance(self.data, (datetime.datetime, datetime.date)):
|
||||
return self.data
|
||||
else:
|
||||
tz = pytz.timezone(event.settings.timezone)
|
||||
tz = ZoneInfo(event.settings.timezone)
|
||||
if isinstance(event, SubEvent):
|
||||
base_date = (
|
||||
getattr(event, self.data.base_date_name)
|
||||
@@ -99,8 +99,7 @@ class RelativeDateWrapper:
|
||||
if self.data.minutes_before is not None:
|
||||
return base_date.astimezone(tz) - datetime.timedelta(minutes=self.data.minutes_before)
|
||||
else:
|
||||
oldoffset = base_date.astimezone(tz).utcoffset()
|
||||
new_date = base_date.astimezone(tz) - datetime.timedelta(days=self.data.days_before)
|
||||
new_date = (base_date.astimezone(tz) - datetime.timedelta(days=self.data.days_before)).astimezone(tz)
|
||||
if self.data.time:
|
||||
new_date = new_date.replace(
|
||||
hour=self.data.time.hour,
|
||||
@@ -108,8 +107,6 @@ class RelativeDateWrapper:
|
||||
second=self.data.time.second
|
||||
)
|
||||
new_date = new_date.astimezone(tz)
|
||||
new_offset = new_date.utcoffset()
|
||||
new_date += oldoffset - new_offset
|
||||
return new_date
|
||||
|
||||
def to_string(self) -> str:
|
||||
|
||||
@@ -32,12 +32,12 @@
|
||||
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations under the License.
|
||||
import os
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from functools import partial, reduce
|
||||
|
||||
import dateutil
|
||||
import dateutil.parser
|
||||
import pytz
|
||||
from dateutil.tz import datetime_exists
|
||||
from django.core.files import File
|
||||
from django.db import IntegrityError, transaction
|
||||
from django.db.models import (
|
||||
@@ -439,7 +439,7 @@ class SQLLogic:
|
||||
|
||||
if operator == 'buildTime':
|
||||
if values[0] == "custom":
|
||||
return Value(dateutil.parser.parse(values[1]).astimezone(pytz.UTC))
|
||||
return Value(dateutil.parser.parse(values[1]).astimezone(timezone.utc))
|
||||
elif values[0] == "customtime":
|
||||
parsed = dateutil.parser.parse(values[1])
|
||||
return Value(now().astimezone(self.list.event.timezone).replace(
|
||||
@@ -447,7 +447,7 @@ class SQLLogic:
|
||||
minute=parsed.minute,
|
||||
second=parsed.second,
|
||||
microsecond=parsed.microsecond,
|
||||
).astimezone(pytz.UTC))
|
||||
).astimezone(timezone.utc))
|
||||
elif values[0] == 'date_from':
|
||||
return Coalesce(
|
||||
F('subevent__date_from'),
|
||||
@@ -475,7 +475,7 @@ class SQLLogic:
|
||||
return int(values[1])
|
||||
elif operator == 'var':
|
||||
if values[0] == 'now':
|
||||
return Value(now().astimezone(pytz.UTC))
|
||||
return Value(now().astimezone(timezone.utc))
|
||||
elif values[0] == 'now_isoweekday':
|
||||
return Value(now().astimezone(self.list.event.timezone).isoweekday())
|
||||
elif values[0] == 'product':
|
||||
@@ -926,14 +926,11 @@ def process_exit_all(sender, **kwargs):
|
||||
if cl.event.settings.get(f'autocheckin_dst_hack_{cl.pk}'): # move time back if yesterday was DST switch
|
||||
d -= timedelta(hours=1)
|
||||
cl.event.settings.delete(f'autocheckin_dst_hack_{cl.pk}')
|
||||
try:
|
||||
cl.exit_all_at = make_aware(datetime.combine(d.date() + timedelta(days=1), d.time()), cl.event.timezone)
|
||||
except pytz.exceptions.AmbiguousTimeError:
|
||||
cl.exit_all_at = make_aware(datetime.combine(d.date() + timedelta(days=1), d.time()), cl.event.timezone,
|
||||
is_dst=False)
|
||||
except pytz.exceptions.NonExistentTimeError:
|
||||
|
||||
cl.exit_all_at = make_aware(datetime.combine(d.date() + timedelta(days=1), d.time().replace(fold=1)), cl.event.timezone)
|
||||
if not datetime_exists(cl.exit_all_at):
|
||||
cl.event.settings.set(f'autocheckin_dst_hack_{cl.pk}', True)
|
||||
d += timedelta(hours=1)
|
||||
cl.exit_all_at = make_aware(datetime.combine(d.date() + timedelta(days=1), d.time()), cl.event.timezone)
|
||||
cl.exit_all_at = make_aware(datetime.combine(d.date() + timedelta(days=1), d.time().replace(fold=1)), cl.event.timezone)
|
||||
# AmbiguousTimeError shouldn't be possible since d.time() includes fold=0
|
||||
cl.save(update_fields=['exit_all_at'])
|
||||
|
||||
@@ -348,7 +348,7 @@ def generate_cancellation(invoice: Invoice, trigger_pdf=True):
|
||||
cancellation.prefix = None
|
||||
cancellation.refers = invoice
|
||||
cancellation.is_cancellation = True
|
||||
cancellation.date = timezone.now().date()
|
||||
cancellation.date = timezone.now().astimezone(invoice.event.timezone).date()
|
||||
cancellation.payment_provider_text = ''
|
||||
cancellation.payment_provider_stamp = ''
|
||||
cancellation.file = None
|
||||
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -20,11 +20,10 @@
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
import calendar
|
||||
from datetime import date, datetime, time, timedelta
|
||||
from datetime import date, datetime, time, timedelta, timezone
|
||||
from itertools import groupby
|
||||
from typing import Optional, Tuple
|
||||
|
||||
import pytz
|
||||
from django import forms
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.formats import date_format
|
||||
@@ -392,7 +391,7 @@ class SerializerDateFrameField(serializers.CharField):
|
||||
if data is None:
|
||||
return None
|
||||
try:
|
||||
resolve_timeframe_to_dates_inclusive(now(), data, pytz.UTC)
|
||||
resolve_timeframe_to_dates_inclusive(now(), data, timezone.utc)
|
||||
except:
|
||||
raise ValidationError("Invalid date frame")
|
||||
|
||||
|
||||
@@ -21,9 +21,9 @@
|
||||
#
|
||||
import logging
|
||||
from importlib import import_module
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import celery.exceptions
|
||||
import pytz
|
||||
from celery import states
|
||||
from celery.result import AsyncResult
|
||||
from django.conf import settings
|
||||
@@ -252,7 +252,7 @@ class AsyncFormView(AsyncMixin, FormView):
|
||||
task_self = self
|
||||
view_instance._task_self = task_self
|
||||
|
||||
with translation.override(locale), timezone.override(pytz.timezone(tz)):
|
||||
with translation.override(locale), timezone.override(ZoneInfo(tz)):
|
||||
form_class = view_instance.get_form_class()
|
||||
if form_kwargs.get('instance'):
|
||||
form_kwargs['instance'] = cls.model.objects.get(pk=form_kwargs['instance'])
|
||||
@@ -302,7 +302,7 @@ class AsyncFormView(AsyncMixin, FormView):
|
||||
'url_args': self.args,
|
||||
'url_kwargs': self.kwargs,
|
||||
'locale': get_language(),
|
||||
'tz': get_current_timezone().zone,
|
||||
'tz': str(get_current_timezone()),
|
||||
}
|
||||
if hasattr(self.request, 'organizer'):
|
||||
kwargs['organizer'] = self.request.organizer.pk
|
||||
@@ -377,7 +377,7 @@ class AsyncPostView(AsyncMixin, View):
|
||||
task_self = self
|
||||
view_instance._task_self = task_self
|
||||
|
||||
with translation.override(locale), timezone.override(pytz.timezone(tz)):
|
||||
with translation.override(locale), timezone.override(ZoneInfo(tz)):
|
||||
return view_instance.async_post(view_instance.request, *url_args, **url_kwargs)
|
||||
|
||||
cls.async_execute = app.task(
|
||||
@@ -405,7 +405,7 @@ class AsyncPostView(AsyncMixin, View):
|
||||
'locale': get_language(),
|
||||
'url_args': args,
|
||||
'url_kwargs': kwargs,
|
||||
'tz': get_current_timezone().zone,
|
||||
'tz': str(get_current_timezone()),
|
||||
}
|
||||
if hasattr(self.request, 'organizer'):
|
||||
kwargs['organizer'] = self.request.organizer.pk
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
|
||||
from decimal import Decimal
|
||||
from urllib.parse import urlencode, urlparse
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
@@ -55,7 +56,7 @@ from django_countries.fields import LazyTypedChoiceField
|
||||
from i18nfield.forms import (
|
||||
I18nForm, I18nFormField, I18nFormSetMixin, I18nTextarea, I18nTextInput,
|
||||
)
|
||||
from pytz import common_timezones, timezone
|
||||
from pytz import common_timezones
|
||||
|
||||
from pretix.base.channels import get_all_sales_channels
|
||||
from pretix.base.email import get_available_placeholders
|
||||
@@ -221,7 +222,7 @@ class EventWizardBasicsForm(I18nModelForm):
|
||||
})
|
||||
|
||||
# change timezone
|
||||
zone = timezone(data.get('timezone'))
|
||||
zone = ZoneInfo(data.get('timezone'))
|
||||
data['date_from'] = self.reset_timezone(zone, data.get('date_from'))
|
||||
data['date_to'] = self.reset_timezone(zone, data.get('date_to'))
|
||||
data['presale_start'] = self.reset_timezone(zone, data.get('presale_start'))
|
||||
@@ -230,7 +231,7 @@ class EventWizardBasicsForm(I18nModelForm):
|
||||
|
||||
@staticmethod
|
||||
def reset_timezone(tz, dt):
|
||||
return tz.localize(dt.replace(tzinfo=None)) if dt is not None else None
|
||||
return dt.replace(tzinfo=tz) if dt is not None else None
|
||||
|
||||
def clean_slug(self):
|
||||
slug = self.cleaned_data['slug']
|
||||
|
||||
@@ -747,7 +747,7 @@ class ItemVariationsFormSet(I18nFormSet):
|
||||
|
||||
def _should_delete_form(self, form):
|
||||
should_delete = super()._should_delete_form(form)
|
||||
if should_delete and (form.instance.orderposition_set.exists() or form.instance.cartposition_set.exists()):
|
||||
if should_delete and form.instance.pk and (form.instance.orderposition_set.exists() or form.instance.cartposition_set.exists()):
|
||||
form._delete_fail = True
|
||||
return False
|
||||
return form.cleaned_data.get(DELETION_FIELD_NAME, False)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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.'))
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -33,8 +33,7 @@
|
||||
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations under the License.
|
||||
|
||||
from django.conf.urls import re_path
|
||||
from django.urls import include
|
||||
from django.urls import include, re_path
|
||||
from django.views.generic.base import RedirectView
|
||||
|
||||
from pretix.control.views import (
|
||||
|
||||
@@ -19,18 +19,7 @@
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
# This file is based on an earlier version of pretix which was released under the Apache License 2.0. The full text of
|
||||
# the Apache License 2.0 can be obtained at <http://www.apache.org/licenses/LICENSE-2.0>.
|
||||
#
|
||||
# This file may have since been changed and any changes are released under the terms of AGPLv3 as described above. A
|
||||
# full history of changes and contributors is available at <https://github.com/pretix/pretix>.
|
||||
#
|
||||
# This file contains Apache-licensed contributions copyrighted by: Jakob Schnell, jasonwaiting@live.hk, pajowu
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software distributed under the Apache License 2.0 is
|
||||
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations under the License.
|
||||
from datetime import timezone
|
||||
|
||||
import dateutil.parser
|
||||
from django.contrib import messages
|
||||
@@ -44,7 +33,6 @@ from django.utils.functional import cached_property
|
||||
from django.utils.timezone import is_aware, make_aware, now
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views.generic import ListView
|
||||
from pytz import UTC
|
||||
|
||||
from pretix.base.channels import get_all_sales_channels
|
||||
from pretix.base.models import Checkin, Order, OrderPosition
|
||||
@@ -60,6 +48,18 @@ from pretix.control.views import CreateView, PaginationMixin, UpdateView
|
||||
from pretix.helpers.compat import CompatDeleteView
|
||||
from pretix.helpers.models import modelcopy
|
||||
|
||||
# This file is based on an earlier version of pretix which was released under the Apache License 2.0. The full text of
|
||||
# the Apache License 2.0 can be obtained at <http://www.apache.org/licenses/LICENSE-2.0>.
|
||||
#
|
||||
# This file may have since been changed and any changes are released under the terms of AGPLv3 as described above. A
|
||||
# full history of changes and contributors is available at <https://github.com/pretix/pretix>.
|
||||
#
|
||||
# This file contains Apache-licensed contributions copyrighted by: Jakob Schnell, jasonwaiting@live.hk, pajowu
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software distributed under the Apache License 2.0 is
|
||||
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations under the License.
|
||||
|
||||
|
||||
class CheckInListQueryMixin:
|
||||
|
||||
@@ -163,20 +163,20 @@ class CheckInListShow(EventPermissionRequiredMixin, PaginationMixin, CheckInList
|
||||
if e.last_entry:
|
||||
if isinstance(e.last_entry, str):
|
||||
# Apparently only happens on SQLite
|
||||
e.last_entry_aware = make_aware(dateutil.parser.parse(e.last_entry), UTC)
|
||||
e.last_entry_aware = make_aware(dateutil.parser.parse(e.last_entry), timezone.utc)
|
||||
elif not is_aware(e.last_entry):
|
||||
# Apparently only happens on MySQL
|
||||
e.last_entry_aware = make_aware(e.last_entry, UTC)
|
||||
e.last_entry_aware = make_aware(e.last_entry, timezone.utc)
|
||||
else:
|
||||
# This would be correct, so guess on which database it works… Yes, it's PostgreSQL.
|
||||
e.last_entry_aware = e.last_entry
|
||||
if e.last_exit:
|
||||
if isinstance(e.last_exit, str):
|
||||
# Apparently only happens on SQLite
|
||||
e.last_exit_aware = make_aware(dateutil.parser.parse(e.last_exit), UTC)
|
||||
e.last_exit_aware = make_aware(dateutil.parser.parse(e.last_exit), timezone.utc)
|
||||
elif not is_aware(e.last_exit):
|
||||
# Apparently only happens on MySQL
|
||||
e.last_exit_aware = make_aware(e.last_exit, UTC)
|
||||
e.last_exit_aware = make_aware(e.last_exit, timezone.utc)
|
||||
else:
|
||||
# This would be correct, so guess on which database it works… Yes, it's PostgreSQL.
|
||||
e.last_exit_aware = e.last_exit
|
||||
|
||||
@@ -34,8 +34,8 @@
|
||||
|
||||
from datetime import timedelta
|
||||
from decimal import Decimal
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import pytz
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db.models import (
|
||||
@@ -510,7 +510,7 @@ def widgets_for_event_qs(request, qs, user, nmax, lazy=False):
|
||||
for event in events:
|
||||
if not lazy:
|
||||
tzname = event.cache.get_or_set('timezone', lambda: event.settings.timezone)
|
||||
tz = pytz.timezone(tzname)
|
||||
tz = ZoneInfo(tzname)
|
||||
if event.has_subevents:
|
||||
if event.min_from is None:
|
||||
dr = pgettext("subevent", "No dates")
|
||||
|
||||
@@ -41,6 +41,7 @@ from decimal import Decimal
|
||||
from io import BytesIO
|
||||
from itertools import groupby
|
||||
from urllib.parse import urlsplit
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import bleach
|
||||
import qrcode
|
||||
@@ -67,7 +68,6 @@ from django.views.generic.base import TemplateView, View
|
||||
from django.views.generic.detail import SingleObjectMixin
|
||||
from i18nfield.strings import LazyI18nString
|
||||
from i18nfield.utils import I18nJSONEncoder
|
||||
from pytz import timezone
|
||||
|
||||
from pretix.base.channels import get_all_sales_channels
|
||||
from pretix.base.email import get_available_placeholders
|
||||
@@ -253,7 +253,7 @@ class EventUpdate(DecoupleMixin, EventSettingsViewMixin, EventPermissionRequired
|
||||
self.item_meta_property_formset.is_valid() and self.confirm_texts_formset.is_valid() and \
|
||||
self.footer_links_formset.is_valid():
|
||||
# reset timezone
|
||||
zone = timezone(self.sform.cleaned_data['timezone'])
|
||||
zone = ZoneInfo(self.sform.cleaned_data['timezone'])
|
||||
event = form.instance
|
||||
event.date_from = self.reset_timezone(zone, event.date_from)
|
||||
event.date_to = self.reset_timezone(zone, event.date_to)
|
||||
@@ -266,7 +266,7 @@ class EventUpdate(DecoupleMixin, EventSettingsViewMixin, EventPermissionRequired
|
||||
|
||||
@staticmethod
|
||||
def reset_timezone(tz, dt):
|
||||
return tz.localize(dt.replace(tzinfo=None)) if dt is not None else None
|
||||
return dt.replace(tzinfo=tz) if dt is not None else None
|
||||
|
||||
@cached_property
|
||||
def item_meta_property_formset(self):
|
||||
|
||||
@@ -60,8 +60,7 @@ from django.utils.timezone import get_current_timezone, now
|
||||
from django.utils.translation import gettext, gettext_lazy as _
|
||||
from django.views import View
|
||||
from django.views.generic import (
|
||||
CreateView, DeleteView, DetailView, FormView, ListView, TemplateView,
|
||||
UpdateView,
|
||||
CreateView, DetailView, FormView, ListView, TemplateView, UpdateView,
|
||||
)
|
||||
|
||||
from pretix.api.models import ApiCall, WebHook
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ class OrderSearch(PaginationMixin, ListView):
|
||||
|
||||
if self.filter_form.use_query_hack():
|
||||
"""
|
||||
We need to work around a bug in PostgreSQL's (and likely MySQL's) query plan optimizer here.
|
||||
We need to work around a bug in PostgreSQL's query plan optimizer here.
|
||||
The database lacks statistical data to predict how common our search filter is and therefore
|
||||
assumes that it is cheaper to first ORDER *all* orders in the system (since we got an index on
|
||||
datetime), then filter out with a full scan until OFFSET/LIMIT condition is fulfilled. If we
|
||||
@@ -167,7 +167,7 @@ class PaymentSearch(PaginationMixin, ListView):
|
||||
|
||||
if self.filter_form.cleaned_data.get('query'):
|
||||
"""
|
||||
We need to work around a bug in PostgreSQL's (and likely MySQL's) query plan optimizer here.
|
||||
We need to work around a bug in PostgreSQL's query plan optimizer here.
|
||||
The database lacks statistical data to predict how common our search filter is and therefore
|
||||
assumes that it is cheaper to first ORDER *all* orders in the system (since we got an index on
|
||||
datetime), then filter out with a full scan until OFFSET/LIMIT condition is fulfilled. If we
|
||||
|
||||
@@ -260,7 +260,7 @@ class SubEventEditorMixin(MetaDataEditorMixin):
|
||||
form=SimpleCheckinListForm, formset=CheckinListFormSet,
|
||||
can_order=False, can_delete=True, extra=extra,
|
||||
)
|
||||
if self.object:
|
||||
if self.object and self.object.pk:
|
||||
kwargs['queryset'] = self.object.checkinlist_set.prefetch_related('limit_products')
|
||||
|
||||
return formsetclass(self.request.POST if self.request.method == "POST" else None,
|
||||
@@ -297,7 +297,7 @@ class SubEventEditorMixin(MetaDataEditorMixin):
|
||||
form=QuotaForm, formset=QuotaFormSet, min_num=1, validate_min=True,
|
||||
can_order=False, can_delete=True, extra=extra,
|
||||
)
|
||||
if self.object:
|
||||
if self.object and self.object.pk:
|
||||
kwargs['queryset'] = self.object.quotas.prefetch_related('items', 'variations')
|
||||
|
||||
return formsetclass(
|
||||
@@ -400,10 +400,10 @@ class SubEventEditorMixin(MetaDataEditorMixin):
|
||||
def itemvar_forms(self):
|
||||
se_item_instances = {
|
||||
sei.item_id: sei for sei in SubEventItem.objects.filter(subevent=self.object)
|
||||
}
|
||||
} if self.object and self.object.pk else {}
|
||||
se_var_instances = {
|
||||
sei.variation_id: sei for sei in SubEventItemVariation.objects.filter(subevent=self.object)
|
||||
}
|
||||
} if self.object and self.object.pk else {}
|
||||
|
||||
if self.copy_from:
|
||||
se_item_instances = {
|
||||
@@ -821,18 +821,18 @@ class SubEventBulkCreate(SubEventEditorMixin, EventPermissionRequiredMixin, Asyn
|
||||
for t in self.get_times():
|
||||
se = copy.copy(form.instance)
|
||||
|
||||
se.date_from = make_aware(datetime.combine(rdate, t['time_from']), tz, is_dst=False)
|
||||
se.date_from = make_aware(datetime.combine(rdate, t['time_from'].replace(fold=1)), tz)
|
||||
|
||||
if t.get('time_to'):
|
||||
se.date_to = (
|
||||
make_aware(datetime.combine(rdate, t['time_to']), tz, is_dst=False)
|
||||
make_aware(datetime.combine(rdate, t['time_to'].replace(fold=1)), tz)
|
||||
if t.get('time_to') > t.get('time_from')
|
||||
else make_aware(datetime.combine(rdate + timedelta(days=1), t['time_to']), tz, is_dst=False)
|
||||
else make_aware(datetime.combine(rdate + timedelta(days=1), t['time_to'].replace(fold=1)), tz)
|
||||
)
|
||||
else:
|
||||
se.date_to = None
|
||||
se.date_admission = (
|
||||
make_aware(datetime.combine(rdate, t['time_admission'].replace(fold=1)), tz, is_dst=False)
|
||||
make_aware(datetime.combine(rdate, t['time_admission'].replace(fold=1)), tz)
|
||||
if t.get('time_admission')
|
||||
else None
|
||||
)
|
||||
@@ -1148,6 +1148,7 @@ class SubEventBulkEdit(SubEventQueryMixin, EventPermissionRequiredMixin, FormVie
|
||||
subevents = list(self.get_queryset().prefetch_related('checkinlist_set'))
|
||||
to_save_products = []
|
||||
to_save_gates = []
|
||||
to_delete_list_ids = []
|
||||
|
||||
for f in self.list_formset.forms:
|
||||
if self.list_formset._should_delete_form(f) and f in self.list_formset.extra_forms:
|
||||
@@ -1159,7 +1160,7 @@ class SubEventBulkEdit(SubEventQueryMixin, EventPermissionRequiredMixin, FormVie
|
||||
log_entries += [
|
||||
q.log_action(action='pretix.event.checkinlist.deleted', user=self.request.user, save=False),
|
||||
]
|
||||
q.delete()
|
||||
to_delete_list_ids.append(q.pk)
|
||||
elif f in self.list_formset.extra_forms:
|
||||
change_data = {k: f.cleaned_data.get(k) for k in f.changed_data}
|
||||
for se in subevents:
|
||||
@@ -1198,6 +1199,8 @@ class SubEventBulkEdit(SubEventQueryMixin, EventPermissionRequiredMixin, FormVie
|
||||
CheckinList.limit_products.through.objects.bulk_create(to_save_products)
|
||||
if to_save_gates:
|
||||
CheckinList.gates.through.objects.bulk_create(to_save_gates)
|
||||
if to_delete_list_ids:
|
||||
CheckinList.objects.filter(id__in=to_delete_list_ids).delete()
|
||||
|
||||
def save_quota_formset(self, log_entries):
|
||||
if not self.quota_formset.has_changed():
|
||||
@@ -1224,6 +1227,7 @@ class SubEventBulkEdit(SubEventQueryMixin, EventPermissionRequiredMixin, FormVie
|
||||
|
||||
if to_delete_quota_ids:
|
||||
Quota.objects.filter(id__in=to_delete_quota_ids).delete()
|
||||
to_delete_quota_ids = []
|
||||
|
||||
for f in self.quota_formset.forms:
|
||||
if self.quota_formset._should_delete_form(f) and f in self.quota_formset.extra_forms:
|
||||
@@ -1245,7 +1249,7 @@ class SubEventBulkEdit(SubEventQueryMixin, EventPermissionRequiredMixin, FormVie
|
||||
'id': q.pk
|
||||
}, save=False)
|
||||
]
|
||||
q.delete()
|
||||
to_delete_quota_ids.append(q.pk)
|
||||
elif f in self.quota_formset.extra_forms:
|
||||
change_data = {k: f.cleaned_data.get(k) for k in f.changed_data}
|
||||
for se in subevents:
|
||||
@@ -1288,6 +1292,8 @@ class SubEventBulkEdit(SubEventQueryMixin, EventPermissionRequiredMixin, FormVie
|
||||
Quota.items.through.objects.bulk_create(to_save_items)
|
||||
if to_save_variations:
|
||||
Quota.variations.through.objects.bulk_create(to_save_variations)
|
||||
if to_delete_quota_ids:
|
||||
Quota.objects.filter(id__in=to_delete_quota_ids).delete()
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
|
||||
@@ -33,8 +33,8 @@
|
||||
# License for the specific language governing permissions and limitations under the License.
|
||||
|
||||
from datetime import datetime, time
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import pytz
|
||||
from dateutil.parser import parse
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.db.models import F, Max, Min, Q
|
||||
@@ -88,7 +88,7 @@ def serialize_event(e):
|
||||
if e.min_from is None:
|
||||
dr = pgettext('subevent', 'No dates')
|
||||
else:
|
||||
tz = pytz.timezone(e.settings.timezone)
|
||||
tz = ZoneInfo(e.settings.timezone)
|
||||
dr = _('Series:') + ' ' + daterange(
|
||||
e.min_from.astimezone(tz),
|
||||
(e.max_fromto or e.max_to or e.max_from).astimezone(tz)
|
||||
|
||||
@@ -29,7 +29,7 @@ def postgres_compile_json_path(key_transforms):
|
||||
return "{" + ','.join(key_transforms) + "}"
|
||||
|
||||
|
||||
def mysql_compile_json_path(key_transforms):
|
||||
def sqlite_compile_json_path(key_transforms):
|
||||
path = ['$']
|
||||
for key_transform in key_transforms:
|
||||
try:
|
||||
@@ -41,9 +41,6 @@ def mysql_compile_json_path(key_transforms):
|
||||
return ''.join(path)
|
||||
|
||||
|
||||
sqlite_compile_json_path = mysql_compile_json_path
|
||||
|
||||
|
||||
class JSONExtract(Expression):
|
||||
def __init__(self, expression, *path, output_field=JSONField(), **extra):
|
||||
super().__init__(output_field=output_field)
|
||||
@@ -66,14 +63,6 @@ class JSONExtract(Expression):
|
||||
params.append(json_path)
|
||||
template = '{} #> %s'.format(arg_sql)
|
||||
return template, params
|
||||
elif '.mysql' in connection.settings_dict['ENGINE']:
|
||||
params = []
|
||||
arg_sql, arg_params = compiler.compile(self.source_expression)
|
||||
params.extend(arg_params)
|
||||
json_path = mysql_compile_json_path(self.path)
|
||||
params.append(json_path)
|
||||
template = 'JSON_EXTRACT({}, %s)'.format(arg_sql)
|
||||
return template, params
|
||||
elif '.sqlite' in connection.settings_dict['ENGINE']:
|
||||
params = []
|
||||
arg_sql, arg_params = compiler.compile(self.source_expression)
|
||||
@@ -84,7 +73,7 @@ class JSONExtract(Expression):
|
||||
return template, params
|
||||
else:
|
||||
raise NotSupportedError(
|
||||
'Functions on JSONFields are only supported on SQLite, PostgreSQL, and MySQL at the moment.'
|
||||
'Functions on JSONFields are only supported on SQLite and PostgreSQL at the moment.'
|
||||
)
|
||||
|
||||
def copy(self):
|
||||
|
||||
@@ -5,7 +5,7 @@ msgstr ""
|
||||
"Project-Id-Version: 1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-05-31 09:01+0000\n"
|
||||
"PO-Revision-Date: 2023-05-31 10:41+0000\n"
|
||||
"PO-Revision-Date: 2023-06-05 16:00+0000\n"
|
||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||
"Language-Team: German <https://translate.pretix.eu/projects/pretix/pretix/de/"
|
||||
">\n"
|
||||
@@ -24748,8 +24748,8 @@ msgid ""
|
||||
"The order has been canceled. You can now select how you want to transfer the "
|
||||
"money back to the user."
|
||||
msgstr ""
|
||||
"Die Bestellung wurde als erstattet markiert. Sie können nun auswählen, wie "
|
||||
"Sie das Geld zurückerstatten möchten."
|
||||
"Die Bestellung wurde storniert. Sie können nun auswählen, wie Sie das Geld "
|
||||
"zurückerstatten möchten."
|
||||
|
||||
#: pretix/control/views/orders.py:1362 pretix/control/views/orders.py:1366
|
||||
msgid "No VAT ID specified."
|
||||
@@ -30492,8 +30492,8 @@ msgstr ""
|
||||
#: pretix/presale/templates/pretixpresale/event/order.html:43
|
||||
msgid "Please note that we still await your payment to complete the process."
|
||||
msgstr ""
|
||||
"Bitte beachten Sie, dass die Bestellung erst mit Eingang der Zahlung "
|
||||
"vollständig abgeschlossen ist."
|
||||
"Bitte beachten Sie, dass wir noch Ihre Zahlung erwarten, um den Prozess "
|
||||
"abzuschließen."
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/order.html:55
|
||||
msgid ""
|
||||
|
||||
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Project-Id-Version: 1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-05-31 09:01+0000\n"
|
||||
"PO-Revision-Date: 2023-05-31 10:41+0000\n"
|
||||
"PO-Revision-Date: 2023-06-05 16:00+0000\n"
|
||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||
"Language-Team: German (informal) <https://translate.pretix.eu/projects/"
|
||||
"pretix/pretix/de_Informal/>\n"
|
||||
@@ -24704,8 +24704,8 @@ msgid ""
|
||||
"The order has been canceled. You can now select how you want to transfer the "
|
||||
"money back to the user."
|
||||
msgstr ""
|
||||
"Die Bestellung wurde als erstattet markiert. Du kannst nun auswählen, wie du "
|
||||
"das Geld zurückerstatten möchtest."
|
||||
"Die Bestellung wurde storniert. Du kannst nun auswählen, wie du das Geld "
|
||||
"zurückerstatten möchtest."
|
||||
|
||||
#: pretix/control/views/orders.py:1362 pretix/control/views/orders.py:1366
|
||||
msgid "No VAT ID specified."
|
||||
@@ -30433,8 +30433,8 @@ msgstr ""
|
||||
#: pretix/presale/templates/pretixpresale/event/order.html:43
|
||||
msgid "Please note that we still await your payment to complete the process."
|
||||
msgstr ""
|
||||
"Bitte beachte, dass der Bestellvorgang erst mit Eingang deiner Zahlung "
|
||||
"abgeschlossen ist."
|
||||
"Bitte beachten Sie, dass wir noch deine Zahlung erwarten, um den Prozess "
|
||||
"abzuschließen."
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/order.html:55
|
||||
msgid ""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -35,8 +35,7 @@
|
||||
import importlib.util
|
||||
|
||||
from django.apps import apps
|
||||
from django.conf.urls import re_path
|
||||
from django.urls import include
|
||||
from django.urls import include, re_path
|
||||
from django.views.generic import TemplateView
|
||||
|
||||
from pretix.multidomain.plugin_handler import plugin_event_urls
|
||||
|
||||
@@ -42,7 +42,9 @@ from django.contrib.sessions.middleware import (
|
||||
from django.core.cache import cache
|
||||
from django.core.exceptions import DisallowedHost
|
||||
from django.http.request import split_domain_port
|
||||
from django.middleware.csrf import CsrfViewMiddleware as BaseCsrfMiddleware
|
||||
from django.middleware.csrf import (
|
||||
CSRF_SESSION_KEY, CsrfViewMiddleware as BaseCsrfMiddleware,
|
||||
)
|
||||
from django.shortcuts import render
|
||||
from django.urls import set_urlconf
|
||||
from django.utils.cache import patch_vary_headers
|
||||
@@ -189,35 +191,25 @@ class CsrfViewMiddleware(BaseCsrfMiddleware):
|
||||
a custom domain.
|
||||
"""
|
||||
|
||||
def process_response(self, request, response):
|
||||
if getattr(response, 'csrf_processing_done', False):
|
||||
return response
|
||||
|
||||
# If CSRF_COOKIE is unset, then CsrfViewMiddleware.process_view was
|
||||
# never called, probably because a request middleware returned a response
|
||||
# (for example, contrib.auth redirecting to a login page).
|
||||
if request.META.get("CSRF_COOKIE") is None:
|
||||
return response
|
||||
|
||||
if not request.META.get("CSRF_COOKIE_USED", False):
|
||||
return response
|
||||
|
||||
# Set the CSRF cookie even if it's already set, so we renew
|
||||
# the expiry timer.
|
||||
set_cookie_without_samesite(
|
||||
request, response,
|
||||
settings.CSRF_COOKIE_NAME,
|
||||
request.META["CSRF_COOKIE"],
|
||||
max_age=settings.CSRF_COOKIE_AGE,
|
||||
domain=get_cookie_domain(request),
|
||||
path=settings.CSRF_COOKIE_PATH,
|
||||
secure=request.scheme == 'https',
|
||||
httponly=settings.CSRF_COOKIE_HTTPONLY
|
||||
)
|
||||
# Content varies with the CSRF cookie, so set the Vary header.
|
||||
patch_vary_headers(response, ('Cookie',))
|
||||
response.csrf_processing_done = True
|
||||
return response
|
||||
def _set_csrf_cookie(self, request, response):
|
||||
if settings.CSRF_USE_SESSIONS:
|
||||
if request.session.get(CSRF_SESSION_KEY) != request.META["CSRF_COOKIE"]:
|
||||
request.session[CSRF_SESSION_KEY] = request.META["CSRF_COOKIE"]
|
||||
else:
|
||||
# Set the CSRF cookie even if it's already set, so we renew
|
||||
# the expiry timer.
|
||||
set_cookie_without_samesite(
|
||||
request, response,
|
||||
settings.CSRF_COOKIE_NAME,
|
||||
request.META["CSRF_COOKIE"],
|
||||
max_age=settings.CSRF_COOKIE_AGE,
|
||||
domain=get_cookie_domain(request),
|
||||
path=settings.CSRF_COOKIE_PATH,
|
||||
secure=request.scheme == 'https',
|
||||
httponly=settings.CSRF_COOKIE_HTTPONLY
|
||||
)
|
||||
# Content varies with the CSRF cookie, so set the Vary header.
|
||||
patch_vary_headers(response, ('Cookie',))
|
||||
|
||||
|
||||
def get_cookie_domain(request):
|
||||
|
||||
@@ -22,8 +22,7 @@
|
||||
import importlib.util
|
||||
|
||||
from django.apps import apps
|
||||
from django.conf.urls import re_path
|
||||
from django.urls import include
|
||||
from django.urls import include, re_path
|
||||
|
||||
from pretix.multidomain.plugin_handler import plugin_event_urls
|
||||
from pretix.presale.urls import (
|
||||
|
||||
@@ -45,6 +45,9 @@ from .models import KnownDomain
|
||||
|
||||
def get_event_domain(event, fallback=False, return_info=False):
|
||||
assert isinstance(event, Event)
|
||||
if not event.pk:
|
||||
# Can happen on the "event deleted" response
|
||||
return (None, None) if return_info else None
|
||||
suffix = ('_fallback' if fallback else '') + ('_info' if return_info else '')
|
||||
domain = getattr(event, '_cached_domain' + suffix, None) or event.cache.get('domain' + suffix)
|
||||
if domain is None:
|
||||
|
||||
@@ -51,7 +51,7 @@ from django.db.models import Case, Exists, OuterRef, Q, Subquery, When
|
||||
from django.db.models.functions import Cast, Coalesce
|
||||
from django.utils.timezone import make_aware
|
||||
from django.utils.translation import gettext as _, gettext_lazy, pgettext_lazy
|
||||
from pypdf import PdfMerger, PdfReader, PdfWriter, Transformation
|
||||
from pypdf import PdfReader, PdfWriter, Transformation
|
||||
from pypdf.generic import RectangleObject
|
||||
from reportlab.lib import pagesizes
|
||||
from reportlab.lib.units import mm
|
||||
@@ -196,7 +196,7 @@ def render_pdf(event, positions, opt):
|
||||
raise ExportError(_("None of the selected products is configured to print badges."))
|
||||
|
||||
# render each badge on its own page first
|
||||
merger = PdfMerger()
|
||||
merger = PdfWriter()
|
||||
merger.add_metadata({
|
||||
'/Title': 'Badges',
|
||||
'/Creator': 'pretix',
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
from django.conf.urls import re_path
|
||||
from django.urls import re_path
|
||||
|
||||
from pretix.api.urls import event_router
|
||||
from pretix.plugins.badges.api import BadgeItemViewSet, BadgeLayoutViewSet
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
from django.conf.urls import re_path
|
||||
from django.urls import re_path
|
||||
|
||||
from pretix.api.urls import orga_router
|
||||
from pretix.plugins.banktransfer.api import BankImportJobViewSet
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
# License for the specific language governing permissions and limitations under the License.
|
||||
|
||||
from collections import OrderedDict
|
||||
from datetime import timezone
|
||||
|
||||
import bleach
|
||||
import dateutil.parser
|
||||
@@ -47,7 +48,6 @@ from django.utils.timezone import is_aware, make_aware, now
|
||||
from django.utils.translation import (
|
||||
gettext as _, gettext_lazy, pgettext, pgettext_lazy,
|
||||
)
|
||||
from pytz import UTC
|
||||
from reportlab.lib.units import mm
|
||||
from reportlab.platypus import Flowable, Paragraph, Spacer, Table, TableStyle
|
||||
|
||||
@@ -513,7 +513,7 @@ class CSVCheckinList(CheckInListMixin, ListExporter):
|
||||
elif op.last_checked_in:
|
||||
last_checked_in = op.last_checked_in
|
||||
if last_checked_in and not is_aware(last_checked_in):
|
||||
last_checked_in = make_aware(last_checked_in, UTC)
|
||||
last_checked_in = make_aware(last_checked_in, timezone.utc)
|
||||
|
||||
last_checked_out = None
|
||||
if isinstance(op.last_checked_out, str): # SQLite
|
||||
@@ -521,7 +521,7 @@ class CSVCheckinList(CheckInListMixin, ListExporter):
|
||||
elif op.last_checked_out:
|
||||
last_checked_out = op.last_checked_out
|
||||
if last_checked_out and not is_aware(last_checked_out):
|
||||
last_checked_out = make_aware(last_checked_out, UTC)
|
||||
last_checked_out = make_aware(last_checked_out, timezone.utc)
|
||||
|
||||
row = [
|
||||
op.order.code,
|
||||
|
||||
@@ -19,8 +19,7 @@
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
from django.conf.urls import re_path
|
||||
from django.urls import include
|
||||
from django.urls import include, re_path
|
||||
|
||||
from .views import abort, oauth_disconnect, redirect_view, success
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ from django.http import HttpRequest, HttpResponse
|
||||
from django.template.loader import get_template
|
||||
from django.urls import resolve
|
||||
from django.utils.crypto import get_random_string
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.utils.translation import gettext_lazy as _, pgettext_lazy
|
||||
|
||||
from pretix.base.forms import SecretKeySettingsField
|
||||
from pretix.base.middleware import _merge_csp, _parse_csp, _render_csp
|
||||
@@ -61,7 +61,10 @@ def pretixcontrol_logentry_display(sender, logentry, **kwargs):
|
||||
'PAYMENT.SALE.REFUNDED': _('Payment refunded.'),
|
||||
'PAYMENT.SALE.REVERSED': _('Payment reversed.'),
|
||||
'PAYMENT.SALE.PENDING': _('Payment pending.'),
|
||||
'CHECKOUT.ORDER.APPROVED': _('Order approved.'),
|
||||
'CHECKOUT.ORDER.APPROVED': pgettext_lazy('paypal', 'Order approved.'),
|
||||
'CHECKOUT.ORDER.COMPLETED': pgettext_lazy('paypal', 'Order completed.'),
|
||||
'PAYMENT.CAPTURE.COMPLETED': pgettext_lazy('paypal', 'Capture completed.'),
|
||||
'PAYMENT.CAPTURE.PENDING': pgettext_lazy('paypal', 'Capture pending.'),
|
||||
}
|
||||
|
||||
if event_type in plains:
|
||||
|
||||
@@ -19,8 +19,7 @@
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
from django.conf.urls import re_path
|
||||
from django.urls import include
|
||||
from django.urls import include, re_path
|
||||
|
||||
from .views import (
|
||||
PayView, XHRView, abort, isu_disconnect, isu_return, redirect_view,
|
||||
|
||||
@@ -37,7 +37,6 @@ import tempfile
|
||||
from collections import OrderedDict, defaultdict
|
||||
from decimal import Decimal
|
||||
|
||||
import pytz
|
||||
from dateutil.parser import parse
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
@@ -123,7 +122,7 @@ class ReportlabExportMixin:
|
||||
return 'report-%s.pdf' % self.event.slug, 'application/pdf', self.create(form_data)
|
||||
|
||||
def get_filename(self):
|
||||
tz = pytz.timezone(self.event.settings.timezone)
|
||||
tz = self.event.timezone
|
||||
return "%s-%s.pdf" % (self.name, now().astimezone(tz).strftime("%Y-%m-%d-%H-%M-%S"))
|
||||
|
||||
@staticmethod
|
||||
@@ -491,7 +490,7 @@ class OrderTaxListReportPDF(Report):
|
||||
headlinestyle = self.get_style()
|
||||
headlinestyle.fontSize = 15
|
||||
headlinestyle.fontName = 'OpenSansBd'
|
||||
tz = pytz.timezone(self.event.settings.timezone)
|
||||
tz = self.event.timezone
|
||||
|
||||
tax_rates = set(
|
||||
a for a
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
from django.conf.urls import re_path
|
||||
from django.urls import re_path
|
||||
|
||||
from .views import ReturnSettings
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#
|
||||
from datetime import datetime, time, timedelta
|
||||
|
||||
from dateutil.tz import datetime_exists
|
||||
from django.db import models
|
||||
from django.db.models import Exists, OuterRef, Q
|
||||
from django.utils import timezone
|
||||
@@ -69,6 +70,9 @@ class ScheduledMail(models.Model):
|
||||
def save(self, **kwargs):
|
||||
if not self.computed_datetime:
|
||||
self.recompute()
|
||||
if 'update_fields' in kwargs:
|
||||
kwargs['update_fields'] = {'computed_datetime', 'last_computed', 'state'}.union(kwargs['update_fields'])
|
||||
|
||||
super().save(**kwargs)
|
||||
|
||||
def recompute(self):
|
||||
@@ -88,10 +92,14 @@ class ScheduledMail(models.Model):
|
||||
base_time = (e.date_to or e.date_from) if self.rule.offset_to_event_end else e.date_from
|
||||
d = base_time.astimezone(self.event.timezone).date() + offset
|
||||
self.computed_datetime = make_aware(
|
||||
datetime.combine(d, time(hour=st.hour, minute=st.minute, second=st.second, microsecond=0)),
|
||||
datetime.combine(d, time(hour=st.hour, minute=st.minute, second=st.second, microsecond=0, fold=1)),
|
||||
self.event.timezone,
|
||||
is_dst=False, # prevent AmbiguousTimeError
|
||||
)
|
||||
if not datetime_exists(self.computed_datetime):
|
||||
self.computed_datetime = make_aware(
|
||||
datetime.combine(d, time(hour=st.hour, minute=st.minute, second=st.second, microsecond=0)) + timedelta(hours=1),
|
||||
self.event.timezone,
|
||||
)
|
||||
|
||||
if self.computed_datetime > timezone.now() and self.state == self.STATE_MISSED:
|
||||
self.state = self.STATE_SCHEDULED
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations under the License.
|
||||
|
||||
from django.conf.urls import re_path
|
||||
from django.urls import re_path
|
||||
|
||||
from pretix.api.urls import event_router
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
from django.conf.urls import re_path
|
||||
from django.urls import re_path
|
||||
|
||||
from . import views
|
||||
|
||||
|
||||
@@ -19,8 +19,7 @@
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
from django.conf.urls import re_path
|
||||
from django.urls import include
|
||||
from django.urls import include, re_path
|
||||
|
||||
from pretix.multidomain import event_url
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ from django.db.models import Case, OuterRef, Q, Subquery, When
|
||||
from django.db.models.functions import Cast, Coalesce
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext as _, gettext_lazy, pgettext_lazy
|
||||
from pypdf import PdfMerger
|
||||
from pypdf import PdfWriter
|
||||
|
||||
from pretix.base.exporter import BaseExporter
|
||||
from pretix.base.i18n import language
|
||||
@@ -117,7 +117,7 @@ class AllTicketsPDF(BaseExporter):
|
||||
return d
|
||||
|
||||
def render(self, form_data):
|
||||
merger = PdfMerger()
|
||||
merger = PdfWriter()
|
||||
qs = OrderPosition.objects.filter(
|
||||
order__event__in=self.events
|
||||
).prefetch_related(
|
||||
|
||||
@@ -44,7 +44,7 @@ from django.http import HttpRequest
|
||||
from django.template.loader import get_template
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from pypdf import PdfMerger
|
||||
from pypdf import PdfWriter
|
||||
|
||||
from pretix.base.i18n import language
|
||||
from pretix.base.models import Order, OrderPosition
|
||||
@@ -113,7 +113,7 @@ class PdfTicketOutput(BaseTicketOutput):
|
||||
return renderer.render_background(buffer, _('Ticket'))
|
||||
|
||||
def generate_order(self, order: Order):
|
||||
merger = PdfMerger()
|
||||
merger = PdfWriter()
|
||||
with language(order.locale, self.event.settings.region):
|
||||
for op in order.positions_with_tickets:
|
||||
layout = override_layout.send_chained(
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
from django.conf.urls import re_path
|
||||
from django.urls import re_path
|
||||
|
||||
from pretix.api.urls import event_router
|
||||
from pretix.plugins.ticketoutputpdf.api import (
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
from django.conf.urls import re_path
|
||||
from django.urls import re_path
|
||||
|
||||
from .views import IndexView
|
||||
|
||||
|
||||
@@ -514,7 +514,6 @@ class AddOnsStep(CartMixin, AsyncAction, TemplateFlowStep):
|
||||
'variation': cartpos.variation,
|
||||
'categories': []
|
||||
}
|
||||
formset.append(formsetentry)
|
||||
|
||||
current_addon_products = defaultdict(list)
|
||||
for a in cartpos.addons.all():
|
||||
@@ -592,6 +591,8 @@ class AddOnsStep(CartMixin, AsyncAction, TemplateFlowStep):
|
||||
'iao': iao,
|
||||
'items': items
|
||||
})
|
||||
if formsetentry['categories']:
|
||||
formset.append(formsetentry)
|
||||
return formset
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
from bootstrap3.renderers import FieldRenderer
|
||||
from bootstrap3.text import text_value
|
||||
from bootstrap3.utils import add_css_class
|
||||
from django.forms import CheckboxInput, CheckboxSelectMultiple, RadioSelect
|
||||
@@ -28,6 +27,8 @@ from django.utils.html import escape, format_html, strip_tags
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import pgettext
|
||||
|
||||
from pretix.base.forms.renderers import FieldRenderer
|
||||
|
||||
|
||||
def render_label(content, label_for=None, label_class=None, label_title='', label_id='', optional=False, is_valid=None, attrs=None):
|
||||
"""
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
import datetime
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import pytz
|
||||
import vobject
|
||||
from django.conf import settings
|
||||
from django.utils.formats import date_format
|
||||
@@ -41,11 +40,11 @@ def get_public_ical(events):
|
||||
"""
|
||||
cal = vobject.iCalendar()
|
||||
cal.add('prodid').value = '-//pretix//{}//'.format(settings.PRETIX_INSTANCE_NAME.replace(" ", "_"))
|
||||
creation_time = datetime.datetime.now(pytz.utc)
|
||||
creation_time = datetime.datetime.now(datetime.timezone.utc)
|
||||
|
||||
for ev in events:
|
||||
event = ev if isinstance(ev, Event) else ev.event
|
||||
tz = pytz.timezone(event.settings.timezone)
|
||||
tz = event.timezone
|
||||
if isinstance(ev, Event):
|
||||
url = build_absolute_uri(event, 'presale:event.index')
|
||||
else:
|
||||
@@ -114,9 +113,9 @@ def get_private_icals(event, positions):
|
||||
- It would be pretty hard to implement it in a way that doesn't require us to use distinct
|
||||
settings fields for emails to customers and to attendees, which feels like an overcomplication.
|
||||
"""
|
||||
tz = pytz.timezone(event.settings.timezone)
|
||||
tz = event.timezone
|
||||
|
||||
creation_time = datetime.datetime.now(pytz.utc)
|
||||
creation_time = datetime.datetime.now(datetime.timezone.utc)
|
||||
calobjects = []
|
||||
|
||||
evs = set(p.subevent or event for p in positions)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user