mirror of
https://github.com/pretix/pretix.git
synced 2026-05-14 16:44:06 +00:00
* setup vite and integrate fully with django - vite starts with `python manage.py runserver` - add templatetags to simply load vite hmr and entry points - add eslint (recheck rules) - enable non-strict ts * better syntax for cors header setting * migrate checkin rules editor to vue3 - move constants to a module - move reading from and writing to non-vue html to django interop module - switch to composition api and script setup sfc with pug - use optional chaining operators a lot to simplify code * migrate webcheckin plugin to vite+vue3 - migrate vue sfcs to script setup and pug - move fetch calls into a api.ts module - move common formatting and i18n strings into module * fix migration error * first draft migrating widget to vue3/vite * first couple widget e2e tests courtesy of claude most of the tests don't work yet * test file is not actually used * drop widget_ prefix from e2e test fixtures * add test for complete widget journey for simple event * switch timezone in e2e tests to Europe/Berlin * make dates in e2e tests relative * migrate widget bugfix #5886 * start testing event series widget * working vite widget setup for prod (untested), local dev (with or without dev server) and pytests, with flags for running the old version or the vite version * simplify e2e test iframe check * less flaky e2e tests * top level await in iife build mode is not supported, so let's do import.meta.glob instead (we just need the build step not to see await, the code doesn't actually ever get loaded because it's DEV only) * fix inconsistencies from automatic migration * Allow gradual rollout of new vite-based widget by adding urls to an allowlist that gets checked against the "Origin" http header of request fetching the widget js * add e2e tests for widget button, testing empty cart, adding specific items, and subevents * remove janky claude testts again * resolve migration TODOs: properly refocus parent on navigations * use `npm run dev:control` for the vite dev server for admin components * upgrade npm dependencies * fix js linter errors * fix python linter errors * build all control vue components * add new js config files to check-manifest ignore * working prod build acutal serving of built assets not tested yet * fix templatetag paths to match what's in the vite mantifest * add missing quotes around 'unsafe-eval' cors value * remove now unused old vue2 tooling * try fixing e2e test ci * fix flake8 error * check if vite build artefacts are in the wheel * add license headers * remove dom manipilation code necessary for `div.pretix-widget-compat` to work. No longer needed for vue3 * remove superfluous `createElement` calls They might have been there because of IE, which is no longer relevant * make widget dev mode parametizable through query params and document the usage and those params * fix rst syntax * remove migration todos file Co-authored-by: luelista <mira@teamwiki.de> * rearrange dockerfile commands for smaller image, thanks @luelista * Update .gitignore, adding .vite Co-authored-by: luelista <mira@teamwiki.de> * add eslint CI * make vue dev work in plugins * fix docker build * rebuild vite setup to support static prod plugins and dynamic hmr plugin development * use toml for vite plugin config instead of standalone json file * Add widget changes from #6047, #6149 * Allow buttons to reuse cart (Z#23226853) * Always keep cart of buttons with items set * widget: handle cart if not same-site (#6149) --------- Co-authored-by: luelista <mira@teamwiki.de> Co-authored-by: Kara Engelhardt <engelhardt@pretix.eu>
898 lines
36 KiB
Python
898 lines
36 KiB
Python
#
|
|
# This file is part of pretix (Community Edition).
|
|
#
|
|
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
|
# Copyright (C) 2020-today pretix 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/>.
|
|
#
|
|
|
|
# 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: FlaviaBastos, Jason Estibeiro, Jonas Große Sundrup,
|
|
# Laura Klünder, Matthew Emerson, Nils Schneider, Tim Freund, Tobias Kunze
|
|
#
|
|
# 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.
|
|
|
|
import configparser
|
|
import logging
|
|
import os
|
|
import sys
|
|
from json import loads
|
|
from pathlib import Path
|
|
from urllib.parse import urlparse
|
|
|
|
import importlib_metadata as metadata
|
|
from django.core.exceptions import ImproperlyConfigured
|
|
from django.utils.crypto import get_random_string
|
|
from kombu import Queue
|
|
|
|
from . import __version__
|
|
from .helpers.config import EnvOrParserConfig
|
|
|
|
# Pull in all settings that we also need at wheel require time
|
|
from ._base_settings import * # NOQA
|
|
|
|
|
|
from django.contrib.messages import constants as messages # NOQA
|
|
from django.utils.translation import gettext_lazy as _ # NOQA
|
|
|
|
_config = configparser.RawConfigParser()
|
|
if 'PRETIX_CONFIG_FILE' in os.environ:
|
|
_config.read_file(open(os.environ.get('PRETIX_CONFIG_FILE'), encoding='utf-8'))
|
|
else:
|
|
_config.read(['/etc/pretix/pretix.cfg', os.path.expanduser('~/.pretix.cfg'), 'pretix.cfg'],
|
|
encoding='utf-8')
|
|
config = EnvOrParserConfig(_config)
|
|
|
|
CONFIG_FILE = config
|
|
DATA_DIR = config.get('pretix', 'datadir', fallback=os.environ.get('DATA_DIR', 'data'))
|
|
LOG_DIR = config.get('pretix', 'logdir', fallback=os.path.join(DATA_DIR, 'logs'))
|
|
MEDIA_ROOT = os.path.join(DATA_DIR, 'media')
|
|
PROFILE_DIR = os.path.join(DATA_DIR, 'profiles')
|
|
CACHE_DIR = config.get('pretix', 'cachedir', fallback=os.path.join(DATA_DIR, 'cache'))
|
|
|
|
Path(DATA_DIR).mkdir(parents=False, exist_ok=True)
|
|
Path(LOG_DIR).mkdir(parents=False, exist_ok=True)
|
|
Path(MEDIA_ROOT).mkdir(parents=False, exist_ok=True)
|
|
Path(CACHE_DIR).mkdir(parents=False, exist_ok=True)
|
|
|
|
if config.has_option('django', 'secret'):
|
|
SECRET_KEY = config.get('django', 'secret')
|
|
else:
|
|
SECRET_FILE = os.path.join(DATA_DIR, '.secret')
|
|
if os.path.exists(SECRET_FILE):
|
|
with open(SECRET_FILE, 'r') as f:
|
|
SECRET_KEY = f.read().strip()
|
|
else:
|
|
chars = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)'
|
|
SECRET_KEY = get_random_string(50, chars)
|
|
with open(SECRET_FILE, 'w') as f:
|
|
os.chmod(SECRET_FILE, 0o600)
|
|
try:
|
|
os.chown(SECRET_FILE, os.getuid(), os.getgid())
|
|
except AttributeError:
|
|
pass # os.chown is not available on Windows
|
|
f.write(SECRET_KEY)
|
|
|
|
|
|
SECRET_KEY_FALLBACKS = []
|
|
for i in range(10):
|
|
if config.has_option('django', f'secret_fallback{i}'):
|
|
SECRET_KEY_FALLBACKS.append(config.get('django', f'secret_fallback{i}'))
|
|
|
|
|
|
# Adjustable settings
|
|
|
|
debug_fallback = "runserver" in sys.argv or "runserver_plus" in sys.argv
|
|
DEBUG = config.getboolean('django', 'debug', fallback=debug_fallback)
|
|
LOG_CSP = config.getboolean('pretix', 'csp_log', fallback=False)
|
|
CSP_ADDITIONAL_HEADER = config.get('pretix', 'csp_additional_header', fallback='')
|
|
|
|
PDFTK = config.get('tools', 'pdftk', fallback=None)
|
|
|
|
PRETIX_AUTH_BACKENDS = config.get('pretix', 'auth_backends', fallback='pretix.base.auth.NativeAuthBackend').split(',')
|
|
|
|
db_backend = config.get('database', 'backend', fallback='sqlite3')
|
|
if db_backend == 'postgresql_psycopg2':
|
|
db_backend = 'postgresql'
|
|
elif 'mysql' in db_backend:
|
|
print("pretix does no longer support running on MySQL/MariaDB")
|
|
sys.exit(1)
|
|
|
|
DATABASE_ADVISORY_LOCK_INDEX = config.getint('database', 'advisory_lock_index', fallback=0)
|
|
|
|
db_options = {}
|
|
|
|
postgresql_sslmode = config.get('database', 'sslmode', fallback='disable')
|
|
USE_DATABASE_TLS = postgresql_sslmode != 'disable'
|
|
USE_DATABASE_MTLS = USE_DATABASE_TLS and config.has_option('database', 'sslcert')
|
|
|
|
if USE_DATABASE_TLS or USE_DATABASE_MTLS:
|
|
tls_config = {}
|
|
if not USE_DATABASE_MTLS:
|
|
if 'postgresql' in db_backend:
|
|
tls_config = {
|
|
'sslmode': config.get('database', 'sslmode'),
|
|
'sslrootcert': config.get('database', 'sslrootcert'),
|
|
}
|
|
else:
|
|
if 'postgresql' in db_backend:
|
|
tls_config = {
|
|
'sslmode': config.get('database', 'sslmode'),
|
|
'sslrootcert': config.get('database', 'sslrootcert'),
|
|
'sslcert': config.get('database', 'sslcert'),
|
|
'sslkey': config.get('database', 'sslkey'),
|
|
}
|
|
|
|
db_options.update(tls_config)
|
|
|
|
db_disable_server_side_cursors = db_backend == 'postgresql' and config.getboolean('database', 'disable_server_side_cursors', fallback=False)
|
|
|
|
DATABASES = {
|
|
'default': {
|
|
'ENGINE': 'django.db.backends.' + db_backend,
|
|
'NAME': config.get('database', 'name', fallback=os.path.join(DATA_DIR, 'db.sqlite3')),
|
|
'USER': config.get('database', 'user', fallback=''),
|
|
'PASSWORD': config.get('database', 'password', fallback=''),
|
|
'HOST': config.get('database', 'host', fallback=''),
|
|
'PORT': config.get('database', 'port', fallback=''),
|
|
'CONN_MAX_AGE': 0 if db_backend == 'sqlite3' else 120,
|
|
'CONN_HEALTH_CHECKS': db_backend != 'sqlite3',
|
|
'DISABLE_SERVER_SIDE_CURSORS': db_disable_server_side_cursors,
|
|
'OPTIONS': db_options,
|
|
'TEST': {}
|
|
}
|
|
}
|
|
DATABASE_REPLICA = 'default'
|
|
if config.has_section('replica'):
|
|
DATABASE_REPLICA = 'replica'
|
|
DATABASES['replica'] = {
|
|
'ENGINE': 'django.db.backends.' + db_backend,
|
|
'NAME': config.get('replica', 'name', fallback=DATABASES['default']['NAME']),
|
|
'USER': config.get('replica', 'user', fallback=DATABASES['default']['USER']),
|
|
'PASSWORD': config.get('replica', 'password', fallback=DATABASES['default']['PASSWORD']),
|
|
'HOST': config.get('replica', 'host', fallback=DATABASES['default']['HOST']),
|
|
'PORT': config.get('replica', 'port', fallback=DATABASES['default']['PORT']),
|
|
'CONN_MAX_AGE': 0 if db_backend == 'sqlite3' else 120,
|
|
'OPTIONS': db_options,
|
|
'TEST': {}
|
|
}
|
|
DATABASE_ROUTERS = ['pretix.helpers.database.ReplicaRouter']
|
|
|
|
if config.has_section('dbreadonly'):
|
|
DATABASES['readonly'] = {
|
|
'ENGINE': 'django.db.backends.' + db_backend,
|
|
'NAME': config.get('dbreadonly', 'name', fallback=DATABASES['default']['NAME']),
|
|
'USER': config.get('dbreadonly', 'user', fallback=DATABASES['default']['USER']),
|
|
'PASSWORD': config.get('dbreadonly', 'password', fallback=DATABASES['default']['PASSWORD']),
|
|
'HOST': config.get('dbreadonly', 'host', fallback=DATABASES['default']['HOST']),
|
|
'PORT': config.get('dbreadonly', 'port', fallback=DATABASES['default']['PORT']),
|
|
'CONN_MAX_AGE': 0, # do not spam primary with open connections as long as readonly is only used occasionally
|
|
'CONN_HEALTH_CHECKS': db_backend != 'sqlite3',
|
|
'DISABLE_SERVER_SIDE_CURSORS': db_disable_server_side_cursors,
|
|
'OPTIONS': db_options,
|
|
'TEST': {}
|
|
}
|
|
|
|
STATIC_URL = config.get('urls', 'static', fallback='/static/')
|
|
|
|
MEDIA_URL = config.get('urls', 'media', fallback='/media/')
|
|
|
|
PRETIX_INSTANCE_NAME = config.get('pretix', 'instance_name', fallback='pretix.de')
|
|
PRETIX_REGISTRATION = config.getboolean('pretix', 'registration', fallback=False)
|
|
PRETIX_PASSWORD_RESET = config.getboolean('pretix', 'password_reset', fallback=True)
|
|
PRETIX_LONG_SESSIONS = config.getboolean('pretix', 'long_sessions', fallback=True)
|
|
PRETIX_ADMIN_AUDIT_COMMENTS = config.getboolean('pretix', 'audit_comments', fallback=False)
|
|
|
|
_obligatory_2fa = config.get('pretix', 'obligatory_2fa', fallback="False")
|
|
_mapping = {'1': True, 'yes': True, 'true': True, 'on': True, '0': False, 'no': False, 'false': False, 'off': False, 'staff': 'staff'}
|
|
if _obligatory_2fa.lower() not in _mapping:
|
|
raise ImproperlyConfigured(
|
|
f"Value '{_obligatory_2fa}' not allowed for configuration key pretix.obligatory_2fa."
|
|
)
|
|
PRETIX_OBLIGATORY_2FA = _mapping[_obligatory_2fa.lower()]
|
|
|
|
PRETIX_SESSION_TIMEOUT_RELATIVE = 3600 * 3
|
|
PRETIX_SESSION_TIMEOUT_ABSOLUTE = 3600 * 12
|
|
|
|
SITE_URL = config.get('pretix', 'url', fallback='http://localhost:8000')
|
|
if SITE_URL.endswith('/'):
|
|
SITE_URL = SITE_URL[:-1]
|
|
|
|
CSRF_TRUSTED_ORIGINS = [urlparse(SITE_URL).scheme + '://' + urlparse(SITE_URL).hostname]
|
|
|
|
TRUST_X_FORWARDED_FOR = config.getboolean('pretix', 'trust_x_forwarded_for', fallback=False)
|
|
USE_X_FORWARDED_HOST = config.getboolean('pretix', 'trust_x_forwarded_host', fallback=False)
|
|
ALLOW_HTTP_TO_PRIVATE_NETWORKS = config.getboolean('pretix', 'allow_http_to_private_networks', fallback=False)
|
|
|
|
|
|
REQUEST_ID_HEADER = config.get('pretix', 'request_id_header', fallback=False)
|
|
if REQUEST_ID_HEADER in config.cp.BOOLEAN_STATES:
|
|
raise ImproperlyConfigured(
|
|
"request_id_header should be set to a header name, not a boolean value."
|
|
)
|
|
|
|
if config.getboolean('pretix', 'trust_x_forwarded_proto', fallback=False):
|
|
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
|
|
|
|
PRETIX_PLUGINS_DEFAULT = config.get('pretix', 'plugins_default',
|
|
fallback='pretix.plugins.sendmail,pretix.plugins.statistics,pretix.plugins.checkinlists')
|
|
PRETIX_PLUGINS_ORGANIZER_DEFAULT = config.get('pretix', 'plugins_organizer_default',
|
|
fallback='')
|
|
PRETIX_PLUGINS_EXCLUDE = config.get('pretix', 'plugins_exclude', fallback='').split(',')
|
|
PRETIX_PLUGINS_SHOW_META = config.getboolean('pretix', 'plugins_show_meta', fallback=True)
|
|
|
|
FETCH_ECB_RATES = config.getboolean('pretix', 'ecb_rates', fallback=True)
|
|
|
|
DEFAULT_CURRENCY = config.get('pretix', 'currency', fallback='EUR')
|
|
|
|
ALLOWED_HOSTS = ['*']
|
|
|
|
LANGUAGE_CODE = config.get('locale', 'default', fallback='en')
|
|
TIME_ZONE = config.get('locale', 'timezone', fallback='UTC')
|
|
|
|
MAIL_FROM = SERVER_EMAIL = DEFAULT_FROM_EMAIL = config.get('mail', 'from', fallback='pretix@localhost')
|
|
MAIL_FROM_NOTIFICATIONS = config.get('mail', 'from_notifications', fallback=MAIL_FROM)
|
|
MAIL_FROM_ORGANIZERS = config.get('mail', 'from_organizers', fallback=MAIL_FROM)
|
|
MAIL_CUSTOM_SENDER_VERIFICATION_REQUIRED = config.getboolean('mail', 'custom_sender_verification_required', fallback=True)
|
|
MAIL_CUSTOM_SENDER_SPF_STRING = config.get('mail', 'custom_sender_spf_string', fallback='')
|
|
MAIL_CUSTOM_SMTP_ALLOW_PRIVATE_NETWORKS = config.getboolean('mail', 'custom_smtp_allow_private_networks', fallback=DEBUG)
|
|
EMAIL_HOST = config.get('mail', 'host', fallback='localhost')
|
|
EMAIL_PORT = config.getint('mail', 'port', fallback=25)
|
|
EMAIL_HOST_USER = config.get('mail', 'user', fallback='')
|
|
EMAIL_HOST_PASSWORD = config.get('mail', 'password', fallback='')
|
|
EMAIL_USE_TLS = config.getboolean('mail', 'tls', fallback=False)
|
|
EMAIL_USE_SSL = config.getboolean('mail', 'ssl', fallback=False)
|
|
EMAIL_SUBJECT_PREFIX = '[pretix] '
|
|
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
|
EMAIL_CUSTOM_SMTP_BACKEND = 'pretix.base.email.CheckPrivateNetworkSmtpBackend'
|
|
EMAIL_TIMEOUT = 60
|
|
|
|
ADMINS = [('Admin', n) for n in config.get('mail', 'admins', fallback='').split(",") if n]
|
|
|
|
METRICS_ENABLED = config.getboolean('metrics', 'enabled', fallback=False)
|
|
METRICS_USER = config.get('metrics', 'user', fallback="metrics")
|
|
METRICS_PASSPHRASE = config.get('metrics', 'passphrase', fallback="")
|
|
|
|
CACHES = {
|
|
'default': {
|
|
'BACKEND': 'pretix.helpers.cache.CustomDummyCache',
|
|
}
|
|
}
|
|
REAL_CACHE_USED = False
|
|
SESSION_ENGINE = None
|
|
|
|
HAS_MEMCACHED = config.has_option('memcached', 'location')
|
|
if HAS_MEMCACHED:
|
|
REAL_CACHE_USED = True
|
|
CACHES['default'] = {
|
|
'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
|
|
'LOCATION': config.get('memcached', 'location'),
|
|
}
|
|
|
|
HAS_REDIS = config.has_option('redis', 'location')
|
|
USE_REDIS_SENTINEL = config.has_option('redis', 'sentinels')
|
|
redis_ssl_cert_reqs = config.get('redis', 'ssl_cert_reqs', fallback='none')
|
|
USE_REDIS_TLS = redis_ssl_cert_reqs != 'none'
|
|
USE_REDIS_MTLS = USE_REDIS_TLS and config.has_option('redis', 'ssl_certfile')
|
|
HAS_REDIS_PASSWORD = config.has_option('redis', 'password')
|
|
if HAS_REDIS:
|
|
OPTIONS = {
|
|
"CLIENT_CLASS": "django_redis.client.DefaultClient",
|
|
"REDIS_CLIENT_KWARGS": {"health_check_interval": 30}
|
|
}
|
|
|
|
if USE_REDIS_SENTINEL:
|
|
DJANGO_REDIS_CONNECTION_FACTORY = "django_redis.pool.SentinelConnectionFactory"
|
|
OPTIONS["CLIENT_CLASS"] = "django_redis.client.SentinelClient"
|
|
OPTIONS["CONNECTION_POOL_CLASS"] = "redis.sentinel.SentinelConnectionPool"
|
|
# See https://github.com/jazzband/django-redis/issues/540
|
|
OPTIONS["SENTINEL_KWARGS"] = {"socket_timeout": 1}
|
|
OPTIONS["SENTINELS"] = [tuple(sentinel) for sentinel in loads(config.get('redis', 'sentinels'))]
|
|
|
|
if USE_REDIS_TLS or USE_REDIS_MTLS:
|
|
tls_config = {}
|
|
if not USE_REDIS_MTLS:
|
|
tls_config = {
|
|
'ssl_cert_reqs': config.get('redis', 'ssl_cert_reqs'),
|
|
'ssl_ca_certs': config.get('redis', 'ssl_ca_certs'),
|
|
}
|
|
else:
|
|
tls_config = {
|
|
'ssl_cert_reqs': config.get('redis', 'ssl_cert_reqs'),
|
|
'ssl_ca_certs': config.get('redis', 'ssl_ca_certs'),
|
|
'ssl_keyfile': config.get('redis', 'ssl_keyfile'),
|
|
'ssl_certfile': config.get('redis', 'ssl_certfile'),
|
|
}
|
|
|
|
if USE_REDIS_SENTINEL is False:
|
|
# The CONNECTION_POOL_KWARGS option is necessary for self-signed certs. For further details, please check
|
|
# https://github.com/jazzband/django-redis/issues/554#issuecomment-949498321
|
|
OPTIONS["CONNECTION_POOL_KWARGS"] = tls_config
|
|
OPTIONS["REDIS_CLIENT_KWARGS"].update(tls_config)
|
|
else:
|
|
OPTIONS["SENTINEL_KWARGS"].update(tls_config)
|
|
|
|
if HAS_REDIS_PASSWORD:
|
|
OPTIONS["PASSWORD"] = config.get('redis', 'password')
|
|
|
|
CACHES['redis'] = {
|
|
"BACKEND": "django_redis.cache.RedisCache",
|
|
"LOCATION": config.get('redis', 'location'),
|
|
"OPTIONS": OPTIONS
|
|
}
|
|
CACHES['redis_sessions'] = {
|
|
"BACKEND": "django_redis.cache.RedisCache",
|
|
"LOCATION": config.get('redis', 'location'),
|
|
"TIMEOUT": 3600 * 24 * 30,
|
|
"OPTIONS": OPTIONS
|
|
}
|
|
if not HAS_MEMCACHED:
|
|
CACHES['default'] = CACHES['redis']
|
|
REAL_CACHE_USED = True
|
|
if config.getboolean('redis', 'sessions', fallback=False):
|
|
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
|
|
SESSION_CACHE_ALIAS = "redis_sessions"
|
|
|
|
if not SESSION_ENGINE:
|
|
if REAL_CACHE_USED:
|
|
SESSION_ENGINE = "django.contrib.sessions.backends.cached_db"
|
|
else:
|
|
SESSION_ENGINE = "django.contrib.sessions.backends.db"
|
|
|
|
HAS_CELERY = config.has_option('celery', 'broker')
|
|
HAS_CELERY_BROKER_TRANSPORT_OPTS = config.has_option('celery', 'broker_transport_options')
|
|
HAS_CELERY_BACKEND_TRANSPORT_OPTS = config.has_option('celery', 'backend_transport_options')
|
|
if HAS_CELERY:
|
|
CELERY_BROKER_URL = config.get('celery', 'broker')
|
|
CELERY_RESULT_BACKEND = config.get('celery', 'backend')
|
|
if HAS_CELERY_BROKER_TRANSPORT_OPTS:
|
|
CELERY_BROKER_TRANSPORT_OPTIONS = loads(config.get('celery', 'broker_transport_options'))
|
|
else:
|
|
CELERY_BROKER_TRANSPORT_OPTIONS = {}
|
|
if HAS_CELERY_BACKEND_TRANSPORT_OPTS:
|
|
CELERY_RESULT_BACKEND_TRANSPORT_OPTIONS = loads(config.get('celery', 'backend_transport_options'))
|
|
CELERY_BROKER_CONNECTION_RETRY_ON_STARTUP = True
|
|
|
|
if CELERY_BROKER_URL.startswith("amqp://"):
|
|
# https://docs.celeryq.dev/en/latest/userguide/routing.html#routing-options-rabbitmq-priorities
|
|
# Enable priorities for all queues
|
|
CELERY_TASK_QUEUE_MAX_PRIORITY = 3
|
|
# On RabbitMQ, higher number is higher priority, and having less levels makes rabbitmq use less CPU and RAM
|
|
PRIORITY_CELERY_LOW = 1
|
|
PRIORITY_CELERY_MID = 2
|
|
PRIORITY_CELERY_HIGH = 3
|
|
PRIORITY_CELERY_LOWEST_FUNC = min
|
|
PRIORITY_CELERY_HIGHEST_FUNC = max
|
|
# Set default
|
|
CELERY_TASK_DEFAULT_PRIORITY = PRIORITY_CELERY_MID
|
|
elif CELERY_BROKER_URL.startswith("redis://"):
|
|
# https://docs.celeryq.dev/en/latest/userguide/routing.html#redis-message-priorities
|
|
CELERY_BROKER_TRANSPORT_OPTIONS.update({
|
|
"queue_order_strategy": "priority",
|
|
"sep": ":",
|
|
"priority_steps": [0, 4, 8]
|
|
})
|
|
# On redis, lower number is higher priority, and it appears that there are always levels 0-9 even though it
|
|
# is only really executed based on the 3 steps listed above.
|
|
PRIORITY_CELERY_LOW = 9
|
|
PRIORITY_CELERY_MID = 5
|
|
PRIORITY_CELERY_HIGH = 0
|
|
PRIORITY_CELERY_LOWEST_FUNC = max
|
|
PRIORITY_CELERY_HIGHEST_FUNC = min
|
|
CELERY_TASK_DEFAULT_PRIORITY = PRIORITY_CELERY_MID
|
|
else:
|
|
# No priority support assumed
|
|
PRIORITY_CELERY_LOW = 0
|
|
PRIORITY_CELERY_MID = 0
|
|
PRIORITY_CELERY_HIGH = 0
|
|
PRIORITY_CELERY_LOWEST_FUNC = min
|
|
PRIORITY_CELERY_HIGHEST_FUNC = max
|
|
else:
|
|
CELERY_TASK_ALWAYS_EAGER = True
|
|
PRIORITY_CELERY_LOW = 0
|
|
PRIORITY_CELERY_MID = 0
|
|
PRIORITY_CELERY_HIGH = 0
|
|
PRIORITY_CELERY_LOWEST_FUNC = min
|
|
PRIORITY_CELERY_HIGHEST_FUNC = max
|
|
|
|
CACHE_TICKETS_HOURS = config.getint('cache', 'tickets', fallback=24 * 3)
|
|
|
|
ENTROPY = {
|
|
'order_code': config.getint('entropy', 'order_code', fallback=5),
|
|
'customer_identifier': config.getint('entropy', 'customer_identifier', fallback=7),
|
|
'ticket_secret': config.getint('entropy', 'ticket_secret', fallback=32),
|
|
'voucher_code': config.getint('entropy', 'voucher_code', fallback=16),
|
|
'giftcard_secret': config.getint('entropy', 'giftcard_secret', fallback=12),
|
|
}
|
|
|
|
HAS_GEOIP = False
|
|
if config.has_option('geoip', 'path'):
|
|
HAS_GEOIP = True
|
|
GEOIP_PATH = config.get('geoip', 'path')
|
|
GEOIP_COUNTRY = config.get('geoip', 'filename_country', fallback='GeoLite2-Country.mmdb')
|
|
|
|
# Internal settings
|
|
SESSION_COOKIE_NAME = 'pretix_session'
|
|
LANGUAGE_COOKIE_NAME = 'pretix_language'
|
|
CSRF_COOKIE_NAME = 'pretix_csrftoken'
|
|
SESSION_COOKIE_HTTPONLY = True
|
|
|
|
INSTALLED_APPS += [ # noqa
|
|
'django_filters',
|
|
'django_markup',
|
|
'django_otp',
|
|
'django_otp.plugins.otp_totp',
|
|
'django_otp.plugins.otp_static',
|
|
'hijack',
|
|
'localflavor',
|
|
]
|
|
|
|
if db_backend == 'postgresql':
|
|
# ALlow plugins to use django.contrib.postgres
|
|
INSTALLED_APPS.insert(0, 'django.contrib.postgres')
|
|
|
|
try:
|
|
import django_extensions # noqa
|
|
INSTALLED_APPS.append('django_extensions')
|
|
except ImportError:
|
|
pass
|
|
|
|
PLUGINS = []
|
|
for entry_point in metadata.entry_points(group='pretix.plugin'):
|
|
if entry_point.module in PRETIX_PLUGINS_EXCLUDE:
|
|
continue
|
|
PLUGINS.append(entry_point.module)
|
|
INSTALLED_APPS.append(entry_point.module)
|
|
|
|
HIJACK_PERMISSION_CHECK = "hijack.permissions.superusers_and_staff"
|
|
HIJACK_INSERT_BEFORE = None
|
|
|
|
|
|
REST_FRAMEWORK = {
|
|
'DEFAULT_PERMISSION_CLASSES': [
|
|
'pretix.api.auth.permission.EventPermission',
|
|
],
|
|
'DEFAULT_PAGINATION_CLASS': 'pretix.api.pagination.Pagination',
|
|
'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.NamespaceVersioning',
|
|
'PAGE_SIZE': 50,
|
|
'DEFAULT_AUTHENTICATION_CLASSES': (
|
|
'pretix.api.auth.token.TeamTokenAuthentication',
|
|
'pretix.api.auth.device.DeviceTokenAuthentication',
|
|
'pretix.api.auth.session.SessionAuthentication',
|
|
'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
|
|
),
|
|
'DEFAULT_RENDERER_CLASSES': (
|
|
'drf_ujson.renderers.UJSONRenderer',
|
|
),
|
|
'DEFAULT_PARSER_CLASSES': (
|
|
'drf_ujson.parsers.UJSONParser',
|
|
'rest_framework.parsers.FormParser',
|
|
'rest_framework.parsers.MultiPartParser'
|
|
),
|
|
'TEST_REQUEST_RENDERER_CLASSES': [
|
|
'rest_framework.renderers.MultiPartRenderer',
|
|
'rest_framework.renderers.JSONRenderer',
|
|
'pretix.testutils.api.UploadRenderer',
|
|
],
|
|
'EXCEPTION_HANDLER': 'pretix.api.exception.custom_exception_handler',
|
|
'UNICODE_JSON': False
|
|
}
|
|
|
|
|
|
MIDDLEWARE = [
|
|
'pretix.helpers.logs.RequestIdMiddleware',
|
|
'pretix.api.middleware.IdempotencyMiddleware',
|
|
'pretix.multidomain.middlewares.MultiDomainMiddleware',
|
|
'pretix.base.middleware.CustomCommonMiddleware',
|
|
'pretix.multidomain.middlewares.SessionMiddleware',
|
|
'pretix.multidomain.middlewares.CsrfViewMiddleware',
|
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
|
'django.contrib.messages.middleware.MessageMiddleware',
|
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
|
'hijack.middleware.HijackUserMiddleware',
|
|
'pretix.control.middleware.PermissionMiddleware',
|
|
'pretix.control.middleware.AuditLogMiddleware',
|
|
'pretix.base.middleware.LocaleMiddleware',
|
|
'pretix.base.middleware.SecurityMiddleware',
|
|
'pretix.base.middleware.RejectInvalidInputMiddleware',
|
|
'pretix.presale.middleware.EventMiddleware',
|
|
'pretix.api.middleware.ApiScopeMiddleware',
|
|
]
|
|
|
|
try:
|
|
import debug_toolbar.settings # noqa
|
|
if DEBUG:
|
|
INSTALLED_APPS.append('debug_toolbar.apps.DebugToolbarConfig')
|
|
MIDDLEWARE.insert(0, 'debug_toolbar.middleware.DebugToolbarMiddleware')
|
|
DEBUG_TOOLBAR_PATCH_SETTINGS = False
|
|
DEBUG_TOOLBAR_CONFIG = {
|
|
'JQUERY_URL': '',
|
|
'DISABLE_PANELS': debug_toolbar.settings.PANELS_DEFAULTS,
|
|
}
|
|
pass
|
|
except ImportError:
|
|
pass
|
|
|
|
|
|
if METRICS_ENABLED:
|
|
MIDDLEWARE.insert(MIDDLEWARE.index('pretix.base.middleware.CustomCommonMiddleware') + 1,
|
|
'pretix.helpers.metrics.middleware.MetricsMiddleware')
|
|
|
|
|
|
PROFILING_RATE = config.getfloat('django', 'profile', fallback=0) # Percentage of requests to profile
|
|
if PROFILING_RATE > 0:
|
|
if not os.path.exists(PROFILE_DIR):
|
|
os.mkdir(PROFILE_DIR)
|
|
MIDDLEWARE.insert(0, 'pretix.helpers.profile.middleware.CProfileMiddleware')
|
|
|
|
|
|
# Security settings
|
|
X_FRAME_OPTIONS = 'DENY'
|
|
|
|
# URL settings
|
|
ROOT_URLCONF = 'pretix.multidomain.maindomain_urlconf'
|
|
FORMS_URLFIELD_ASSUME_HTTPS = True # transitional for django 6.0
|
|
|
|
WSGI_APPLICATION = 'pretix.wsgi.application'
|
|
|
|
if config.has_option('languages', 'path'):
|
|
LOCALE_PATHS.insert(0, config.get('languages', 'path')) # noqa
|
|
|
|
LANGUAGES_INCUBATING = LANGUAGES_INCUBATING - set(config.get('languages', 'allow_incubating', fallback='').split(',')) # noqa
|
|
LANGUAGES = []
|
|
LANGUAGES_ENABLED = [lang for lang in config.get("languages", "enabled", fallback='').split(',') if lang]
|
|
for k, v in ALL_LANGUAGES: # noqa
|
|
if not DEBUG and k in LANGUAGES_INCUBATING:
|
|
continue
|
|
if LANGUAGES_ENABLED and k not in LANGUAGES_ENABLED:
|
|
continue
|
|
LANGUAGES.append((k, v))
|
|
|
|
if LANGUAGE_CODE not in {l[0] for l in LANGUAGES}:
|
|
raise ImproperlyConfigured(
|
|
f"Default language {LANGUAGE_CODE} is not one of the available and enabled languages."
|
|
)
|
|
|
|
|
|
AUTH_USER_MODEL = 'pretixbase.User'
|
|
LOGIN_URL = 'control:auth.login'
|
|
LOGIN_URL_CONTROL = 'control:auth.login'
|
|
CSRF_FAILURE_VIEW = 'pretix.base.views.errors.csrf_failure'
|
|
|
|
template_loaders = (
|
|
'django.template.loaders.filesystem.Loader',
|
|
'pretix.helpers.template_loaders.AppLoader',
|
|
)
|
|
if not DEBUG:
|
|
TEMPLATES[0]['OPTIONS']['loaders'] = ( # noqa
|
|
('django.template.loaders.cached.Loader', template_loaders),
|
|
)
|
|
TEMPLATES[0]['DIRS'].insert(0, os.path.join(DATA_DIR, 'templates')) # noqa
|
|
|
|
INTERNAL_IPS = ('127.0.0.1', '::1')
|
|
|
|
MESSAGE_TAGS = {
|
|
messages.INFO: 'alert-info',
|
|
messages.ERROR: 'alert-danger',
|
|
messages.WARNING: 'alert-warning',
|
|
messages.SUCCESS: 'alert-success',
|
|
}
|
|
MESSAGE_STORAGE = 'django.contrib.messages.storage.session.SessionStorage'
|
|
|
|
loglevel = 'DEBUG' if DEBUG else config.get('pretix', 'loglevel', fallback='INFO')
|
|
|
|
COMPRESS_ENABLED = COMPRESS_OFFLINE = not debug_fallback
|
|
|
|
LOGGING = {
|
|
'version': 1,
|
|
'disable_existing_loggers': False,
|
|
'formatters': {
|
|
'default': {
|
|
'format': (
|
|
'%(levelname)s %(asctime)s RequestId=%(request_id)s %(name)s %(module)s %(message)s'
|
|
if REQUEST_ID_HEADER
|
|
else '%(levelname)s %(asctime)s %(name)s %(module)s %(message)s'
|
|
)
|
|
},
|
|
},
|
|
'filters': {
|
|
'require_admin_enabled': {
|
|
'()': 'pretix.helpers.logs.AdminExistsFilter',
|
|
},
|
|
'request_id': {
|
|
'()': 'pretix.helpers.logs.RequestIdFilter'
|
|
},
|
|
},
|
|
'handlers': {
|
|
'console': {
|
|
'level': loglevel,
|
|
'class': 'logging.StreamHandler',
|
|
'formatter': 'default',
|
|
'filters': ['request_id'],
|
|
},
|
|
'csp_file': {
|
|
'level': loglevel,
|
|
'class': 'logging.FileHandler',
|
|
'filename': os.path.join(LOG_DIR, 'csp.log'),
|
|
'formatter': 'default',
|
|
'filters': ['request_id'],
|
|
},
|
|
'file': {
|
|
'level': loglevel,
|
|
'class': 'logging.FileHandler',
|
|
'filename': os.path.join(LOG_DIR, 'pretix.log'),
|
|
'formatter': 'default',
|
|
'filters': ['request_id'],
|
|
},
|
|
'mail_admins': {
|
|
'level': 'ERROR',
|
|
'class': 'django.utils.log.AdminEmailHandler',
|
|
'filters': ['require_admin_enabled']
|
|
},
|
|
'null': {
|
|
'class': 'logging.NullHandler',
|
|
},
|
|
},
|
|
'loggers': {
|
|
'': {
|
|
'handlers': ['file', 'console'],
|
|
'level': loglevel,
|
|
'propagate': True,
|
|
},
|
|
'django.request': {
|
|
'handlers': ['file', 'console', 'mail_admins'],
|
|
'level': loglevel,
|
|
'propagate': True,
|
|
},
|
|
'pretix.security.csp': {
|
|
'handlers': ['csp_file'],
|
|
'level': loglevel,
|
|
'propagate': False,
|
|
},
|
|
'django.security': {
|
|
'handlers': ['file', 'console', 'mail_admins'],
|
|
'level': loglevel,
|
|
'propagate': True,
|
|
},
|
|
'django.security.DisallowedHost': {
|
|
'handlers': ['null'],
|
|
'propagate': False,
|
|
},
|
|
'celery.utils.functional': {
|
|
'handlers': ['file', 'console'],
|
|
'level': 'INFO', # Do not output all the queries
|
|
'propagate': False,
|
|
},
|
|
'django.db.backends': {
|
|
'handlers': ['file', 'console'],
|
|
'level': 'INFO', # Do not output all the queries
|
|
'propagate': False,
|
|
},
|
|
'asyncio': {
|
|
'handlers': ['file', 'console'],
|
|
'level': 'WARNING',
|
|
},
|
|
},
|
|
}
|
|
|
|
SENTRY_ENABLED = False
|
|
if config.has_option('sentry', 'dsn') and not any(c in sys.argv for c in ('shell', 'shell_scoped', 'shell_plus')):
|
|
import django.db.models.signals
|
|
import sentry_sdk
|
|
from sentry_sdk.integrations.celery import CeleryIntegration
|
|
from sentry_sdk.integrations.logging import (
|
|
LoggingIntegration, ignore_logger,
|
|
)
|
|
from sentry_sdk.scrubber import EventScrubber, DEFAULT_DENYLIST
|
|
|
|
from .sentry import PretixSentryIntegration, setup_custom_filters
|
|
|
|
SENTRY_TOKEN = config.get('sentry', 'traces_sample_token', fallback='')
|
|
pretix_denylist = DEFAULT_DENYLIST + [
|
|
"access_token",
|
|
"sentry_dsn",
|
|
]
|
|
|
|
def traces_sampler(sampling_context):
|
|
qs = sampling_context.get('wsgi_environ', {}).get('QUERY_STRING', '')
|
|
if SENTRY_TOKEN and SENTRY_TOKEN in qs:
|
|
return 1.0
|
|
return config.getfloat('sentry', 'traces_sample_rate', fallback=0.0)
|
|
|
|
SENTRY_ENABLED = True
|
|
sentry_sdk.init(
|
|
dsn=config.get('sentry', 'dsn'),
|
|
integrations=[
|
|
PretixSentryIntegration(
|
|
signals_denylist=[
|
|
django.db.models.signals.pre_init,
|
|
django.db.models.signals.post_init,
|
|
]
|
|
),
|
|
CeleryIntegration(),
|
|
LoggingIntegration(
|
|
level=logging.INFO,
|
|
event_level=logging.CRITICAL
|
|
)
|
|
],
|
|
traces_sampler=traces_sampler,
|
|
environment=urlparse(SITE_URL).netloc,
|
|
release=__version__,
|
|
event_scrubber=EventScrubber(denylist=pretix_denylist, recursive=True),
|
|
send_default_pii=False,
|
|
propagate_traces=False, # see https://github.com/getsentry/sentry-python/issues/1717
|
|
)
|
|
ignore_logger('pretix.base.tasks')
|
|
ignore_logger('django.security.DisallowedHost')
|
|
setup_custom_filters()
|
|
|
|
CELERY_TASK_SERIALIZER = 'json'
|
|
CELERY_RESULT_SERIALIZER = 'json'
|
|
CELERY_TASK_DEFAULT_QUEUE = 'default'
|
|
CELERY_TASK_QUEUES = (
|
|
Queue('default', routing_key='default.#'),
|
|
Queue('checkout', routing_key='checkout.#'),
|
|
Queue('mail', routing_key='mail.#'),
|
|
Queue('background', routing_key='background.#'),
|
|
Queue('notifications', routing_key='notifications.#'),
|
|
)
|
|
CELERY_TASK_ROUTES = ([
|
|
('pretix.base.services.cart.*', {'queue': 'checkout'}),
|
|
('pretix.base.services.export.scheduled_organizer_export', {'queue': 'background'}),
|
|
('pretix.base.services.export.scheduled_event_export', {'queue': 'background'}),
|
|
('pretix.base.services.orders.*', {'queue': 'checkout'}),
|
|
('pretix.base.services.mail.*', {'queue': 'mail'}),
|
|
('pretix.base.services.update_check.*', {'queue': 'background'}),
|
|
('pretix.base.services.quotas.*', {'queue': 'background'}),
|
|
('pretix.base.services.waitinglist.*', {'queue': 'background'}),
|
|
('pretix.base.services.notifications.*', {'queue': 'notifications'}),
|
|
('pretix.api.webhooks.*', {'queue': 'notifications'}),
|
|
('pretix.presale.style.*', {'queue': 'background'}),
|
|
('pretix.plugins.banktransfer.*', {'queue': 'background'}),
|
|
],)
|
|
|
|
BOOTSTRAP3 = {
|
|
'success_css_class': '',
|
|
'field_renderers': {
|
|
'default': 'bootstrap3.renderers.FieldRenderer',
|
|
'inline': 'bootstrap3.renderers.InlineFieldRenderer',
|
|
'control': 'pretix.control.forms.renderers.ControlFieldRenderer',
|
|
'control_with_visibility': 'pretix.control.forms.renderers.ControlFieldWithVisibilityRenderer',
|
|
'bulkedit': 'pretix.control.forms.renderers.BulkEditFieldRenderer',
|
|
'bulkedit_inline': 'pretix.control.forms.renderers.InlineBulkEditFieldRenderer',
|
|
'checkout': 'pretix.presale.forms.renderers.CheckoutFieldRenderer',
|
|
},
|
|
'set_placeholder': False,
|
|
}
|
|
|
|
PASSWORD_HASHERS = [
|
|
# Note that when updating this, all user passwords will be re-hashed on next login, however,
|
|
# the HistoricPassword model will not be changed automatically. In case a serious issue with a hasher
|
|
# comes to light, dropping the contents of the HistoricPassword table might be the more risk-adequate
|
|
# decision.
|
|
*(
|
|
["django.contrib.auth.hashers.Argon2PasswordHasher"]
|
|
if config.getboolean('django', 'passwords_argon2', fallback=True)
|
|
else []
|
|
),
|
|
"django.contrib.auth.hashers.PBKDF2PasswordHasher",
|
|
"django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher",
|
|
"django.contrib.auth.hashers.BCryptSHA256PasswordHasher",
|
|
"django.contrib.auth.hashers.ScryptPasswordHasher",
|
|
]
|
|
AUTH_PASSWORD_VALIDATORS = [
|
|
{
|
|
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
|
},
|
|
{
|
|
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
|
|
"OPTIONS": {
|
|
# To fulfill per PCI DSS requirement 8.3.6
|
|
"min_length": 12,
|
|
},
|
|
},
|
|
{
|
|
# To fulfill per PCI DSS requirement 8.3.6
|
|
'NAME': 'pretix.base.auth.NumericAndAlphabeticPasswordValidator',
|
|
},
|
|
{
|
|
"NAME": "pretix.base.auth.HistoryPasswordValidator",
|
|
"OPTIONS": {
|
|
# To fulfill per PCI DSS requirement 8.3.7
|
|
"history_length": 4,
|
|
},
|
|
},
|
|
{
|
|
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
|
},
|
|
{
|
|
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
|
},
|
|
]
|
|
CUSTOMER_AUTH_PASSWORD_VALIDATORS = [
|
|
# For customer accounts, we apply a little less strict requirements to provide a risk-adequate
|
|
# user experience.
|
|
{
|
|
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
|
},
|
|
{
|
|
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
|
|
"OPTIONS": {
|
|
"min_length": 8,
|
|
},
|
|
},
|
|
{
|
|
'NAME': 'pretix.base.auth.NumericAndAlphabeticPasswordValidator',
|
|
},
|
|
{
|
|
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
|
},
|
|
{
|
|
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
|
},
|
|
]
|
|
OAUTH2_PROVIDER_APPLICATION_MODEL = 'pretixapi.OAuthApplication'
|
|
OAUTH2_PROVIDER_GRANT_MODEL = 'pretixapi.OAuthGrant'
|
|
OAUTH2_PROVIDER_ACCESS_TOKEN_MODEL = 'pretixapi.OAuthAccessToken'
|
|
OAUTH2_PROVIDER_ID_TOKEN_MODEL = 'pretixapi.OAuthIDToken'
|
|
OAUTH2_PROVIDER_REFRESH_TOKEN_MODEL = 'pretixapi.OAuthRefreshToken'
|
|
OAUTH2_PROVIDER = {
|
|
'SCOPES': {
|
|
'profile': _('User profile only'),
|
|
'read': _('Read access'),
|
|
'write': _('Write access'),
|
|
},
|
|
'OAUTH2_VALIDATOR_CLASS': 'pretix.api.oauth.Validator',
|
|
'ALLOWED_REDIRECT_URI_SCHEMES': ['https'] if not DEBUG else ['http', 'https'],
|
|
'ACCESS_TOKEN_EXPIRE_SECONDS': 3600 * 24,
|
|
'ROTATE_REFRESH_TOKEN': False,
|
|
'PKCE_REQUIRED': False,
|
|
'OIDC_RESPONSE_TYPES_SUPPORTED': ["code"], # We don't support proper OIDC for now
|
|
}
|
|
|
|
COUNTRIES_OVERRIDE = {
|
|
'XK': _('Kosovo'),
|
|
}
|
|
|
|
DATA_UPLOAD_MAX_NUMBER_FIELDS = 25000
|
|
DATA_UPLOAD_MAX_MEMORY_SIZE = 10 * 1024 * 1024 # 10 MB
|
|
|
|
OUTGOING_MAIL_RETENTION = 14 * 24 * 3600 # 14 days in seonds
|
|
|
|
# File sizes are in MiB
|
|
FILE_UPLOAD_MAX_SIZE_IMAGE = 1024 * 1024 * config.getint("pretix_file_upload", "max_size_image", fallback=10)
|
|
FILE_UPLOAD_MAX_SIZE_FAVICON = 1024 * 1024 * config.getint("pretix_file_upload", "max_size_favicon", fallback=1)
|
|
FILE_UPLOAD_MAX_SIZE_EMAIL_ATTACHMENT = 1024 * 1024 * config.getint("pretix_file_upload", "max_size_email_attachment", fallback=5)
|
|
FILE_UPLOAD_MAX_SIZE_EMAIL_AUTO_ATTACHMENT = 1024 * 1024 * config.getint("pretix_file_upload", "max_size_email_auto_attachment", fallback=1)
|
|
FILE_UPLOAD_MAX_SIZE_OTHER = 1024 * 1024 * config.getint("pretix_file_upload", "max_size_other", fallback=10)
|
|
|
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
|
|
|
|
|
VITE_DEV_SERVER_PORT = 5173
|
|
VITE_DEV_SERVER = f"http://localhost:{VITE_DEV_SERVER_PORT}"
|
|
VITE_DEV_MODE = DEBUG
|
|
VITE_IGNORE = False # Used to ignore `collectstatic`/`rebuild`
|
|
PRETIX_WIDGET_VITE = os.environ.get('PRETIX_WIDGET_VITE', '') not in ('', '0')
|