Compare commits

..

50 Commits

Author SHA1 Message Date
Mira Weller
3cee5f524b use slicksort for reordering questionnaires and questions 2026-05-05 18:12:21 +02:00
Mira Weller
3a1a6b988e improve i18n, implement "add text block" dialog 2026-04-28 19:23:35 +02:00
Mira Weller
bdc720f5a2 wip 2026-03-27 18:57:22 +01:00
Mira Weller
583184af38 change existing views 2026-03-19 21:49:20 +01:00
Mira Weller
63d9f7cea8 adapt vue app to new data model (wip) 2026-03-19 21:49:20 +01:00
Mira Weller
c3d6fb1bd6 add Questionnaire and QuestionnaireChild models and their API; migrate existing data 2026-03-19 21:49:20 +01:00
Mira Weller
bb43acd257 vite config: add questionnaires app 2026-03-19 13:52:28 +01:00
Mira Weller
e0744951e8 vite config: allow cors access from pretix.work subdomains 2026-03-19 13:52:18 +01:00
Mira Weller
fef1e356f7 fix out-of-the-box experience 2026-03-19 13:50:37 +01:00
Mira Weller
81775f5d2d setup questionnaires vue app, add code from question editor proof of concept 2026-03-19 13:50:37 +01:00
Mira Weller
4bbcc398cc Merge remote-tracking branch 'refs/remotes/origin/master' into questions-vue3 2026-03-19 13:13:32 +01:00
rash
a924d0a266 add license headers 2026-03-17 14:17:15 +01:00
rash
0754e9d049 check if vite build artefacts are in the wheel 2026-03-17 14:08:37 +01:00
rash
0ebdab5389 fix flake8 error 2026-03-17 12:29:16 +01:00
rash
35c5f02b59 try fixing e2e test ci 2026-03-17 10:47:18 +01:00
rash
11c332dfd6 remove now unused old vue2 tooling 2026-03-17 10:31:45 +01:00
rash
b95847c395 add missing quotes around 'unsafe-eval' cors value 2026-03-17 10:22:03 +01:00
rash
8fc2e78a58 fix templatetag paths to match what's in the vite mantifest 2026-03-16 16:11:49 +01:00
rash
21bd00e7e6 working prod build
acutal serving of built assets not tested yet
2026-03-16 15:19:12 +01:00
rash
3df9d86216 add new js config files to check-manifest ignore 2026-03-16 09:57:51 +01:00
rash
c850a35235 build all control vue components 2026-03-16 09:57:21 +01:00
rash
d794738b8f fix python linter errors 2026-03-13 16:58:24 +01:00
rash
c62a6359e1 fix js linter errors 2026-03-13 15:39:10 +01:00
rash
e62bed7092 upgrade npm dependencies 2026-03-13 15:33:25 +01:00
rash
477601e9f9 use npm run dev:control for the vite dev server for admin components 2026-03-13 14:17:06 +01:00
rash
6024fad056 Merge branch 'pretix:master' into vite-vue3 2026-03-13 13:27:02 +01:00
rash
8945e55900 resolve migration TODOs: properly refocus parent on navigations 2026-03-13 13:24:31 +01:00
rash
7251ae953d remove janky claude testts again 2026-03-13 10:15:51 +01:00
rash
3c1b806c8e add e2e tests for widget button, testing empty cart, adding specific items, and subevents 2026-03-13 10:14:46 +01:00
rash
2dade31f23 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 2026-03-10 14:45:00 +01:00
rash
504191c005 fix inconsistencies from automatic migration 2026-03-09 18:31:27 +01:00
rash
00cb77de8f 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) 2026-02-23 16:43:11 +01:00
rash
99bd78f2c6 less flaky e2e tests 2026-02-23 16:35:42 +01:00
rash
d9b691690e simplify e2e test iframe check 2026-02-22 19:10:11 +01:00
rash
b1b2a688a8 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 2026-02-22 17:40:25 +01:00
rash
3f92868dba start testing event series widget 2026-02-20 13:18:06 +01:00
rash
961253bac4 migrate widget bugfix #5886 2026-02-19 14:13:08 +01:00
rash
f3eb2fba6d Merge branch 'pretix:master' into vite-vue3 2026-02-19 13:40:30 +01:00
rash
4d8c22a839 make dates in e2e tests relative 2026-02-19 10:21:00 +01:00
rash
d544df098c switch timezone in e2e tests to Europe/Berlin 2026-02-19 10:14:52 +01:00
rash
6cfdfc2cd6 add test for complete widget journey for simple event 2026-02-18 17:13:39 +01:00
rash
55299b1eae drop widget_ prefix from e2e test fixtures 2026-02-18 17:12:55 +01:00
rash
97857e7a67 test file is not actually used 2026-02-12 13:30:28 +01:00
rash
469b777dcf first couple widget e2e tests
courtesy of claude
most of the tests don't work yet
2026-02-12 13:25:43 +01:00
rash
333dc56ef7 first draft migrating widget to vue3/vite 2026-02-11 15:12:43 +01:00
rash
2898f06f56 fix migration error 2026-02-05 15:41:32 +01:00
rash
0d522c0eed 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
2026-02-05 15:29:00 +01:00
rash
ee44d4c968 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
2026-02-03 15:02:32 +01:00
rash
3d37f62c51 better syntax for cors header setting 2026-02-03 10:34:52 +01:00
rash
94dd5b0350 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
2026-02-03 09:31:22 +01:00
163 changed files with 15840 additions and 11459 deletions

View File

@@ -1,5 +1,6 @@
doc/
env/
node_modules/
res/
local/
.git/

5
.editorconfig Normal file
View File

@@ -0,0 +1,5 @@
[*.{js,jsx,ts,tsx,vue}]
indent_style = tab
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true

View File

@@ -24,7 +24,7 @@ jobs:
name: Packaging
strategy:
matrix:
python-version: ["3.13"]
python-version: ["3.11"]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
@@ -46,4 +46,7 @@ jobs:
- name: Run build
run: python -m build
- name: Check files
run: unzip -l dist/pretix*whl | grep node_modules || exit 1
run: |
for pat in 'static.dist/vite/widget/widget.js' 'static.dist/vite/control/assets/checkinrules/main-' 'static.dist/vite/control/assets/webcheckin/main-'; do
unzip -l dist/pretix*whl | grep -q "$pat" || { echo "Missing: $pat"; exit 1; }
done

View File

@@ -24,10 +24,10 @@ jobs:
name: Check gettext syntax
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.13
- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
python-version: 3.13
python-version: 3.11
- uses: actions/cache@v4
with:
path: ~/.cache/pip
@@ -49,10 +49,10 @@ jobs:
name: Spellcheck
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.13
- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
python-version: 3.13
python-version: 3.11
- uses: actions/cache@v4
with:
path: ~/.cache/pip

View File

@@ -24,10 +24,10 @@ jobs:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.13
- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
python-version: 3.13
python-version: 3.11
- uses: actions/cache@v4
with:
path: ~/.cache/pip
@@ -44,10 +44,10 @@ jobs:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.13
- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
python-version: 3.13
python-version: 3.11
- uses: actions/cache@v4
with:
path: ~/.cache/pip
@@ -64,10 +64,10 @@ jobs:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.13
- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
python-version: 3.13
python-version: 3.11
- name: Install Dependencies
run: pip3 install licenseheaders
- name: Run licenseheaders

View File

@@ -23,15 +23,13 @@ jobs:
name: Tests
strategy:
matrix:
python-version: ["3.11", "3.13", "3.14"]
python-version: ["3.10", "3.11", "3.13"]
database: [sqlite, postgres]
exclude:
- database: sqlite
python-version: "3.10"
- database: sqlite
python-version: "3.11"
- database: sqlite
python-version: "3.12"
services:
postgres:
image: postgres:15
@@ -72,7 +70,7 @@ jobs:
run: make all compress
- name: Run tests
working-directory: ./src
run: PRETIX_CONFIG_FILE=tests/ci_${{ matrix.database }}.cfg py.test -n 3 -p no:sugar --cov=./ --cov-report=xml tests --maxfail=100
run: PRETIX_CONFIG_FILE=tests/ci_${{ matrix.database }}.cfg py.test -n 3 -p no:sugar --cov=./ --cov-report=xml tests --ignore=tests/e2e --maxfail=100
- name: Run concurrency tests
working-directory: ./src
run: PRETIX_CONFIG_FILE=tests/ci_${{ matrix.database }}.cfg py.test tests/concurrency_tests/ --reuse-db
@@ -83,4 +81,47 @@ jobs:
file: src/coverage.xml
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: false
if: matrix.database == 'postgres' && matrix.python-version == '3.13'
if: matrix.database == 'postgres' && matrix.python-version == '3.11'
e2e:
runs-on: ubuntu-22.04
name: E2E Tests
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: pretix
options: >-
--health-cmd "pg_isready -U postgres -d pretix"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.13"
- uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-
- name: Install system dependencies
run: sudo apt update && sudo apt install -y gettext
- name: Install Python dependencies
run: pip3 install uv && uv pip install --system -e ".[dev]" psycopg2-binary
- name: Install JS dependencies
working-directory: ./src
run: make npminstall
- name: Compile
working-directory: ./src
run: make all compress
- name: Install Playwright browsers
run: npx playwright install
- name: Run E2E tests
working-directory: ./src
run: PRETIX_CONFIG_FILE=tests/ci_postgres.cfg py.test tests/e2e/ -v --maxfail=10

1
.gitignore vendored
View File

@@ -24,5 +24,6 @@ local/
.project
.pydevproject
.DS_Store
node_modules/

1
.prettierignore Normal file
View File

@@ -0,0 +1 @@
/*

View File

@@ -20,11 +20,11 @@ RUN apt-get update && \
supervisor \
libmaxminddb0 \
libmaxminddb-dev \
zlib1g-dev \
nodejs \
npm && \
zlib1g-dev && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* && \
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo bash - && \
apt-get install -y nodejs && \
dpkg-reconfigure locales && \
locale-gen C.UTF-8 && \
/usr/sbin/update-locale LANG=C.UTF-8 && \
@@ -49,6 +49,10 @@ COPY deployment/docker/production_settings.py /pretix/src/production_settings.py
COPY pyproject.toml /pretix/pyproject.toml
COPY _build /pretix/_build
COPY src /pretix/src
COPY package.json /pretix/package.json
COPY package-lock.json /pretix/package-lock.json
COPY tsconfig.json /pretix/tsconfig.json
COPY vite.config.ts /pretix/vite.config.ts
RUN pip3 install -U \
pip \

View File

@@ -48,3 +48,8 @@ recursive-include src Makefile
recursive-exclude doc *
recursive-exclude deployment *
recursive-exclude res *
include package.json
include package-lock.json
include tsconfig.json
include vite.config.ts

108
eslint.config.mjs Normal file
View File

@@ -0,0 +1,108 @@
import { defineConfig, globalIgnores } from 'eslint/config'
import globals from 'globals'
import js from '@eslint/js'
import ts from 'typescript-eslint'
import stylistic from '@stylistic/eslint-plugin'
import vue from 'eslint-plugin-vue'
import vuePug from 'eslint-plugin-vue-pug'
const ignores = globalIgnores([
'**/node_modules',
'**/dist'
])
export default defineConfig([
ignores,
...ts.config(
js.configs.recommended,
ts.configs.recommended
),
stylistic.configs.customize({
indent: 'tab',
braceStyle: '1tbs',
quoteProps: 'as-needed'
}),
...vue.configs['flat/recommended'],
...vuePug.configs['flat/recommended'],
{
languageOptions: {
globals: {
...globals.browser,
...globals.node,
localStorage: false,
$: 'readonly',
$$: 'readonly',
$ref: 'readonly',
$computed: 'readonly',
},
parserOptions: {
parser: '@typescript-eslint/parser'
}
},
rules: {
'no-debugger': 'off',
curly: 0,
'no-return-assign': 0,
'no-console': 'off',
'vue/require-default-prop': 0,
'vue/require-v-for-key': 0,
'vue/valid-v-for': 'warn',
'vue/no-reserved-keys': 0,
'vue/no-setup-props-destructure': 0,
'vue/multi-word-component-names': 0,
'vue/max-attributes-per-line': 0,
'vue/attribute-hyphenation': ['warn', 'never'],
'vue/v-on-event-hyphenation': ['warn', 'never'],
'import/first': 0,
'@typescript-eslint/ban-ts-comment': 0,
'@typescript-eslint/no-explicit-any': 0,
'no-use-before-define': 'off',
'no-var': 'error',
'@typescript-eslint/no-use-before-define': ['error', {
typedefs: false,
functions: false,
}],
'@typescript-eslint/no-unused-vars': ['error', {
args: 'all',
argsIgnorePattern: '^_',
caughtErrors: 'all',
caughtErrorsIgnorePattern: '^_',
destructuredArrayIgnorePattern: '^_',
varsIgnorePattern: '^_',
ignoreRestSiblings: true
}],
'@stylistic/comma-dangle': 0,
'@stylistic/space-before-function-paren': ['error', 'always'],
'@stylistic/max-statements-per-line': ['error', { max: 1, ignoredNodes: ['BreakStatement'] }],
'@stylistic/member-delimiter-style': 0,
'@stylistic/arrow-parens': 0,
'@stylistic/generator-star-spacing': 0,
'@stylistic/yield-star-spacing': ['error', 'after'],
},
},
{
files: [
'src/pretix/static/pretixcontrol/js/ui/checkinrules/**/*.vue',
'src/pretix/plugins/webcheckin/**/*.vue',
],
languageOptions: {
globals: {
moment: 'readonly',
},
},
},
{
files: [
'src/pretix/static/pretixpresale/widget/**/*.{ts,vue}',
],
languageOptions: {
globals: {
LANG: 'readonly',
},
},
},
])

4784
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

52
package.json Normal file
View File

@@ -0,0 +1,52 @@
{
"name": "pretix",
"version": "1.0.0",
"description": "",
"homepage": "https://github.com/pretix/pretix#readme",
"bugs": {
"url": "https://github.com/pretix/pretix/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/pretix/pretix.git"
},
"license": "SEE LICENSE IN LICENSE",
"author": "",
"type": "module",
"main": "index.js",
"directories": {
"doc": "doc"
},
"scripts": {
"dev:control": "vite",
"dev:widget": "vite src/pretix/static/pretixpresale/widget",
"build": "npm run build:control -s && npm run build:widget -s",
"build:control": "vite build",
"build:widget": "vite build src/pretix/static/pretixpresale/widget",
"lint:eslint": "eslint src/pretix/static/pretixpresale/widget src/pretix/static/pretixcontrol/js/ui/checkinrules src/pretix/plugins/webcheckin",
"test": "echo \"Error: no test specified\" && exit 1"
},
"dependencies": {
"vue": "^3.5.30",
"vue-slicksort": "^2.0.5"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@stylistic/eslint-plugin": "^5.10.0",
"@types/jquery": "^3.5.33",
"@types/moment": "^2.11.29",
"@types/node": "^25.5.0",
"@vitejs/plugin-vue": "^6.0.5",
"@vue/eslint-config-typescript": "^14.7.0",
"@vue/language-plugin-pug": "^3.2.5",
"eslint": "^10.0.3",
"eslint-plugin-vue": "^10.8.0",
"eslint-plugin-vue-pug": "^1.0.0-alpha.5",
"globals": "^17.4.0",
"pug": "^3.0.3",
"sass-embedded": "^1.98.0",
"stylus": "^0.64.0",
"typescript-eslint": "^8.57.0",
"vite": "^8.0.0"
}
}

View File

@@ -3,7 +3,7 @@ name = "pretix"
dynamic = ["version"]
description = "Reinventing presales, one ticket at a time"
readme = "README.rst"
requires-python = ">=3.11"
requires-python = ">=3.10"
license = {file = "LICENSE"}
keywords = ["tickets", "web", "shop", "ecommerce"]
authors = [
@@ -19,11 +19,10 @@ classifiers = [
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
"Environment :: Web Environment",
"License :: OSI Approved :: GNU Affero General Public License v3",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Framework :: Django :: 5.2",
"Framework :: Django :: 4.2",
]
dependencies = [
@@ -37,7 +36,7 @@ dependencies = [
"css-inline==0.20.*",
"defusedcsv>=1.1.0",
"dnspython==2.*",
"Django[argon2]==5.2.*",
"Django[argon2]==4.2.*,>=4.2.26",
"django-bootstrap3==26.1",
"django-compressor==4.6.0",
"django-countries==8.2.*",
@@ -60,7 +59,7 @@ dependencies = [
"dnspython==2.8.*",
"drf_ujson2==1.7.*",
"geoip2==5.*",
"importlib_metadata==9.*", # Polyfill, we can probably drop this once we require Python 3.10+
"importlib_metadata==8.*", # Polyfill, we can probably drop this once we require Python 3.10+
"isoweek",
"jsonschema",
"kombu==5.6.*",
@@ -93,7 +92,7 @@ dependencies = [
"redis==7.1.*",
"reportlab==4.4.*",
"requests==2.32.*",
"sentry-sdk==2.56.*",
"sentry-sdk==2.54.*",
"sepaxml==2.7.*",
"stripe==7.9.*",
"text-unidecode==1.*",
@@ -124,6 +123,7 @@ dev = [
"pytest-mock==3.15.*",
"pytest-sugar",
"pytest-xdist==3.8.*",
"pytest-playwright",
"pytest==9.0.*",
"responses",
]

View File

@@ -37,4 +37,9 @@ ignore =
CONTRIBUTING.md
Dockerfile
SECURITY.md
eslint.config.mjs
package-lock.json
package.json
tsconfig.json
vite.config.js

View File

@@ -9,10 +9,10 @@ localegen:
./manage.py makemessages --keep-pot --ignore "pretix/static/npm_dir/*" $(LNGS)
./manage.py makemessages --keep-pot -d djangojs --ignore "pretix/static/npm_dir/*" --ignore "pretix/helpers/*" --ignore "pretix/static/jsi18n/*" --ignore "pretix/static/jsi18n/*" --ignore "pretix/static.dist/*" --ignore "data/*" --ignore "pretix/static/rrule/*" --ignore "build/*" $(LNGS)
staticfiles: jsi18n
staticfiles: npminstall npmbuild jsi18n
./manage.py collectstatic --noinput
compress: npminstall
compress:
./manage.py compress
jsi18n: localecompile
@@ -25,8 +25,8 @@ coverage:
coverage run -m py.test
npminstall:
# keep this in sync with pretix/_build.py!
mkdir -p pretix/static.dist/node_prefix/
cp -r pretix/static/npm_dir/* pretix/static.dist/node_prefix/
npm ci --prefix=pretix/static.dist/node_prefix
npm ci
npmbuild:
npm run build

View File

@@ -37,9 +37,11 @@ INSTALLED_APPS = [
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.humanize',
# pretix needs to go before staticfiles
# so we can override the runserver command
'pretix.base',
'django.contrib.staticfiles',
'pretix.control',
'pretix.presale',
'pretix.multidomain',
@@ -243,7 +245,6 @@ STORAGES = {
COMPRESS_PRECOMPILERS = (
('text/x-scss', 'django_libsass.SassCompiler'),
('text/vue', 'pretix.helpers.compressor.VueCompiler'),
)
COMPRESS_OFFLINE_CONTEXT = {

View File

@@ -21,13 +21,13 @@
#
import os
import shutil
import subprocess
from setuptools.command.build import build
from setuptools.command.build_ext import build_ext
here = os.path.abspath(os.path.dirname(__file__))
project_root = os.path.abspath(os.path.join(here, '..', '..'))
npm_installed = False
@@ -35,14 +35,14 @@ def npm_install():
global npm_installed
if not npm_installed:
# keep this in sync with Makefile!
node_prefix = os.path.join(here, 'static.dist', 'node_prefix')
os.makedirs(node_prefix, exist_ok=True)
shutil.copytree(os.path.join(here, 'static', 'npm_dir'), node_prefix, dirs_exist_ok=True)
subprocess.check_call('npm ci', shell=True, cwd=node_prefix)
subprocess.check_call('npm ci', shell=True, cwd=project_root)
npm_installed = True
def npm_build():
subprocess.check_call('npm run build', shell=True, cwd=project_root)
class CustomBuild(build):
def run(self):
if "src" not in os.listdir(".") or "pretix" not in os.listdir("src"):
@@ -62,6 +62,7 @@ class CustomBuild(build):
settings.COMPRESS_OFFLINE = True
npm_install()
npm_build()
management.call_command('compilemessages', verbosity=1)
management.call_command('compilejsi18n', verbosity=1)
management.call_command('collectstatic', verbosity=1, interactive=False)

View File

@@ -47,3 +47,5 @@ HAS_MEMCACHED = False
HAS_CELERY = False
HAS_GEOIP = False
SENTRY_ENABLED = False
VITE_DEV_MODE = False
VITE_IGNORE = False

View File

@@ -51,6 +51,7 @@ from pretix.base.models import (
ItemVariation, ItemVariationMetaValue, Question, QuestionOption, Quota,
SalesChannel,
)
from pretix.base.models.items import Questionnaire, QuestionnaireChild
class InlineItemVariationSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer):
@@ -540,6 +541,7 @@ class LegacyDependencyValueField(serializers.CharField):
class QuestionSerializer(I18nAwareModelSerializer):
options = InlineQuestionOptionSerializer(many=True, required=False)
identifier = serializers.CharField(allow_null=True)
internal_name = serializers.CharField(allow_null=True, source='question', read_only=True)
dependency_value = LegacyDependencyValueField(source='dependency_values', required=False, allow_null=True)
class Meta:
@@ -548,7 +550,7 @@ class QuestionSerializer(I18nAwareModelSerializer):
'ask_during_checkin', 'show_during_checkin', 'identifier', 'dependency_question', 'dependency_values',
'hidden', 'dependency_value', 'print_on_invoice', 'help_text', 'valid_number_min',
'valid_number_max', 'valid_date_min', 'valid_date_max', 'valid_datetime_min', 'valid_datetime_max',
'valid_string_length_max', 'valid_file_portrait')
'valid_string_length_max', 'valid_file_portrait', 'internal_name',)
def validate_identifier(self, value):
Question._clean_identifier(self.context['event'], value, self.instance)
@@ -624,6 +626,160 @@ class QuestionSerializer(I18nAwareModelSerializer):
return question
class QuestionRefField(serializers.PrimaryKeyRelatedField):
def to_representation(self, qc):
if not qc:
return None
elif qc.system_question:
return qc.system_question
elif qc.user_question_id:
return qc.user_question_id
else:
return None
def to_internal_value(self, data):
if type(data) == int:
return {'user_question': super().to_internal_value(data), 'system_question': None}
elif type(data) == str or data is None:
return {'user_question': None, 'system_question': data}
else:
self.fail('incorrect_type', data_type=type(data).__name__)
def use_pk_only_optimization(self):
return self.source == '*'
class InlineQuestionnaireChildSerializer(I18nAwareModelSerializer):
question = QuestionRefField(source='*', queryset=Question.objects.none())
dependency_question = QuestionRefField(allow_null=True, required=False, queryset=Question.objects.none())
class Meta:
model = QuestionnaireChild
fields = ('question', 'required', 'label', 'help_text', 'dependency_question', 'dependency_values')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["question"].queryset = self.context["event"].questions.all()
self.fields["dependency_question"].queryset = self.context["event"].questions.all()
def validate(self, data):
data = super().validate(data)
event = self.context['event']
full_data = self.to_internal_value(self.to_representation(self.instance)) if self.instance else {}
full_data.update(data)
if full_data.get('ask_during_checkin') and full_data.get('dependency_question'):
raise ValidationError('Dependencies are not supported during check-in.')
dep = full_data.get('dependency_question')
if dep:
if dep.ask_during_checkin:
raise ValidationError(_('Question cannot depend on a question asked during check-in.'))
seen_ids = {self.instance.pk} if self.instance else set()
while dep:
if dep.pk in seen_ids:
raise ValidationError(_('Circular dependency between questions detected.'))
seen_ids.add(dep.pk)
dep = dep.dependency_question
return data
def validate_dependency_question(self, value):
if value:
if value.type not in (Question.TYPE_CHOICE, Question.TYPE_BOOLEAN, Question.TYPE_CHOICE_MULTIPLE):
raise ValidationError('Question dependencies can only be set to boolean or choice questions.')
if value == self.instance:
raise ValidationError('A question cannot depend on itself.')
return value
class QuestionnaireSerializer(I18nAwareModelSerializer):
limit_sales_channels = serializers.SlugRelatedField(
slug_field="identifier",
queryset=SalesChannel.objects.none(),
required=False,
allow_empty=True,
many=True,
)
class Meta:
model = Questionnaire
fields = ('id', 'type', 'internal_name', 'items', 'position', 'all_sales_channels', 'limit_sales_channels', 'children')
def __init__(self, *args, **kwargs):
self.fields['children'] = InlineQuestionnaireChildSerializer(many=True, required=True, context=kwargs['context'], partial=False)
super().__init__(*args, **kwargs)
def validate(self, data):
data = super().validate(data)
event = self.context['event']
#full_data = self.to_internal_value(self.to_representation(self.instance)) if self.instance else {}
#full_data.update(data)
#if full_data.get('ask_during_checkin') and full_data.get('dependency_question'):
# raise ValidationError('Dependencies are not supported during check-in.')
#if full_data.get('ask_during_checkin') and full_data.get('type') in Question.ASK_DURING_CHECKIN_UNSUPPORTED:
# raise ValidationError(_('This type of question cannot be asked during check-in.'))
#if full_data.get('show_during_checkin') and full_data.get('type') in Question.SHOW_DURING_CHECKIN_UNSUPPORTED:
# raise ValidationError(_('This type of question cannot be shown during check-in.'))
#Question.clean_items(event, full_data.get('items') or [])
return data
def validate_children(self, value):
prev_questions = {}
for child in value:
if child.get('dependency_question'):
if (child['dependency_question']['user_question'] or child['dependency_question']['system_question']) not in prev_questions:
raise ValidationError('A question can only depend on a previous question from the same questionnaire.')
if child['user_question']:
prev_questions[child['user_question']] = child
if child['system_question']:
prev_questions[child['system_question']] = child
return value
@transaction.atomic
def create(self, validated_data):
children_data = validated_data.pop('children') if 'children' in validated_data else []
questionnaire = super().create(validated_data)
self.set_children(questionnaire, children_data)
return questionnaire
@transaction.atomic
def update(self, instance, validated_data):
children_data = validated_data.pop('children', None)
questionnaire = super().update(instance, validated_data)
if children_data is not None:
self.set_children(questionnaire, children_data)
return questionnaire
def set_children(self, questionnaire, new_data):
result = []
child_serializer = self.fields['children'].child
existing = questionnaire.children.all()
for i, d in enumerate(new_data):
d['questionnaire'] = questionnaire
d['position'] = i + 1
d.setdefault('required', False)
d.setdefault('help_text', None)
d.setdefault('dependency_question', None)
d.setdefault('dependency_values', None)
updatable = min(len(existing), len(new_data))
for i in range(0, updatable):
result.append(child_serializer.update(existing[i], new_data[i]))
for i in range(updatable, len(new_data)):
result.append(child_serializer.create(new_data[i]))
for i in range(updatable, len(existing)):
existing[i].delete()
return result
class QuotaSerializer(I18nAwareModelSerializer):
available = serializers.BooleanField(read_only=True)
available_number = serializers.IntegerField(read_only=True)

View File

@@ -79,7 +79,8 @@ event_router.register(r'subevents', event.SubEventViewSet)
event_router.register(r'clone', event.CloneEventViewSet)
event_router.register(r'items', item.ItemViewSet)
event_router.register(r'categories', item.ItemCategoryViewSet)
event_router.register(r'questions', item.QuestionViewSet)
event_router.register(r'datafields', item.QuestionViewSet)
event_router.register(r'questionnaires', item.QuestionnaireViewSet)
event_router.register(r'discounts', discount.DiscountViewSet)
event_router.register(r'quotas', item.QuotaViewSet)
event_router.register(r'vouchers', voucher.VoucherViewSet)

View File

@@ -47,13 +47,14 @@ from pretix.api.pagination import TotalOrderingFilter
from pretix.api.serializers.item import (
ItemAddOnSerializer, ItemBundleSerializer, ItemCategorySerializer,
ItemProgramTimeSerializer, ItemSerializer, ItemVariationSerializer,
QuestionOptionSerializer, QuestionSerializer, QuotaSerializer,
QuestionOptionSerializer, QuestionSerializer, QuestionnaireSerializer, QuotaSerializer,
)
from pretix.api.views import ConditionalListView
from pretix.base.models import (
CartPosition, Item, ItemAddOn, ItemBundle, ItemCategory, ItemProgramTime,
ItemVariation, Question, QuestionOption, Quota,
)
from pretix.base.models.items import Questionnaire
from pretix.base.services.quotas import QuotaAvailability
from pretix.helpers.dicts import merge_dicts
from pretix.helpers.i18n import i18ncomp
@@ -538,6 +539,51 @@ class QuestionOptionViewSet(viewsets.ModelViewSet):
super().perform_destroy(instance)
class QuestionnaireViewSet(ConditionalListView, viewsets.ModelViewSet):
serializer_class = QuestionnaireSerializer
queryset = Questionnaire.objects.none()
#filter_backends = (DjangoFilterBackend, TotalOrderingFilter)
#filterset_class = QuestionFilter
ordering_fields = ('id', 'position')
ordering = ('position', 'id')
permission = None
write_permission = 'event.items:write'
def get_queryset(self):
return self.request.event.questionnaires.prefetch_related('children').all()
def perform_create(self, serializer):
serializer.save(event=self.request.event)
serializer.instance.log_action(
'pretix.event.questionnaire.added',
user=self.request.user,
auth=self.request.auth,
data=self.request.data
)
def get_serializer_context(self):
ctx = super().get_serializer_context()
ctx['event'] = self.request.event
return ctx
def perform_update(self, serializer):
serializer.save(event=self.request.event)
serializer.instance.log_action(
'pretix.event.questionnaire.changed',
user=self.request.user,
auth=self.request.auth,
data=self.request.data
)
def perform_destroy(self, instance):
instance.log_action(
'pretix.event.questionnaire.deleted',
user=self.request.user,
auth=self.request.auth,
)
super().perform_destroy(instance)
class NumberInFilter(django_filters.BaseInFilter, django_filters.NumberFilter):
pass

View File

@@ -45,6 +45,7 @@ import pycountry
from django import forms
from django.conf import settings
from django.contrib import messages
from django.contrib.gis.geoip2 import GeoIP2
from django.core.exceptions import ValidationError
from django.core.files.uploadedfile import SimpleUploadedFile
from django.core.validators import (
@@ -101,7 +102,6 @@ from pretix.helpers.countries import (
from pretix.helpers.escapejson import escapejson_attr
from pretix.helpers.http import get_client_ip
from pretix.helpers.i18n import get_format_without_seconds
from pretix.helpers.security import get_geoip
from pretix.presale.signals import question_form_fields
logger = logging.getLogger(__name__)
@@ -393,7 +393,7 @@ class WrappedPhoneNumberPrefixWidget(PhoneNumberPrefixWidget):
def guess_country_from_request(request, event):
if settings.HAS_GEOIP:
g = get_geoip()
g = GeoIP2()
try:
res = g.country(get_client_ip(request))
if res['country_code'] and len(res['country_code']) == 2:

View File

@@ -36,9 +36,8 @@ from django.core.management.commands.makemigrations import Command as Parent
from ._migrations import monkeypatch_migrations
monkeypatch_migrations()
class Command(Parent):
def handle(self, *args, **kwargs):
monkeypatch_migrations()
return super().handle(*args, **kwargs)
pass

View File

@@ -64,7 +64,7 @@ class Command(BaseCommand):
if not periodic_task.receivers or periodic_task.sender_receivers_cache.get(self) is NO_RECEIVERS:
return
for receiver in periodic_task._live_receivers(self)[0]:
for receiver in periodic_task._live_receivers(self):
name = f'{receiver.__module__}.{receiver.__name__}'
if options['list_tasks']:
print(name)

View File

@@ -0,0 +1,59 @@
#
# 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 command supersedes the Django-inbuilt runserver command.
It runs the local frontend server, if node is installed and the setting
is set.
"""
import atexit
import os
import subprocess
from pathlib import Path
from django.conf import settings
from django.contrib.staticfiles.management.commands.runserver import (
Command as Parent,
)
from django.utils.autoreload import DJANGO_AUTORELOAD_ENV
class Command(Parent):
def handle(self, *args, **options):
# Only start Vite in the non-main process of the autoreloader
if settings.VITE_DEV_MODE and os.environ.get(DJANGO_AUTORELOAD_ENV) != "true":
# Start the vite server in the background
vite_server = subprocess.Popen(
["npm", "run", "dev:control"],
cwd=Path(__file__).parent.parent.parent.parent.parent
)
def cleanup():
vite_server.terminate()
try:
vite_server.wait(timeout=5)
except subprocess.TimeoutExpired:
vite_server.kill()
atexit.register(cleanup)
super().handle(*args, **options)

View File

@@ -280,11 +280,11 @@ class SecurityMiddleware(MiddlewareMixin):
h = {
'default-src': ["{static}"],
'script-src': ['{static}'],
'script-src': ["{static}"] + (["http://localhost:5173", "ws://localhost:5173"] if settings.VITE_DEV_MODE else []),
'object-src': ["'none'"],
'frame-src': ['{static}'],
'style-src': ["{static}", "{media}"],
'connect-src': ["{dynamic}", "{media}"],
'style-src': ["{static}", "{media}"] + (["'unsafe-inline'"] if settings.VITE_DEV_MODE else []),
'connect-src': ["{dynamic}", "{media}"] + (["http://localhost:5173", "ws://localhost:5173"] if settings.VITE_DEV_MODE else []),
'img-src': ["{static}", "{media}", "data:"] + img_src,
'font-src': ["{static}"] + list(font_src),
'media-src': ["{static}", "data:"],

View File

@@ -41,20 +41,16 @@ class Migration(migrations.Migration):
name='datetime',
field=models.DateTimeField(),
),
migrations.AddIndex(
'logentry',
models.Index(fields=('datetime', 'id'), name="pretixbase__datetim_b1fe5a_idx"),
migrations.AlterIndexTogether(
name='logentry',
index_together={('datetime', 'id')},
),
migrations.AddIndex(
'order',
models.Index(fields=["datetime", "id"], name="pretixbase__datetim_66aff0_idx"),
migrations.AlterIndexTogether(
name='order',
index_together={('datetime', 'id'), ('last_modified', 'id')},
),
migrations.AddIndex(
'order',
models.Index(fields=["last_modified", "id"], name="pretixbase__last_mo_4ebf8b_idx"),
),
migrations.AddIndex(
'transaction',
models.Index(fields=('datetime', 'id'), name="pretixbase__datetim_b20405_idx"),
migrations.AlterIndexTogether(
name='transaction',
index_together={('datetime', 'id')},
),
]

View File

@@ -61,10 +61,7 @@ class Migration(migrations.Migration):
options={
'ordering': ('identifier', 'type', 'organizer'),
'unique_together': {('identifier', 'type', 'organizer')},
'indexes': [
models.Index(fields=('identifier', 'type', 'organizer'), name='reusable_medium_organizer_index'),
models.Index(fields=('updated', 'id'), name="pretixbase__updated_093277_idx")
],
'index_together': {('identifier', 'type', 'organizer'), ('updated', 'id')},
},
bases=(models.Model, pretix.base.models.base.LoggingMixin),
),

View File

@@ -9,6 +9,31 @@ class Migration(migrations.Migration):
]
operations = [
migrations.RenameIndex(
model_name="logentry",
new_name="pretixbase__datetim_b1fe5a_idx",
old_fields=("datetime", "id"),
),
migrations.RenameIndex(
model_name="order",
new_name="pretixbase__datetim_66aff0_idx",
old_fields=("datetime", "id"),
),
migrations.RenameIndex(
model_name="order",
new_name="pretixbase__last_mo_4ebf8b_idx",
old_fields=("last_modified", "id"),
),
migrations.RenameIndex(
model_name="reusablemedium",
new_name="pretixbase__updated_093277_idx",
old_fields=("updated", "id"),
),
migrations.RenameIndex(
model_name="transaction",
new_name="pretixbase__datetim_b20405_idx",
old_fields=("datetime", "id"),
),
migrations.AlterField(
model_name="attendeeprofile",
name="id",

View File

@@ -1,6 +1,6 @@
# Generated by Django 4.2.10 on 2024-04-02 15:16
from django.db import migrations, models
from django.db import migrations
class Migration(migrations.Migration):
@@ -10,8 +10,8 @@ class Migration(migrations.Migration):
]
operations = [
migrations.RemoveIndex(
"reusablemedium",
'reusable_medium_organizer_index',
migrations.AlterIndexTogether(
name="reusablemedium",
index_together=set(),
),
]

View File

@@ -0,0 +1,162 @@
# Generated by Django 4.2.29 on 2026-03-19 14:24
import json
from collections import namedtuple
from itertools import chain, groupby
from django.db import migrations, models
import django.db.models.deletion
import i18nfield.fields
from i18nfield.strings import LazyI18nString
import pretix.base.models.base
import pretix.base.models.fields
FakeQuestion = namedtuple(
'FakeQuestion', 'id question position required'
)
def get_fake_questions(settings):
def b(s):
return s == 'True'
fq = []
sqo = json.loads(settings.get('system_question_order', '{}'))
_ = LazyI18nString.from_gettext
if b(settings.get('attendee_names_asked', 'True')):
fq.append(FakeQuestion('attendee_name_parts', _('Attendee name'), sqo.get('attendee_name_parts', 0), b(settings.get('attendee_names_required'))))
if b(settings.get('attendee_emails_asked')):
fq.append(FakeQuestion('attendee_email', _('Attendee email'), sqo.get('attendee_email', 0), b(settings.get('attendee_emails_required'))))
if b(settings.get('attendee_company_asked')):
fq.append(FakeQuestion('company', _('Company'), sqo.get('company', 0), b(settings.get('attendee_company_required'))))
if b(settings.get('attendee_addresses_asked')):
fq.append(FakeQuestion('street', _('Street'), sqo.get('street', 0), b(settings.get('attendee_addresses_required'))))
fq.append(FakeQuestion('zipcode', _('ZIP code'), sqo.get('zipcode', 0), b(settings.get('attendee_addresses_required'))))
fq.append(FakeQuestion('city', _('City'), sqo.get('city', 0), b(settings.get('attendee_addresses_required'))))
fq.append(FakeQuestion('country', _('Country'), sqo.get('country', 0), b(settings.get('attendee_addresses_required'))))
return fq
def migrate_questions_forward(apps, schema_editor):
Event = apps.get_model("pretixbase", "Event")
Item = apps.get_model("pretixbase", "Item")
Question = apps.get_model("pretixbase", "Question")
Questionnaire = apps.get_model("pretixbase", "Questionnaire")
QuestionnaireChild = apps.get_model("pretixbase", "QuestionnaireChild")
EventSettingsStore = apps.get_model('pretixbase', 'Event_SettingsStore')
for event in Event.objects.iterator():
# get relevant settings
settings = {
setting.key: setting.value for setting in EventSettingsStore.objects.filter(object_id=event.id, key__in=(
'system_question_order', 'attendee_names_asked', 'attendee_names_required', 'attendee_emails_asked', 'attendee_emails_required',
'attendee_company_asked', 'attendee_company_required', 'attendee_addresses_asked', 'attendee_addresses_required',
))
}
# get all questions (user-defined and system provided), along with the products for which they're asked
questions = event.questions.all()
children = sorted(chain((
(item, q.position, q.id, q)
for q in questions
for item in q.items.values_list('id', 'internal_name', 'name')
), (
(item, q.position, None, q)
for q in get_fake_questions(settings)
for item in event.items.filter(personalized=True).values_list('id', 'internal_name', 'name')
)), key=lambda t: (t[0], t[1], t[2]))
# group by item, creating a unique questionnaire per item
item_questionnaires = (([t[3] for t in children], item_id) for item_id, children in groupby(children, key=lambda t: t[0]))
# group again, merging all questionnaires with identical children
merged_questionnaires = groupby(sorted(item_questionnaires, key=lambda t: [q.id for q in t[0]]), key=lambda t: t[0])
for children, iterator in merged_questionnaires:
items = [item for _c, item in iterator]
# create questionnaires and children
questionnaire = Questionnaire.objects.create(
event=event, type='PS', position=0, all_sales_channels=True,
internal_name=', '.join(str(iname or name) for (id, iname, name) in items)
)
questionnaire.items.set([id for (id, iname, name) in items])
deps = {}
for position, child in enumerate(children):
if isinstance(child, FakeQuestion):
QuestionnaireChild.objects.create(
questionnaire=questionnaire,
position=position + 1,
system_question=child.id,
required=child.required,
label=child.question,
)
else:
deps[child.id] = QuestionnaireChild.objects.create(
questionnaire=questionnaire,
position=position + 1,
user_question=child,
required=child.required,
label=child.question,
help_text=child.help_text,
dependency_question=deps[child.dependency_question.id] if child.dependency_question else None,
dependency_values=child.dependency_values,
)
def migrate_questions_backward(apps, schema_editor):
pass # as long as we don't delete the old columns, this is a no op. after that, it gets complicated...
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0298_pluggable_permissions'),
]
operations = [
migrations.CreateModel(
name='Questionnaire',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
('internal_name', models.CharField(max_length=255)),
('type', models.CharField(max_length=5)),
('position', models.PositiveIntegerField(default=0)),
('all_sales_channels', models.BooleanField(default=True)),
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='questionnaires', to='pretixbase.event')),
('items', models.ManyToManyField(related_name='questionnaires', to='pretixbase.item')),
('limit_sales_channels', models.ManyToManyField(to='pretixbase.saleschannel')),
],
options={
'abstract': False,
},
bases=(models.Model, pretix.base.models.base.LoggingMixin),
),
migrations.CreateModel(
name='QuestionnaireChild',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
('position', models.PositiveIntegerField(default=0)),
('system_question', models.CharField(max_length=25, null=True)),
('required', models.BooleanField(default=False)),
('label', i18nfield.fields.I18nTextField()),
('help_text', i18nfield.fields.I18nTextField(null=True)),
('dependency_values', pretix.base.models.fields.MultiStringField(default=[])),
('dependency_question', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='dependent_questions', to='pretixbase.questionnairechild')),
('questionnaire', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='children', to='pretixbase.questionnaire')),
('user_question', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='references', to='pretixbase.question')),
],
options={
'abstract': False,
},
bases=(models.Model, pretix.base.models.base.LoggingMixin),
),
migrations.RunPython(
migrate_questions_forward,
migrate_questions_backward,
),
# TODO remove old columns from Question model
]

View File

@@ -1595,10 +1595,12 @@ class ItemBundle(models.Model):
class Question(LoggedModel):
"""
A question is an input field that can be used to extend a ticket by custom information,
e.g. "Attendee age". The answers are found next to the position. The answers may be found
in QuestionAnswers, attached to OrderPositions/CartPositions. A question can allow one of
several input types, currently:
A question is a data field that can be used to extend an order or a ticket by custom
information, e.g. "Attendee age". To be actually useful, questions need to be added to
one or multiple Questionnaires. The answers may be found in QuestionAnswers, attached
to Orders, OrderPositions or CartPositions.
A question can allow one of several input types, currently:
* a number (``TYPE_NUMBER``)
* a one-line string (``TYPE_STRING``)
@@ -1667,7 +1669,7 @@ class Question(LoggedModel):
related_name="questions",
on_delete=models.CASCADE
)
question = I18nTextField(
question = I18nTextField( # to be renamed to 'internal_name'
verbose_name=_("Question")
)
identifier = models.CharField(
@@ -1682,7 +1684,7 @@ class Question(LoggedModel):
),
],
)
help_text = I18nTextField(
help_text = I18nTextField( # to be removed
verbose_name=_("Help text"),
help_text=_("If the question needs to be explained or clarified, do it here!"),
null=True, blank=True,
@@ -1692,22 +1694,22 @@ class Question(LoggedModel):
choices=TYPE_CHOICES,
verbose_name=_("Question type")
)
required = models.BooleanField(
required = models.BooleanField( # to be removed, -> QuestionnaireChild
default=False,
verbose_name=_("Required question")
)
items = models.ManyToManyField(
items = models.ManyToManyField( # to be removed, -> Questionnaire
Item,
related_name='questions',
verbose_name=_("Products"),
blank=True,
help_text=_('This question will be asked to buyers of the selected products')
)
position = models.PositiveIntegerField(
position = models.PositiveIntegerField( # to be removed, -> Questionnaire + QuestionnaireChild
default=0,
verbose_name=_("Position")
)
ask_during_checkin = models.BooleanField(
ask_during_checkin = models.BooleanField( # to be removed
verbose_name=_('Ask during check-in instead of in the ticket buying process'),
help_text=_('Not supported by all check-in apps for all question types.'),
default=False
@@ -1717,7 +1719,7 @@ class Question(LoggedModel):
help_text=_('Not supported by all check-in apps for all question types.'),
default=False
)
hidden = models.BooleanField(
hidden = models.BooleanField( # to be removed
verbose_name=_('Hidden question'),
help_text=_('This question will only show up in the backend.'),
default=False
@@ -1726,10 +1728,10 @@ class Question(LoggedModel):
verbose_name=_('Print answer on invoices'),
default=False
)
dependency_question = models.ForeignKey(
dependency_question = models.ForeignKey( # to be removed, -> QuestionnaireChild
'Question', null=True, blank=True, on_delete=models.SET_NULL, related_name='dependent_questions'
)
dependency_values = MultiStringField(default=[])
dependency_values = MultiStringField(default=[]) # to be removed, -> QuestionnaireChild
valid_number_min = models.DecimalField(decimal_places=6, max_digits=30, null=True, blank=True,
verbose_name=_('Minimum value'),
help_text=_('Currently not supported in our apps and during check-in'))
@@ -1763,9 +1765,9 @@ class Question(LoggedModel):
objects = ScopedManager(organizer='event__organizer')
class Meta:
verbose_name = _("Question")
verbose_name_plural = _("Questions")
ordering = ('position', 'id')
verbose_name = _("Data field")
verbose_name_plural = _("Data fields")
ordering = ('question', 'id')
unique_together = (('event', 'identifier'),)
def __str__(self):
@@ -1990,6 +1992,103 @@ class QuestionOption(models.Model):
ordering = ('position', 'id')
class Questionnaire(LoggedModel):
TYPE_ORDER_SALE = "OS"
TYPE_ORDER_POSITION_SALE = "PS"
TYPE_ORDER_POSITION_ATTENDEE_ONLY = "PA"
TYPE_ORDER_POSITION_CHECKIN = "PC"
TYPE_CHOICES = (
(TYPE_ORDER_SALE, _("Order-wide, before purchase")),
(TYPE_ORDER_POSITION_SALE, _("Per product, before purchase")),
(TYPE_ORDER_POSITION_ATTENDEE_ONLY, _("Per product, via attendee link")),
(TYPE_ORDER_POSITION_CHECKIN, _("Per product, at check-in")),
)
event = models.ForeignKey(
Event,
related_name="questionnaires",
on_delete=models.CASCADE
)
internal_name = models.CharField(
verbose_name=_("Internal name"),
max_length=255,
)
type = models.CharField(
max_length=5,
choices=TYPE_CHOICES,
verbose_name=_("Questionnaire type")
)
items = models.ManyToManyField(
Item,
related_name='questionnaires',
verbose_name=_("Products"),
blank=True,
help_text=_('This questionnaire will be asked to buyers of the selected products')
)
position = models.PositiveIntegerField(
default=0,
verbose_name=_("Position")
)
all_sales_channels = models.BooleanField(
verbose_name=_("Sell on all sales channels the product is sold on"),
default=True,
)
limit_sales_channels = models.ManyToManyField(
"SalesChannel",
verbose_name=_("Restrict to specific sales channels"),
help_text=_('The sales channel selection for the product as a whole takes precedence, so if a sales channel is '
'selected here but not on product level, the variation will not be available.'),
blank=True,
)
class QuestionnaireChild(LoggedModel):
SYSTEM_QUESTION_CHOICES = (
('attendee_name_parts', _('Attendee name')),
('attendee_email', _('Attendee email')),
('company', _('Company')),
('street', _('Street')),
('zipcode', _('ZIP code')),
('city', _('City')),
('country', _('Country')),
)
questionnaire = models.ForeignKey(
Questionnaire,
related_name="children",
on_delete=models.CASCADE
)
position = models.PositiveIntegerField(
default=0,
verbose_name=_("Position")
)
user_question = models.ForeignKey(
Question,
related_name="references",
on_delete=models.CASCADE,
null=True, blank=True,
)
system_question = models.CharField(
max_length=25,
choices=SYSTEM_QUESTION_CHOICES,
null=True, blank=True,
)
required = models.BooleanField(
default=False,
verbose_name=_("Required question")
)
label = I18nTextField(
verbose_name=_("Question")
)
help_text = I18nTextField(
verbose_name=_("Help text"),
help_text=_("If the question needs to be explained or clarified, do it here!"),
null=True, blank=True,
)
dependency_question = models.ForeignKey(
'QuestionnaireChild', null=True, blank=True, on_delete=models.SET_NULL, related_name='dependent_questions'
)
dependency_values = MultiStringField(default=[])
class Quota(LoggedModel):
"""
A quota is a "pool of tickets". It is there to limit the number of items

View File

@@ -88,7 +88,7 @@ class LogEntry(models.Model):
class Meta:
ordering = ('-datetime', '-id')
indexes = [models.Index(fields=["datetime", "id"], name="pretixbase__datetim_b1fe5a_idx")]
indexes = [models.Index(fields=["datetime", "id"])]
def display(self):
from pretix.base.logentrytype_registry import log_entry_types

View File

@@ -122,7 +122,7 @@ class ReusableMedium(LoggedModel):
class Meta:
unique_together = (("identifier", "type", "organizer"),)
indexes = [
models.Index(fields=("updated", "id"), name="pretixbase__updated_093277_idx"),
models.Index(fields=("updated", "id")),
]
ordering = "identifier", "type", "organizer"

View File

@@ -336,8 +336,8 @@ class Order(LockModel, LoggedModel):
verbose_name_plural = _("Orders")
ordering = ("-datetime", "-pk")
indexes = [
models.Index(fields=["datetime", "id"], name="pretixbase__datetim_66aff0_idx"),
models.Index(fields=["last_modified", "id"], name="pretixbase__last_mo_4ebf8b_idx"),
models.Index(fields=["datetime", "id"]),
models.Index(fields=["last_modified", "id"]),
]
constraints = [
models.UniqueConstraint(fields=["organizer", "code"], name="order_organizer_code_uniq"),
@@ -3080,7 +3080,7 @@ class Transaction(models.Model):
class Meta:
ordering = 'datetime', 'pk'
indexes = [
models.Index(fields=['datetime', 'id'], name="pretixbase__datetim_b20405_idx")
models.Index(fields=['datetime', 'id'])
]
def save(self, *args, **kwargs):

File diff suppressed because one or more lines are too long

View File

@@ -32,7 +32,6 @@
# 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 logging
import warnings
from typing import Any, Callable, Generic, List, Tuple, TypeVar
@@ -49,8 +48,6 @@ from .plugins import (
PLUGIN_LEVEL_ORGANIZER,
)
logger = logging.getLogger(__name__)
app_cache = {}
T = TypeVar('T')
@@ -63,25 +60,23 @@ def _populate_app_cache():
def get_defining_app(o):
# If sentry packed this in a wrapper, unpack that
module = getattr(o, "__module__", None)
if module and "sentry" in module:
if "sentry" in o.__module__:
o = o.__wrapped__
if hasattr(o, "__mocked_app"):
return o.__mocked_app
# Find the Django application this belongs to
searchpath = module or getattr(o.__class__, "__module__", None) or ""
searchpath = o.__module__
# Core modules are always active
if searchpath and any(searchpath.startswith(cm) for cm in settings.CORE_MODULES):
if any(searchpath.startswith(cm) for cm in settings.CORE_MODULES):
return 'CORE'
if not app_cache:
_populate_app_cache()
app = None
while searchpath:
while True:
app = app_cache.get(searchpath)
if "." not in searchpath or app:
break
@@ -162,7 +157,7 @@ class PluginSignal(Generic[T], django.dispatch.Signal):
if not app_cache:
_populate_app_cache()
for receiver in self._live_receivers(sender)[0]:
for receiver in self._sorted_receivers(sender):
if self._is_receiver_active(sender, receiver):
response = receiver(signal=self, sender=sender, **named)
responses.append((receiver, response))
@@ -184,7 +179,7 @@ class PluginSignal(Generic[T], django.dispatch.Signal):
if not app_cache:
_populate_app_cache()
for receiver in self._live_receivers(sender)[0]:
for receiver in self._sorted_receivers(sender):
if self._is_receiver_active(sender, receiver):
named[chain_kwarg_name] = response
response = receiver(signal=self, sender=sender, **named)
@@ -209,7 +204,7 @@ class PluginSignal(Generic[T], django.dispatch.Signal):
if not app_cache:
_populate_app_cache()
for receiver in self._live_receivers(sender)[0]:
for receiver in self._sorted_receivers(sender):
if self._is_receiver_active(sender, receiver):
try:
response = receiver(signal=self, sender=sender, **named)
@@ -219,35 +214,17 @@ class PluginSignal(Generic[T], django.dispatch.Signal):
responses.append((receiver, response))
return responses
def asend(self, sender: T, **named):
raise NotImplementedError() # NOQA
def asend_robust(self, sender: T, **named):
raise NotImplementedError() # NOQA
def _live_receivers(self, sender):
orig_list, orig_async_list = super()._live_receivers(sender)
if orig_async_list:
logger.error('Async receivers are not supported.')
raise NotImplementedError
def _getattr_fallback_to_class(obj, key):
return getattr(obj, key, getattr(obj.__class__, key))
def _is_core_module(receiver):
m = _getattr_fallback_to_class(receiver, "__module__")
return any(m.startswith(c) for c in settings.CORE_MODULES)
def _sorted_receivers(self, sender):
orig_list = self._live_receivers(sender)
sorted_list = sorted(
orig_list,
key=lambda receiver: (
0 if _is_core_module(receiver) else 1,
_getattr_fallback_to_class(receiver, "__module__"),
_getattr_fallback_to_class(receiver, "__name__"),
0 if any(receiver.__module__.startswith(m) for m in settings.CORE_MODULES) else 1,
receiver.__module__,
receiver.__name__,
)
)
return sorted_list, []
return sorted_list
class EventPluginSignal(PluginSignal[Event]):
@@ -323,41 +300,23 @@ class GlobalSignal(django.dispatch.Signal):
if not self.receivers or self.sender_receivers_cache.get(sender) is NO_RECEIVERS:
return response
for receiver in self._live_receivers(sender)[0]:
for receiver in self._live_receivers(sender):
named[chain_kwarg_name] = response
response = receiver(signal=self, sender=sender, **named)
return response
def asend(self, sender: T, **named):
raise NotImplementedError() # NOQA
def asend_robust(self, sender: T, **named):
raise NotImplementedError() # NOQA
def _live_receivers(self, sender):
# Ensure consistent sorting of receivers
orig_list, orig_async_list = super()._live_receivers(sender)
if orig_async_list:
logger.error('Async receivers are not supported.')
raise NotImplementedError
def _getattr_fallback_to_class(obj, key):
return getattr(obj, key, getattr(obj.__class__, key))
def _is_core_module(receiver):
m = _getattr_fallback_to_class(receiver, "__module__")
return any(m.startswith(c) for c in settings.CORE_MODULES)
orig_list = super()._live_receivers(sender)
sorted_list = sorted(
orig_list,
key=lambda receiver: (
0 if _is_core_module(receiver) else 1,
_getattr_fallback_to_class(receiver, "__module__"),
_getattr_fallback_to_class(receiver, "__name__"),
0 if any(receiver.__module__.startswith(m) for m in settings.CORE_MODULES) else 1,
receiver.__module__,
receiver.__name__,
)
)
return sorted_list, []
return sorted_list
class DeprecatedSignal(GlobalSignal):

View File

@@ -0,0 +1,113 @@
#
# 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/>.
#
import json
import logging
from urllib.parse import urljoin
from django import template
from django.conf import settings
from django.utils.safestring import mark_safe
register = template.Library()
LOGGER = logging.getLogger(__name__)
_MANIFEST = {}
# TODO more os.path.join ?
MANIFEST_PATH = settings.STATIC_ROOT + "/vite/control/.vite/manifest.json"
MANIFEST_BASE = "vite/control/"
# We're building the manifest if we don't have a dev server running AND if we're
# not currently running `rebuild` (which creates the manifest in the first place).
if not settings.VITE_DEV_MODE and not settings.VITE_IGNORE:
try:
with open(MANIFEST_PATH) as fp:
_MANIFEST = json.load(fp)
except Exception as e:
LOGGER.warning(f"Error reading vite manifest at {MANIFEST_PATH}: {str(e)}")
def generate_script_tag(path, attrs):
all_attrs = " ".join(f'{key}="{value}"' for key, value in attrs.items())
if settings.VITE_DEV_MODE:
src = urljoin(settings.VITE_DEV_SERVER, path)
else:
src = urljoin(settings.STATIC_URL, path)
return f'<script {all_attrs} src="{src}"></script>'
def generate_css_tags(asset, already_processed=None):
"""Recursively builds all CSS tags used in a given asset.
Ignore the side effects."""
tags = []
manifest_entry = _MANIFEST[asset]
if already_processed is None:
already_processed = []
# Put our own CSS file first for specificity
if "css" in manifest_entry:
for css_path in manifest_entry["css"]:
if css_path not in already_processed:
full_path = urljoin(settings.STATIC_URL, MANIFEST_BASE + css_path)
tags.append(f'<link rel="stylesheet" href="{full_path}" />')
already_processed.append(css_path)
# Import each file only one by way of side effects in already_processed
if "imports" in manifest_entry:
for import_path in manifest_entry["imports"]:
tags += generate_css_tags(import_path, already_processed)
return tags
@register.simple_tag
@mark_safe
def vite_asset(path):
"""
Generates one <script> tag and <link> tags for each of the CSS dependencies.
"""
if not path:
return ""
if settings.VITE_DEV_MODE:
return generate_script_tag(path, {"type": "module"})
manifest_entry = _MANIFEST.get(path)
if not manifest_entry:
raise RuntimeError(f"Cannot find {path} in Vite manifest at {MANIFEST_PATH}")
tags = generate_css_tags(path)
tags.append(
generate_script_tag(
MANIFEST_BASE + manifest_entry["file"], {"type": "module", "crossorigin": ""}
)
)
return "".join(tags)
@register.simple_tag
@mark_safe
def vite_hmr():
if not settings.VITE_DEV_MODE:
return ""
return generate_script_tag("@vite/client", {"type": "module"})

View File

@@ -34,7 +34,6 @@
import datetime
import os
from dataclasses import dataclass
from django import forms
from django.conf import settings
@@ -421,11 +420,6 @@ class SplitDateTimeField(forms.SplitDateTimeField):
class FontSelect(forms.RadioSelect):
option_template_name = 'pretixcontrol/font_option.html'
@dataclass
class FontOption:
title: str
data: str
class ItemMultipleChoiceField(SafeModelMultipleChoiceField):
def label_from_instance(self, obj):

View File

@@ -63,7 +63,7 @@ from pretix.base.forms import (
from pretix.base.models import Event, Organizer, TaxRule, Team
from pretix.base.models.event import EventFooterLink, EventMetaValue, SubEvent
from pretix.base.models.organizer import TeamQuerySet
from pretix.base.models.tax import TAX_CODE_LISTS, VAT_ID_COUNTRIES
from pretix.base.models.tax import TAX_CODE_LISTS
from pretix.base.reldate import RelativeDateField, RelativeDateTimeField
from pretix.base.services.placeholders import FormPlaceholderMixin
from pretix.base.settings import (
@@ -73,8 +73,8 @@ from pretix.base.settings import (
)
from pretix.base.validators import multimail_validate
from pretix.control.forms import (
FontSelect, MultipleLanguagesWidget, SalesChannelCheckboxSelectMultiple,
SlugWidget, SplitDateTimeField, SplitDateTimePickerWidget,
MultipleLanguagesWidget, SalesChannelCheckboxSelectMultiple, SlugWidget,
SplitDateTimeField, SplitDateTimePickerWidget,
)
from pretix.control.forms.widgets import Select2
from pretix.helpers.countries import CachedCountries
@@ -531,13 +531,6 @@ class EventUpdateForm(I18nModelForm):
class EventSettingsValidationMixin:
def clean_invoice_address_from_vat_id(self):
value = self.cleaned_data.get('invoice_address_from_vat_id')
country = self.cleaned_data.get('invoice_address_from_country')
if value and country and country not in VAT_ID_COUNTRIES:
return None
return value
def clean(self):
data = super().clean()
settings_dict = self.obj.settings.freeze()
@@ -729,7 +722,7 @@ class EventSettingsForm(EventSettingsValidationMixin, FormPlaceholderMixin, Sett
del self.fields['event_list_filters']
del self.fields['event_calendar_future_only']
self.fields['primary_font'].choices = [('Open Sans', 'Open Sans')] + sorted([
(a, FontSelect.FontOption(title=a, data=v)) for a, v in get_fonts(self.event, pdf_support_required=False).items()
(a, {"title": a, "data": v}) for a, v in get_fonts(self.event, pdf_support_required=False).items()
], key=lambda a: a[0])
# create "virtual" fields for better UX when editing <name>_asked and <name>_required fields

View File

@@ -104,6 +104,12 @@ class GlobalSettingsForm(SettingsForm):
help_text=_("Will be served at {domain}/.well-known/apple-developer-merchantid-domain-association").format(
domain=settings.SITE_URL
)
)),
('widget_vite_origins', forms.CharField(
widget=forms.Textarea(attrs={'rows': '3'}),
required=False,
label=_("Vite widget origins"),
help_text=_("One origin per line (e.g. https://example.com). Requests from these origins will be served the new vite-based widget."),
))
])
responses = register_global_settings.send(self)

View File

@@ -33,18 +33,15 @@
# License for the specific language governing permissions and limitations under the License.
import csv
from collections import Counter, namedtuple
from collections import namedtuple
from io import StringIO
from django import forms
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.core.validators import EmailValidator
from django.db.models import Count, F, Max
from django.db.models.functions import Upper
from django.forms.utils import ErrorDict
from django.urls import reverse
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _, pgettext_lazy
from django.utils.translation import gettext_lazy as _
from django_scopes.forms import SafeModelChoiceField
from pretix.base.email import get_available_placeholders
@@ -53,9 +50,7 @@ from pretix.base.forms import (
)
from pretix.base.forms.widgets import format_placeholders_help_text
from pretix.base.i18n import language
from pretix.base.models import Item, ItemVariation, Quota, SubEvent, Voucher
from pretix.base.services.locking import lock_objects
from pretix.base.services.quotas import QuotaAvailability
from pretix.base.models import Item, Voucher
from pretix.control.forms import SplitDateTimeField, SplitDateTimePickerWidget
from pretix.control.forms.widgets import Select2, Select2ItemVarQuota
from pretix.control.signals import voucher_form_validation
@@ -110,17 +105,15 @@ class VoucherForm(I18nModelForm):
except Item.DoesNotExist:
pass
super().__init__(*args, **kwargs)
if not self.event and self.instance:
self.event = self.instance.event
if self.event.has_subevents:
self.fields['subevent'].queryset = self.event.subevents.all()
if instance.event.has_subevents:
self.fields['subevent'].queryset = instance.event.subevents.all()
self.fields['subevent'].widget = Select2(
attrs={
'data-model-select2': 'event',
'data-select2-url': reverse('control:event.subevents.select2', kwargs={
'event': self.event.slug,
'organizer': self.event.organizer.slug,
'event': instance.event.slug,
'organizer': instance.event.organizer.slug,
}),
}
)
@@ -130,19 +123,18 @@ class VoucherForm(I18nModelForm):
del self.fields['subevent']
choices = []
prefix = (self.prefix + '-') if self.prefix else ''
if 'itemvar' in initial or (self.data and prefix + 'itemvar' in self.data):
iv = self.data.get(prefix + 'itemvar', '') or initial.get('itemvar', '') or ''
if 'itemvar' in initial or (self.data and 'itemvar' in self.data):
iv = self.data.get('itemvar') or initial.get('itemvar', '')
if iv.startswith('q-'):
q = self.event.quotas.get(pk=iv[2:])
q = self.instance.event.quotas.get(pk=iv[2:])
choices.append(('q-%d' % q.pk, _('Any product in quota "{quota}"').format(quota=q)))
elif '-' in iv:
itemid, varid = iv.split('-')
i = self.event.items.get(pk=itemid)
i = self.instance.event.items.get(pk=itemid)
v = i.variations.get(pk=varid)
choices.append(('%d-%d' % (i.pk, v.pk), '%s %s' % (str(i), v.value)))
elif iv:
i = self.event.items.get(pk=iv)
i = self.instance.event.items.get(pk=iv)
if i.variations.exists():
choices.append((str(i.pk), _('{product} Any variation').format(product=i)))
else:
@@ -153,8 +145,8 @@ class VoucherForm(I18nModelForm):
attrs={
'data-model-select2': 'generic',
'data-select2-url': reverse('control:event.vouchers.itemselect2', kwargs={
'event': self.event.slug,
'organizer': self.event.organizer.slug,
'event': instance.event.slug,
'organizer': instance.event.organizer.slug,
}),
'data-placeholder': _('All products')
}
@@ -162,7 +154,7 @@ class VoucherForm(I18nModelForm):
self.fields['itemvar'].required = False
self.fields['itemvar'].widget.choices = self.fields['itemvar'].choices
if self.event.seating_plan or self.event.subevents.filter(seating_plan__isnull=False).exists():
if self.instance.event.seating_plan or self.instance.event.subevents.filter(seating_plan__isnull=False).exists():
self.fields['seat'] = forms.CharField(
label=_("Specific seat ID"),
max_length=255,
@@ -189,14 +181,14 @@ class VoucherForm(I18nModelForm):
itemid, varid = None, None
if itemid:
self.instance.item = self.event.items.get(pk=itemid)
self.instance.item = self.instance.event.items.get(pk=itemid)
if varid:
self.instance.variation = self.instance.item.variations.get(pk=varid)
else:
self.instance.variation = None
self.instance.quota = None
elif quotaid:
self.instance.quota = self.event.quotas.get(pk=quotaid)
self.instance.quota = self.instance.event.quotas.get(pk=quotaid)
self.instance.item = None
self.instance.variation = None
else:
@@ -217,7 +209,7 @@ class VoucherForm(I18nModelForm):
try:
Voucher.clean_item_properties(
data, self.event,
data, self.instance.event,
self.instance.quota, self.instance.item, self.instance.variation,
seats_given=data.get('seat') or data.get('seats'),
block_quota=data.get('block_quota')
@@ -237,7 +229,7 @@ class VoucherForm(I18nModelForm):
try:
Voucher.clean_subevent(
data, self.event
data, self.instance.event
)
except ValidationError as e:
raise ValidationError({"subevent": e.message})
@@ -253,19 +245,19 @@ class VoucherForm(I18nModelForm):
if check_quota:
Voucher.clean_quota_check(
data, cnt, self.initial_instance_data,
self.event, self.instance.quota, self.instance.item, self.instance.variation
self.instance.event, self.instance.quota, self.instance.item, self.instance.variation
)
Voucher.clean_voucher_code(data, self.event, self.instance.pk)
Voucher.clean_voucher_code(data, self.instance.event, self.instance.pk)
if 'seat' in self.fields:
if data.get('seat'):
self.instance.seat = Voucher.clean_seat_id(
data, self.instance.item, self.instance.quota, self.event, self.instance.pk
data, self.instance.item, self.instance.quota, self.instance.event, self.instance.pk
)
self.instance.item = self.instance.seat.product
else:
self.instance.seat = None
voucher_form_validation.send(sender=self.event, form=self, data=data)
voucher_form_validation.send(sender=self.instance.event, form=self, data=data)
return data
@@ -273,273 +265,6 @@ class VoucherForm(I18nModelForm):
return super().save(commit)
class VoucherBulkEditForm(VoucherForm):
def __init__(self, *args, **kwargs):
self.mixed_values = kwargs.pop('mixed_values')
self.queryset = kwargs.pop('queryset')
super().__init__(**kwargs)
del self.fields["code"]
self.fields.pop("seat", None)
def clean(self):
# We skip the parent class because it's not suited for bulk editing and implement custom validation here.
# This does not validate *everything* we validate in VoucherForm. For example, we skip validation that one does
# not create a voucher for an add-on product or that the seat matches the product to save on complexity.
# This is a UX validation only anyway, since one could first create the voucher and then make the product an
# add-on product. However, we need to validate everything that we don't want violated in the database.
data = super(VoucherForm, self).clean()
if self.prefix + "itemvar" in self.data.getlist('_bulk'):
try:
itemid = quotaid = None
iv = data.get('itemvar', '')
if iv.startswith('q-'):
quotaid = iv[2:]
elif '-' in iv:
itemid, varid = iv.split('-')
elif iv:
itemid, varid = iv, None
else:
itemid, varid = None, None
if itemid:
data["item"] = self.event.items.get(pk=itemid)
if varid:
data["variation"] = data["item"].variations.get(pk=varid)
else:
data["variation"] = None
data["quota"] = None
elif quotaid:
data["quota"] = self.event.quotas.get(pk=quotaid)
data["item"] = None
data["variation"] = None
else:
data["quota"] = None
data["item"] = None
data["variation"] = None
except ObjectDoesNotExist:
raise ValidationError(_("Invalid product selected."))
if self.prefix + "max_usages" in self.data.getlist('_bulk') and "max_usages" in data:
max_redeemed = self.queryset.aggregate(m=Max("redeemed"))["m"]
if data["max_usages"] < max_redeemed:
raise ValidationError(_(
"You cannot reduce the maximum number of redemptions to %(max_usages)s, because at least one "
"of the selected vouchers has already been redeemed %(max_redeemed)s times."
) % {"max_usages": data["max_usages"], "max_redeemed": max_redeemed})
# Check diff on product and quota usage based on old groups of vouchers
if any(self.prefix + k in self.data.getlist('_bulk') for k in ("max_usages", "itemvar", "block_quota", "valid_until", "subevent")):
quota_diff = Counter()
current_vouchers = self.queryset.order_by().values(
"item", "variation", "quota", "block_quota", "valid_until", "subevent", "redeemed", "max_usages",
"allow_ignore_quota",
).annotate(c=Count("*"))
item_cache = {i.pk: i for i in Item.objects.filter(pk__in=[c["item"] for c in current_vouchers])}
var_cache = {v.pk: v for v in ItemVariation.objects.filter(pk__in=[c["variation"] for c in current_vouchers])}
quota_cache = {q.pk: q for q in Quota.objects.filter(pk__in=[c["quota"] for c in current_vouchers])}
subevent_cache = {s.pk: s for s in SubEvent.objects.filter(pk__in=[c["subevent"] for c in current_vouchers])}
for current in current_vouchers:
was_valid = current["valid_until"] is None or current["valid_until"] >= now()
# Get quotas that are currently used
if current["item"]:
current["item"] = item_cache[current["item"]]
if current["variation"]:
current["variation"] = var_cache[current["variation"]]
if current["quota"]:
current["quota"] = quota_cache[current["quota"]]
if current["subevent"]:
current["subevent"] = subevent_cache[current["subevent"]]
old_quotas = set()
if was_valid and current["block_quota"] and current["max_usages"] > current["redeemed"]:
if current["quota"]:
old_quotas.add(current["quota"])
elif current["variation"]:
old_quotas |= set(current["variation"].quotas.filter(subevent=current["subevent"]))
elif current["item"]:
if current["item"].has_variations:
old_quotas |= set(
Quota.objects.filter(pk__in=Quota.variations.through.objects.filter(
itemvariation__item=current["item"],
quota__subevent=current["subevent"],
).values('quota_id'))
)
else:
old_quotas |= set(current["item"].quotas.filter(subevent=current["subevent"]))
old_amount = max(current["max_usages"] - current["redeemed"], 0) * current["c"]
# Predict state after change
after_change = dict(current)
if self.prefix + "itemvar" in self.data.getlist('_bulk') and "itemvar" in data:
after_change["item"] = data["item"]
after_change["variation"] = data["variation"]
after_change["quota"] = data["quota"]
if self.prefix + "subevent" in self.data.getlist('_bulk') and "subevent" in data:
after_change["subevent"] = data["subevent"]
if self.prefix + "max_usages" in self.data.getlist('_bulk') and "max_usages" in data:
after_change["max_usages"] = data["max_usages"]
if self.prefix + "block_quota" in self.data.getlist('_bulk') and "block_quota" in data:
after_change["block_quota"] = data["block_quota"]
if self.prefix + "valid_until" in self.data.getlist('_bulk') and "valid_until" in data:
after_change["valid_until"] = data["valid_until"]
if self.prefix + "allow_ignore_quota" in self.data.getlist('_bulk') and "allow_ignore_quota" in data:
after_change["allow_ignore_quota"] = data["allow_ignore_quota"]
if after_change["quota"] and self.event.has_subevents and not after_change["subevent"]:
raise ValidationError(_("You cannot create a voucher that allows selection of a quota but has no date selected."))
if after_change["quota"] and after_change["subevent"] and after_change["quota"].subevent_id != after_change["subevent"].pk:
raise ValidationError(_("The selected quota does not match the selected subevent."))
if after_change["block_quota"] and self.event.has_subevents and not after_change["subevent"]:
raise ValidationError(
_('If you want this voucher to block quota, you need to select a specific date.'))
if after_change["block_quota"] and not after_change["item"] and not after_change["quota"]:
raise ValidationError(
_('You need to select a specific product or quota if this voucher should reserve '
'tickets.')
)
if after_change["allow_ignore_quota"]:
# todo: is this the most useful way to do this?
continue
will_be_valid = after_change["valid_until"] is None or after_change["valid_until"] >= now()
new_quotas = set()
if will_be_valid and after_change["block_quota"] and after_change["max_usages"] > current["redeemed"]:
if after_change["quota"]:
new_quotas.add(after_change["quota"])
elif after_change["variation"]:
new_quotas |= set(after_change["variation"].quotas.filter(subevent=after_change["subevent"]))
elif after_change["item"]:
if after_change["item"].has_variations:
new_quotas |= set(
Quota.objects.filter(pk__in=Quota.variations.through.objects.filter(
itemvariation__item=after_change["item"],
quota__subevent=after_change["subevent"],
).values('quota_id'))
)
else:
new_quotas |= set(after_change["item"].quotas.filter(subevent=after_change["subevent"]))
new_amount = max(after_change["max_usages"] - after_change["redeemed"], 0) * current["c"]
if new_quotas != old_quotas or new_amount != old_amount:
for q in old_quotas:
quota_diff[q] -= old_amount
for q in new_quotas:
quota_diff[q] += new_amount
if any(v > 0 for q, v in quota_diff.items()):
lock_objects([q for q, v in quota_diff.items() if q.size is not None and v > 0], shared_lock_objects=[self.event])
qa = QuotaAvailability(count_waitinglist=False)
qa.queue(*(q for q, v in quota_diff.items() if v > 0))
qa.compute()
if any(qa.results[q][0] != Quota.AVAILABILITY_OK or (qa.results[q][1] is not None and qa.results[q][1] < required)
for q, required in quota_diff.items() if required > 0):
raise ValidationError(_(
'There is no sufficient quota available to perform this change.'
))
has_seat = self.queryset.filter(seat__isnull=False).exists()
if has_seat:
if self.prefix + "max_usages" in self.data.getlist('_bulk'):
raise ValidationError(_(
'Changing the maximum number of usages in bulk is not supported if any of the selected vouchers '
'is assigned a seat.'
))
if self.prefix + "subevent" in self.data.getlist('_bulk'):
raise ValidationError(pgettext_lazy(
'subevent',
'Changing the date in bulk is not supported if any of the selected vouchers '
'is assigned a seat.'
))
if self.prefix + "itemvar" in self.data.getlist('_bulk') and data["quota"]:
raise ValidationError(_(
'Changing the product to a quota is not supported if any of the selected vouchers '
'is assigned a seat.'
))
if self.prefix + "valid_until" in self.data.getlist('_bulk'):
if data["valid_until"] is None or data["valid_until"] >= now():
currently_not_blocked_seats = self.queryset.filter(
seat__isnull=False,
max_usages__gt=F("redeemed"),
valid_until__lt=now(),
)
if self.event.has_subevents:
subevents = self.event.subevents.filter(pk__in=currently_not_blocked_seats.values_list("subevent"))
for se in subevents:
conflicts = currently_not_blocked_seats.filter(
subevent=se
).exclude(
seat_id__in=se.free_seats().values("pk")
)
if conflicts:
raise ValidationError(_(
'This change cannot be completed because not all assigned seats of the vouchers are '
'still available'
))
else:
conflicts = currently_not_blocked_seats.exclude(
seat_id__in=self.event.free_seats().values("pk")
)
if conflicts:
raise ValidationError(_(
'This change cannot be completed because not all assigned seats of the vouchers are '
'still available'
))
return data
def save(self, commit=True):
objs = list(self.queryset)
fields = set()
check_map = {
'price_mode': '__price',
'value': '__price',
}
for k in self.fields:
cb_val = self.prefix + check_map.get(k, k)
if cb_val not in self.data.getlist('_bulk'):
continue
if k == 'itemvar':
fields.add("item")
fields.add("variation")
fields.add("quota")
else:
fields.add(k)
for obj in objs:
if k == 'itemvar':
obj.item = self.cleaned_data["item"]
obj.variation = self.cleaned_data["variation"]
obj.quota = self.cleaned_data["quota"]
else:
setattr(obj, k, self.cleaned_data[k])
fields = [f for f in fields if f != 'itemvars']
if fields:
Voucher.objects.bulk_update(objs, fields, 200)
def full_clean(self):
if len(self.data) == 0:
# form wasn't submitted
self._errors = ErrorDict()
return
super().full_clean()
def _post_clean(self):
pass # skip model-level clean
class VoucherBulkForm(VoucherForm):
codes = forms.CharField(
widget=forms.Textarea,

View File

@@ -850,6 +850,9 @@ class OrganizerPluginStateLogEntryType(LogEntryType):
'pretix.event.question.option.added': _('An answer option has been added to the question.'),
'pretix.event.question.option.deleted': _('An answer option has been removed from the question.'),
'pretix.event.question.option.changed': _('An answer option has been changed.'),
'pretix.event.questionnaire.added': _('A questionnaire has been created.'),
'pretix.event.questionnaire.deleted': _('A questionnaire has been deleted.'),
'pretix.event.questionnaire.changed': _('A questionnaire has been changed.'),
'pretix.event.permissions.added': _('A user has been added to the event team.'),
'pretix.event.permissions.invited': _('A user has been invited to the event team.'),
'pretix.event.permissions.changed': _('A user\'s permissions have been changed.'),

View File

@@ -182,12 +182,12 @@ def get_event_navigation(request: HttpRequest):
'active': 'event.items.categories' in url.url_name,
},
{
'label': _('Questions'),
'url': reverse('control:event.items.questions', kwargs={
'label': _('Questionnaires'),
'url': reverse('control:event.items.questionnaires', kwargs={
'event': request.event.slug,
'organizer': request.event.organizer.slug,
}),
'active': 'event.items.questions' in url.url_name,
'active': 'event.items.questionnaires' in url.url_name or 'event.items.questions' in url.url_name,
},
{
'label': _('Discounts'),

View File

@@ -3,6 +3,7 @@
{% load bootstrap3 %}
{% load static %}
{% load compress %}
{% load vite %}
{% block title %}
{% if checkinlist %}
{% blocktrans with name=checkinlist.name %}Check-in list: {{ name }}{% endblocktrans %}
@@ -74,45 +75,8 @@
{% bootstrap_field form.ignore_in_statistics layout="control" %}
<h3>{% trans "Custom check-in rule" %}</h3>
<div id="rules-editor" class="form-inline">
<div>
<ul class="nav nav-tabs" role="tablist">
<li role="presentation" class="active">
<a href="#rules-edit" role="tab" data-toggle="tab">
<span class="fa fa-edit"></span>
{% trans "Edit" %}
</a>
</li>
<li role="presentation">
<a href="#rules-viz" role="tab" data-toggle="tab">
<span class="fa fa-eye"></span>
{% trans "Visualize" %}
</a>
</li>
</ul>
<!-- Tab panes -->
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="rules-edit">
<checkin-rules-editor></checkin-rules-editor>
</div>
<div role="tabpanel" class="tab-pane" id="rules-viz">
<checkin-rules-visualization></checkin-rules-visualization>
</div>
</div>
</div>
<div class="alert alert-info" v-if="missingItems.length">
<p>
{% trans "Your rule always filters by product or variation, but the following products or variations are not contained in any of your rule parts so people with these tickets will not get in:" %}
</p>
<ul>
<li v-for="h in missingItems">{{ "{" }}{h}{{ "}" }}</li>
</ul>
<p>
{% trans "Please double-check if this was intentional." %}
</p>
</div>
<div id="rules-editor">
<!-- Vue app mount point -->
</div>
<div class="disabled-withoutjs sr-only">
{{ form.rules }}
@@ -127,11 +91,6 @@
</form>
{{ items|json_script:"items" }}
{% if DEBUG %}
<script type="text/javascript" src="{% static "vuejs/vue.js" %}"></script>
{% else %}
<script type="text/javascript" src="{% static "vuejs/vue.min.js" %}"></script>
{% endif %}
{% compress js %}
<script type="text/javascript" src="{% static "d3/d3.v6.js" %}"></script>
<script type="text/javascript" src="{% static "d3/d3-color.v2.js" %}"></script>
@@ -144,15 +103,6 @@
<script type="text/javascript" src="{% static "d3/d3-drag.v2.js" %}"></script>
<script type="text/javascript" src="{% static "d3/d3-zoom.v2.js" %}"></script>
{% endcompress %}
{% compress js %}
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/checkinrules/jsonlogic-boolalg.js" %}"></script>
<script type="text/vue" src="{% static 'pretixcontrol/js/ui/checkinrules/datetimefield.vue' %}"></script>
<script type="text/vue" src="{% static 'pretixcontrol/js/ui/checkinrules/timefield.vue' %}"></script>
<script type="text/vue" src="{% static 'pretixcontrol/js/ui/checkinrules/lookup-select2.vue' %}"></script>
<script type="text/vue" src="{% static 'pretixcontrol/js/ui/checkinrules/checkin-rule.vue' %}"></script>
<script type="text/vue" src="{% static 'pretixcontrol/js/ui/checkinrules/checkin-rules-editor.vue' %}"></script>
<script type="text/vue" src="{% static 'pretixcontrol/js/ui/checkinrules/viz-node.vue' %}"></script>
<script type="text/vue" src="{% static 'pretixcontrol/js/ui/checkinrules/checkin-rules-visualization.vue' %}"></script>
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/checkinrules.js" %}"></script>
{% endcompress %}
{% vite_hmr %}
{% vite_asset "src/pretix/static/pretixcontrol/js/ui/checkinrules/index.ts" %}
{% endblock %}

View File

@@ -5,6 +5,7 @@
{% load getitem %}
{% load static %}
{% load compress %}
{% load vite %}
{% block title %}{% trans "Check-in simulator" %}{% endblock %}
{% block inside %}
<h1>
@@ -124,11 +125,9 @@
{% endif %}
{% if result.rule_graph %}
<div id="rules-editor" class="form-inline">
<div role="tabpanel" class="tab-pane" id="rules-viz">
<checkin-rules-visualization></checkin-rules-visualization>
</div>
<textarea id="id_rules" class="sr-only">{{ result.rule_graph|attr_escapejson_dumps }}</textarea>
<!-- Vue app mount point -->
</div>
<textarea id="id_rules" class="sr-only">{{ result.rule_graph|attr_escapejson_dumps }}</textarea>
{% endif %}
</div>
</div>
@@ -152,10 +151,6 @@
<script type="text/javascript" src="{% static "d3/d3-drag.v2.js" %}"></script>
<script type="text/javascript" src="{% static "d3/d3-zoom.v2.js" %}"></script>
{% endcompress %}
{% compress js %}
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/checkinrules/jsonlogic-boolalg.js" %}"></script>
<script type="text/vue" src="{% static 'pretixcontrol/js/ui/checkinrules/viz-node.vue' %}"></script>
<script type="text/vue" src="{% static 'pretixcontrol/js/ui/checkinrules/checkin-rules-visualization.vue' %}"></script>
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/checkinrules.js" %}"></script>
{% endcompress %}
{% vite_hmr %}
{% vite_asset "src/pretix/static/pretixcontrol/js/ui/checkinrules/index.ts" %}
{% endblock %}

View File

@@ -12,142 +12,113 @@
{% endblock %}
{% block inside %}
{% if question %}
<h1>{% blocktrans with name=question.question %}Question: {{ name }}{% endblocktrans %}</h1>
<h1>{% blocktrans with name=question.question %}Data field: {{ name }}{% endblocktrans %}</h1>
{% else %}
<h1>{% trans "Question" %}</h1>
<h1>{% trans "Data field" %}</h1>
{% endif %}
<form action="" method="post" class="form-horizontal">
{% csrf_token %}
{% bootstrap_form_errors form %}
<div class="tabbed-form">
<fieldset>
<legend>{% trans "General" %}</legend>
{% bootstrap_field form.question layout="control" %}
{% bootstrap_field form.type layout="control" %}
{% bootstrap_field form.items layout="control" %}
{% bootstrap_field form.required layout="control" %}
<div class="alert alert-info alert-required-boolean">
{% blocktrans trimmed %}
If you mark a Yes/No question as required, it means that the user has to select Yes and No is not
accepted. If you want to allow both options, do not make this field required.
{% endblocktrans %}
</div>
<div id="valid-number">
{% bootstrap_field form.valid_number_min layout="control" %}
{% bootstrap_field form.valid_number_max layout="control" %}
</div>
<div id="valid-date">
{% bootstrap_field form.valid_date_min layout="control" %}
{% bootstrap_field form.valid_date_max layout="control" %}
</div>
<div id="valid-datetime">
{% bootstrap_field form.valid_datetime_min layout="control" %}
{% bootstrap_field form.valid_datetime_max layout="control" %}
</div>
<div id="valid-string">
{% bootstrap_field form.valid_string_length_max layout="control" %}
</div>
<div id="valid-file">
{% bootstrap_field form.valid_file_portrait layout="control" %}
</div>
<div id="answer-options">
<h3>{% trans "Answer options" %}</h3>
<noscript>
<p>{% trans "Only applicable if you choose 'Choose one/multiple from a list' above." %}</p>
</noscript>
<div class="formset" data-formset data-formset-prefix="{{ formset.prefix }}" data-formset-delete-confirm-text="{% trans "If you delete an answer option, you will no longer be able to see statistical data on customers who previously selected this option, and when such customers edit their answers, they need to select a different option." %}">
{{ formset.management_form }}
{% bootstrap_formset_errors formset %}
<div data-formset-body>
{% for form in formset %}
<div data-formset-form>
<div class="sr-only">
{{ form.id }}
{% bootstrap_field form.DELETE form_group_class="" layout="inline" %}
{% bootstrap_field form.ORDER form_group_class="" layout="inline" %}
</div>
<div class="row question-option-row">
<div class="col-xs-10">
<span class="text-muted">
{% blocktrans trimmed with id=form.instance.identifier %}
Answer option {{ id }}
{% endblocktrans %}
</span>
{% bootstrap_form_errors form %}
{% bootstrap_field form.answer layout='inline' form_group_class="" %}
</div>
<div class="col-xs-2 text-right flip">
<span>&nbsp;</span><br>
<button type="button" class="btn btn-default" data-formset-move-up-button>
<i class="fa fa-arrow-up"></i></button>
<button type="button" class="btn btn-default" data-formset-move-down-button>
<i class="fa fa-arrow-down"></i></button>
<button type="button" class="btn btn-danger" data-formset-delete-button>
<i class="fa fa-trash"></i></button>
</div>
</div>
</div>
{% endfor %}
</div>
<script type="form-template" data-formset-empty-form>
{% escapescript %}
<div data-formset-form>
<div class="sr-only">
{{ formset.empty_form.id }}
{% bootstrap_field formset.empty_form.DELETE form_group_class="" layout="inline" %}
{% bootstrap_field formset.empty_form.ORDER form_group_class="" layout="inline" %}
</div>
<div class="row question-option-row">
<div class="col-xs-10">
<span class="text-muted">
{% trans "New answer option" %}
</span>
{% bootstrap_field formset.empty_form.answer layout='inline' form_group_class="" %}
</div>
<div class="col-xs-2 text-right flip">
<span>&nbsp;</span><br>
<button type="button" class="btn btn-default" data-formset-move-up-button>
<i class="fa fa-arrow-up"></i></button>
<button type="button" class="btn btn-default" data-formset-move-down-button>
<i class="fa fa-arrow-down"></i></button>
<button type="button" class="btn btn-danger" data-formset-delete-button>
<i class="fa fa-trash"></i></button>
</div>
</div>
</div>
{% endescapescript %}
</script>
<p>
<button type="button" class="btn btn-default" data-formset-add>
<i class="fa fa-plus"></i> {% trans "Add a new option" %}</button>
</p>
</div>
</div>
</fieldset>
<fieldset>
<legend>{% trans "Advanced" %}</legend>
{% bootstrap_field form.help_text layout="control" %}
{% bootstrap_field form.identifier layout="control" %}
{% bootstrap_field form.ask_during_checkin layout="control" %}
{% bootstrap_field form.show_during_checkin layout="control" %}
{% bootstrap_field form.hidden layout="control" %}
{% bootstrap_field form.print_on_invoice layout="control" %}
<div class="form-group">
<label class="col-md-3 control-label" for="id_dependency_question">
{% trans "Question dependency" %}
<br><span class="optional">{% trans "Optional" context "form" %}</span>
</label>
<div class="col-md-4">
{% bootstrap_field form.dependency_question layout="inline" form_group_class="inner" %}
</div>
<div class="col-md-5">
<script type="text/plain" id="dependency_value_val">{{ form.instance.dependency_values|escapejson_dumps }}</script>
{% bootstrap_field form.dependency_values layout="inline" form_group_class="inner" %}
</div>
</div>
</fieldset>
{% bootstrap_field form.question layout="control" %}
{% bootstrap_field form.type layout="control" %}
<div id="valid-number">
{% bootstrap_field form.valid_number_min layout="control" %}
{% bootstrap_field form.valid_number_max layout="control" %}
</div>
<div id="valid-date">
{% bootstrap_field form.valid_date_min layout="control" %}
{% bootstrap_field form.valid_date_max layout="control" %}
</div>
<div id="valid-datetime">
{% bootstrap_field form.valid_datetime_min layout="control" %}
{% bootstrap_field form.valid_datetime_max layout="control" %}
</div>
<div id="valid-string">
{% bootstrap_field form.valid_string_length_max layout="control" %}
</div>
<div id="valid-file">
{% bootstrap_field form.valid_file_portrait layout="control" %}
</div>
<div id="answer-options">
<h3>{% trans "Answer options" %}</h3>
<noscript>
<p>{% trans "Only applicable if you choose 'Choose one/multiple from a list' above." %}</p>
</noscript>
<div class="formset" data-formset data-formset-prefix="{{ formset.prefix }}" data-formset-delete-confirm-text="{% trans "If you delete an answer option, you will no longer be able to see statistical data on customers who previously selected this option, and when such customers edit their answers, they need to select a different option." %}">
{{ formset.management_form }}
{% bootstrap_formset_errors formset %}
<div data-formset-body>
{% for form in formset %}
<div data-formset-form>
<div class="sr-only">
{{ form.id }}
{% bootstrap_field form.DELETE form_group_class="" layout="inline" %}
{% bootstrap_field form.ORDER form_group_class="" layout="inline" %}
</div>
<div class="row question-option-row">
<div class="col-xs-10">
<span class="text-muted">
{% blocktrans trimmed with id=form.instance.identifier %}
Answer option {{ id }}
{% endblocktrans %}
</span>
{% bootstrap_form_errors form %}
{% bootstrap_field form.answer layout='inline' form_group_class="" %}
</div>
<div class="col-xs-2 text-right flip">
<span>&nbsp;</span><br>
<button type="button" class="btn btn-default" data-formset-move-up-button>
<i class="fa fa-arrow-up"></i></button>
<button type="button" class="btn btn-default" data-formset-move-down-button>
<i class="fa fa-arrow-down"></i></button>
<button type="button" class="btn btn-danger" data-formset-delete-button>
<i class="fa fa-trash"></i></button>
</div>
</div>
</div>
{% endfor %}
</div>
<script type="form-template" data-formset-empty-form>
{% escapescript %}
<div data-formset-form>
<div class="sr-only">
{{ formset.empty_form.id }}
{% bootstrap_field formset.empty_form.DELETE form_group_class="" layout="inline" %}
{% bootstrap_field formset.empty_form.ORDER form_group_class="" layout="inline" %}
</div>
<div class="row question-option-row">
<div class="col-xs-10">
<span class="text-muted">
{% trans "New answer option" %}
</span>
{% bootstrap_field formset.empty_form.answer layout='inline' form_group_class="" %}
</div>
<div class="col-xs-2 text-right flip">
<span>&nbsp;</span><br>
<button type="button" class="btn btn-default" data-formset-move-up-button>
<i class="fa fa-arrow-up"></i></button>
<button type="button" class="btn btn-default" data-formset-move-down-button>
<i class="fa fa-arrow-down"></i></button>
<button type="button" class="btn btn-danger" data-formset-delete-button>
<i class="fa fa-trash"></i></button>
</div>
</div>
</div>
{% endescapescript %}
</script>
<p>
<button type="button" class="btn btn-default" data-formset-add>
<i class="fa fa-plus"></i> {% trans "Add a new option" %}</button>
</p>
</div>
</div>
{% bootstrap_field form.identifier layout="control" %}
{% bootstrap_field form.ask_during_checkin layout="control" %}
{% bootstrap_field form.show_during_checkin layout="control" %}
{% bootstrap_field form.hidden layout="control" %}
{% bootstrap_field form.print_on_invoice layout="control" %}
<div class="form-group submit-group">
<button type="submit" class="btn btn-primary btn-save">
{% trans "Save" %}

View File

@@ -0,0 +1,34 @@
{% extends "pretixcontrol/items/base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% load static %}
{% load icon %}
{% load compress %}
{% load vite %}
{% block title %}
{% trans "Questionnaires" %}
{% endblock %}
{% block inside %}
<h1>
{% trans "Questionnaires" %}
<a href="{% url "control:event.items.questions" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn btn-default pull-right">
{% icon "wrench" %} {% trans "Manage data fields" %}
</a>
</h1>
<p>
{% blocktrans trimmed %}
Questions allow your attendees to fill in additional data about their ticket. If you provide food, one
example might be to ask your users about dietary requirements.
{% endblocktrans %}
</p>
{{ request.event.settings.locales|json_script:"event_locales" }}
<div id="questionnaires-editor">
<!-- Vue app mount point -->
</div>
{% vite_hmr %}
{% vite_asset "src/pretix/static/pretixcontrol/js/ui/questionnaires/index.ts" %}
{% endblock %}

View File

@@ -1,8 +1,8 @@
{% extends "pretixcontrol/items/base.html" %}
{% load i18n %}
{% block title %}{% trans "Questions" %}{% endblock %}
{% block title %}{% trans "Data fields" %}{% endblock %}
{% block inside %}
<h1>{% trans "Questions" %}</h1>
<h1>{% trans "Data fields" %}</h1>
<p>
{% blocktrans trimmed %}
Questions allow your attendees to fill in additional data about their ticket. If you provide food, one
@@ -12,7 +12,7 @@
{% csrf_token %}
{% if 'event.items:write' in request.eventpermset %}
<p>
<a href="{% url "control:event.items.questions.add" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new question" %}
<a href="{% url "control:event.items.questions.add" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new data field" %}
</a>
</p>
{% endif %}
@@ -20,39 +20,27 @@
<table class="table table-hover table-quotas">
<thead>
<tr>
<th>{% trans "Question" %}</th>
<th>{% trans "Internal name" %}</th>
<th>{% trans "Type" %}</th>
<th class="iconcol"></th>
<th class="iconcol"></th>
<th class="iconcol"></th>
<th>{% trans "Products" %}</th>
{% if 'event.items:write' in request.eventpermset %}
<th class="action-col-2"></th>
{% endif %}
<th class="action-col-2"></th>
</tr>
</thead>
<tbody data-dnd-url="{% url "control:event.items.questions.reorder" organizer=request.event.organizer.slug event=request.event.slug %}">
<tbody>
{% for q in questions %}
<tr data-dnd-id="{{ q.id }}">
<tr>
<td>
<strong>
{% if q.pk %}
<a href="{% url "control:event.items.questions.show" organizer=request.event.organizer.slug event=request.event.slug question=q.id %}">
{% endif %}
{{ q.question }}
{% if q.pk %}
</a>
{% endif %}
<a href="{% url "control:event.items.questions.show" organizer=request.event.organizer.slug event=request.event.slug question=q.id %}">
{{ q.question }}
</a>
</strong><br>
<small class="text-muted">{{ q.identifier }}</small>
</td>
<td>
{% if q.pk %}
{{ q.get_type_display }}
{% else %}
{% trans "System question" %}
{% endif %}
{{ q.get_type_display }}
</td>
<td>
{% if q.required %}
@@ -63,42 +51,17 @@
{% if q.pk and q.ask_during_checkin %}
<span class="fa fa-check-square text-muted" data-toggle="tooltip" title="{% trans "Ask during check-in" %}"></span>
{% endif %}
</td>
<td>
{% if q.pk and q.hidden %}
<span class="fa fa-eye-slash text-muted" data-toggle="tooltip" title="{% trans "Hidden question" %}"></span>
{% endif %}
</td>
<td>
{% if q.pk %}
<ul>
{% for item in q.items.all %}
<li>
<a href="{% url "control:event.item" organizer=request.event.organizer.slug event=request.event.slug item=item.id %}">{{ item }}</a>
</li>
{% endfor %}
</ul>
{% else %}
<small>{% trans "All personalized products" %}</small>
{% endif %}
</td>
{% if 'event.items:write' in request.eventpermset %}
<td class="dnd-container">
</td>
{% endif %}
<td class="text-right flip">
{% if q.pk %}
<a href="{% url "control:event.items.questions.show" organizer=request.event.organizer.slug event=request.event.slug question=q.id %}" class="btn btn-default btn-sm"><i class="fa fa-bar-chart"></i></a>
{% if 'event.items:write' in request.eventpermset %}
<a href="{% url "control:event.items.questions.edit" organizer=request.event.organizer.slug event=request.event.slug question=q.id %}" class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
<a href="{% url "control:event.items.questions.delete" organizer=request.event.organizer.slug event=request.event.slug question=q.id %}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
{% endif %}
{% else %}
{% if 'event.settings.general:write' in request.eventpermset %}
<a href="{% url "control:event.settings" organizer=request.event.organizer.slug event=request.event.slug %}#tab-0-2-open"
class="btn btn-default btn-sm"><i class="fa fa-wrench"></i></a>
{% endif %}
<a href="{% url "control:event.items.questions.show" organizer=request.event.organizer.slug event=request.event.slug question=q.id %}" class="btn btn-default btn-sm"><i class="fa fa-bar-chart"></i></a>
{% if 'event.items:write' in request.eventpermset %}
<a href="{% url "control:event.items.questions.edit" organizer=request.event.organizer.slug event=request.event.slug question=q.id %}" class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
<a href="{% url "control:event.items.questions.delete" organizer=request.event.organizer.slug event=request.event.slug question=q.id %}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
{% endif %}
</td>
</tr>

View File

@@ -8,7 +8,7 @@
{% if page_obj.has_previous %}
{% if page_obj.previous_page_number > 1 %}
<li>
<a href="?{% url_replace request 'page' 1 %}" title="{% trans "Go to page 1" %}">
<a href="?{% url_replace request 'page' page_obj.num_pages %}" title="{% trans "Go to page 1" %}">
<span class="fa fa-angle-double-left"></span>
</a>
</li>

View File

@@ -8,45 +8,7 @@
{% csrf_token %}
{% bootstrap_form_errors form %}
{% bootstrap_field form.name layout='horizontal' %}
<div class="form-group{% if form.devicetype.errors %} has-error{% endif %}">
<label class="col-md-3 control-label">{% trans "Device type" %}</label>
<div class="col-md-9">
<div>
<div class="big-radio radio">
<label>
<input type="radio" required value="totp" name="{{ form.devicetype.html_name }}" {% if form.devicetype.value == "totp" %}checked{% endif %}>
<strong>{% trans "Smartphone with Authenticator app" %}</strong><br>
<div class="help-block">
{% blocktrans trimmed %}
Use your smartphone with any Time-based One-Time-Password app like freeOTP, Google Authenticator or Proton Authenticator.
{% endblocktrans %}
</div>
</label>
</div>
<div class="big-radio radio">
<label>
<input type="radio" required value="webauthn" name="{{ form.devicetype.html_name }}" {% if form.devicetype.value == "webauthn" %}checked{% endif %}>
<strong>{% trans "WebAuthn-compatible hardware token" %}</strong><br>
<div class="help-block">
{% blocktrans trimmed %}
Use a hardware token like the Yubikey, or other biometric authentication like fingerprint or face recognition.
{% endblocktrans %}
</div>
</label>
</div>
</div>
{% if form.devicetype.errors %}
<div class="help-block">
{% for error in form.devicetype.errors %}
<p>{{ error|escape }}</p>
{% endfor %}
</div>
{% endif %}
</div>
</div>
{% bootstrap_field form.devicetype layout='horizontal' %}
<div class="form-group submit-group">
<button type="submit" class="btn btn-primary btn-save">
{% trans "Continue" %}

View File

@@ -28,6 +28,11 @@
{% trans "iOS (iTunes)" %}
</a>
</li>
<li>
<a href="https://m.google.com/authenticator">
{% trans "Blackberry (Link via Google)" %}
</a>
</li>
</ul>
</li>
<li>

View File

@@ -1,87 +0,0 @@
{% extends "pretixcontrol/items/base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% load eventsignal %}
{% load eventurl %}
{% block title %}{% trans "Change multiple vouchers" %}{% endblock %}
{% block inside %}
<h1>
{% trans "Change multiple vouchers" %}
<small>
{% blocktrans trimmed with number=vouchers.count %}
{{ number }} selected
{% endblocktrans %}
</small>
</h1>
<form action="" method="post" class="form-horizontal">
{% csrf_token %}
{% bootstrap_form_errors form %}
<fieldset>
<legend>{% trans "Voucher details" %}</legend>
{% bootstrap_field form.max_usages layout="bulkedit" %}
{% bootstrap_field form.valid_until layout="bulkedit" %}
{% bootstrap_field form.itemvar layout="bulkedit" %}
<div class="bulk-edit-field-group">
<label class="field-toggle">
<input type="checkbox" name="_bulk" value="{{ form.prefix }}__price" {% if form.prefix|add:"__price" in bulk_selected %}checked{% endif %}>
{% trans "change" context "form_bulk" %}
</label>
<div class="field-content">
<div class="form-group">
<label class="col-md-3 control-label" for="id_tag">{% trans "Price effect" %}</label>
<div class="col-md-5">
{% bootstrap_field form.price_mode show_label=False form_group_class="" %}
</div>
<div class="col-md-4">
{% bootstrap_field form.value show_label=False form_group_class="" %}
</div>
</div>
</div>
</div>
<div class="form-group">
<div class="col-md-9 col-md-offset-3">
<div class="controls">
<div class="alert alert-info">
{% blocktrans trimmed %}
If you choose "any product" for a specific quota and choose to reserve quota for this
voucher above, the product can still be unavailable to the voucher holder if another quota
associated with the product is sold out!
{% endblocktrans %}
</div>
</div>
</div>
</div>
{% if form.subevent %}
{% bootstrap_field form.subevent layout="bulkedit" %}
{% endif %}
</fieldset>
<fieldset>
<legend>{% trans "Advanced settings" %}</legend>
{% bootstrap_field form.block_quota layout="bulkedit" %}
{% bootstrap_field form.allow_ignore_quota layout="bulkedit" %}
{% bootstrap_field form.min_usages layout="bulkedit" %}
{% bootstrap_field form.budget addon_after=request.event.currency layout="bulkedit" %}
{% bootstrap_field form.tag layout="bulkedit" %}
{% bootstrap_field form.comment layout="bulkedit" %}
{% bootstrap_field form.show_hidden_items layout="bulkedit" %}
{% bootstrap_field form.all_addons_included layout="bulkedit" %}
{% bootstrap_field form.all_bundles_included layout="bulkedit" %}
</fieldset>
<div class="form-group submit-group">
<button type="submit" class="btn btn-primary btn-save">
{% trans "Save" %}
</button>
{% if voucher.pk %}
<div class="pull-left">
<a href="{% url "control:event.voucher.delete" organizer=request.organizer.slug event=request.event.slug voucher=voucher.pk %}"
class="btn btn-danger btn-lg">
<span class="fa fa-trash"></span>
{% trans "Delete voucher" %}
</a>
</div>
{% endif %}
</div>
</form>
{% endblock %}

View File

@@ -144,18 +144,6 @@
{% endif %}
<th></th>
</tr>
{% if "event.vouchers:write" in request.eventpermset and page_obj.paginator.num_pages > 1 %}
<tr class="table-select-all warning hidden">
<td>
<input type="checkbox" name="__ALL" id="__all" data-results-total="{{ page_obj.paginator.count }}">
</td>
<td colspan="5">
<label for="__all">
{% trans "Select all results on other pages as well" %}
</label>
</td>
</tr>
{% endif %}
</thead>
<tbody>
{% for v in vouchers %}
@@ -223,10 +211,6 @@
<i class="fa fa-trash" aria-hidden="true"></i>
{% trans "Delete selected" %}
</button>
<button type="submit" class="btn btn-primary btn-save" name="action" value="edit"
formaction="{% url "control:event.vouchers.bulkedit" organizer=request.event.organizer.slug event=request.event.slug %}">
<i class="fa fa-edit"></i>{% trans "Edit selected" %}
</button>
</div>
{% endif %}
</form>

View File

@@ -348,6 +348,7 @@ urlpatterns = [
re_path(r'^questions/(?P<question>\d+)/change$', item.QuestionUpdate.as_view(),
name='event.items.questions.edit'),
re_path(r'^questions/add$', item.QuestionCreate.as_view(), name='event.items.questions.add'),
re_path(r'^questionnaires/$', item.QuestionnairesEditor.as_view(), name='event.items.questionnaires'),
re_path(r'^quotas/$', item.QuotaList.as_view(), name='event.items.quotas'),
re_path(r'^quotas/(?P<quota>\d+)/$', item.QuotaView.as_view(), name='event.items.quotas.show'),
re_path(r'^quotas/select$', typeahead.quotas_select2, name='event.items.quotas.select2'),
@@ -379,7 +380,6 @@ urlpatterns = [
re_path(r'^vouchers/bulk_add$', vouchers.VoucherBulkCreate.as_view(), name='event.vouchers.bulk'),
re_path(r'^vouchers/bulk_add/mail_preview$', vouchers.VoucherBulkMailPreview.as_view(), name='event.vouchers.bulk.mail_preview'),
re_path(r'^vouchers/bulk_action$', vouchers.VoucherBulkAction.as_view(), name='event.vouchers.bulkaction'),
re_path(r'^vouchers/bulk_edit$', vouchers.VoucherBulkUpdateView.as_view(), name='event.vouchers.bulkedit'),
re_path(r'^vouchers/import/$', modelimport.VoucherImportView.as_view(), name='event.vouchers.import'),
re_path(r'^vouchers/import/(?P<file>[^/]+)/$', modelimport.VoucherProcessView.as_view(), name='event.vouchers.import.process'),
re_path(r'^orders/(?P<code>[0-9A-Z]+)/transition$', orders.OrderTransition.as_view(),

View File

@@ -55,7 +55,7 @@ from django.utils.functional import cached_property
from django.utils.timezone import now
from django.utils.translation import gettext, gettext_lazy as _
from django.views.decorators.http import require_http_methods
from django.views.generic import ListView
from django.views.generic import ListView, TemplateView
from django.views.generic.detail import DetailView, SingleObjectMixin
from django_countries.fields import Country
@@ -435,87 +435,7 @@ class QuestionList(ListView):
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
questions = []
if self.request.event.settings.attendee_names_asked:
questions.append(
FakeQuestion(
id='attendee_name_parts',
question=_('Attendee name'),
position=self.request.event.settings.system_question_order.get(
'attendee_name_parts', 0
),
required=self.request.event.settings.attendee_names_required,
)
)
if self.request.event.settings.attendee_emails_asked:
questions.append(
FakeQuestion(
id='attendee_email',
question=_('Attendee email'),
position=self.request.event.settings.system_question_order.get(
'attendee_email', 0
),
required=self.request.event.settings.attendee_emails_required,
)
)
if self.request.event.settings.attendee_company_asked:
questions.append(
FakeQuestion(
id='company',
question=_('Company'),
position=self.request.event.settings.system_question_order.get(
'company', 0
),
required=self.request.event.settings.attendee_company_required,
)
)
if self.request.event.settings.attendee_addresses_asked:
questions.append(
FakeQuestion(
id='street',
question=_('Street'),
position=self.request.event.settings.system_question_order.get(
'street', 0
),
required=self.request.event.settings.attendee_addresses_required,
)
)
questions.append(
FakeQuestion(
id='zipcode',
question=_('ZIP code'),
position=self.request.event.settings.system_question_order.get(
'zipcode', 0
),
required=self.request.event.settings.attendee_addresses_required,
)
)
questions.append(
FakeQuestion(
id='city',
question=_('City'),
position=self.request.event.settings.system_question_order.get(
'city', 0
),
required=self.request.event.settings.attendee_addresses_required,
)
)
questions.append(
FakeQuestion(
id='country',
question=_('Country'),
position=self.request.event.settings.system_question_order.get(
'country', 0
),
required=self.request.event.settings.attendee_addresses_required,
)
)
questions += list(ctx['questions'])
questions = list(ctx['questions'])
questions.sort(key=lambda q: q.position)
ctx['questions'] = questions
return ctx
@@ -831,6 +751,11 @@ class QuestionCreate(EventPermissionRequiredMixin, QuestionMixin, CreateView):
return ret
class QuestionnairesEditor(EventPermissionRequiredMixin, TemplateView):
permission = 'can_change_items'
template_name = 'pretixcontrol/items/questionnaires.html'
class QuotaList(PaginationMixin, ListView):
model = Quota
context_object_name = 'quotas'

View File

@@ -42,7 +42,7 @@ from django.conf import settings
from django.contrib import messages
from django.core.exceptions import PermissionDenied, ValidationError
from django.db import connection, transaction
from django.db.models import Count, Exists, OuterRef, Sum
from django.db.models import Exists, OuterRef, Sum
from django.http import (
Http404, HttpResponse, HttpResponseBadRequest, HttpResponseRedirect,
JsonResponse,
@@ -55,7 +55,7 @@ from django.utils.safestring import mark_safe
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from django.views.generic import (
CreateView, FormView, ListView, TemplateView, UpdateView, View,
CreateView, ListView, TemplateView, UpdateView, View,
)
from django_scopes import scopes_disabled
@@ -70,9 +70,7 @@ from pretix.base.services.vouchers import vouchers_send
from pretix.base.templatetags.rich_text import markdown_compile_email
from pretix.base.views.tasks import AsyncFormView
from pretix.control.forms.filter import VoucherFilterForm, VoucherTagFilterForm
from pretix.control.forms.vouchers import (
VoucherBulkEditForm, VoucherBulkForm, VoucherForm,
)
from pretix.control.forms.vouchers import VoucherBulkForm, VoucherForm
from pretix.control.permissions import EventPermissionRequiredMixin
from pretix.control.signals import voucher_form_class
from pretix.control.views import PaginationMixin
@@ -82,35 +80,7 @@ from pretix.helpers.models import modelcopy
from pretix.multidomain.urlreverse import build_absolute_uri
class VoucherQueryMixin:
@cached_property
def request_data(self):
if self.request.method == "POST":
return self.request.POST
return self.request.GET
@scopes_disabled() # we have an event check here, and we can save some performance on subqueries
def get_queryset(self):
qs = self.request.event.vouchers.exclude(
Exists(WaitingListEntry.objects.filter(voucher_id=OuterRef('pk')))
)
if self.filter_form.is_valid():
qs = self.filter_form.filter_qs(qs)
if 'voucher' in self.request_data and '__ALL' not in self.request_data:
qs = qs.filter(
id__in=self.request_data.getlist('voucher')
)
return qs
@cached_property
def filter_form(self):
return VoucherFilterForm(data=self.request_data, prefix='filter', event=self.request.event)
class VoucherList(VoucherQueryMixin, PaginationMixin, EventPermissionRequiredMixin, ListView):
class VoucherList(PaginationMixin, EventPermissionRequiredMixin, ListView):
model = Voucher
context_object_name = 'vouchers'
template_name = 'pretixcontrol/vouchers/index.html'
@@ -118,15 +88,25 @@ class VoucherList(VoucherQueryMixin, PaginationMixin, EventPermissionRequiredMix
@scopes_disabled() # we have an event check here, and we can save some performance on subqueries
def get_queryset(self):
return Voucher.annotate_budget_used(super().get_queryset().select_related(
qs = Voucher.annotate_budget_used(self.request.event.vouchers.exclude(
Exists(WaitingListEntry.objects.filter(voucher_id=OuterRef('pk')))
).select_related(
'item', 'variation', 'seat'
))
if self.filter_form.is_valid():
qs = self.filter_form.filter_qs(qs)
return qs
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['filter_form'] = self.filter_form
return ctx
@cached_property
def filter_form(self):
return VoucherFilterForm(data=self.request.GET, event=self.request.event)
def get(self, request, *args, **kwargs):
if request.GET.get("download", "") == "yes":
return self._download_csv()
@@ -313,12 +293,6 @@ class VoucherUpdate(EventPermissionRequiredMixin, UpdateView):
f.disabled = True
return form
def get_form_kwargs(self):
return {
**super().get_form_kwargs(),
"event": self.request.event,
}
def get_object(self, queryset=None) -> VoucherForm:
url = resolve(self.request.path_info)
try:
@@ -629,21 +603,26 @@ class VoucherRNG(EventPermissionRequiredMixin, View):
})
class VoucherBulkAction(VoucherQueryMixin, EventPermissionRequiredMixin, View):
class VoucherBulkAction(EventPermissionRequiredMixin, View):
permission = 'event.vouchers:write'
@cached_property
def objects(self):
return self.request.event.vouchers.filter(
id__in=self.request.POST.getlist('voucher')
)
@transaction.atomic
def post(self, request, *args, **kwargs):
if request.POST.get('action') == 'delete':
return render(request, 'pretixcontrol/vouchers/delete_bulk.html', {
'allowed': self.get_queryset().filter(redeemed=0),
'forbidden': self.get_queryset().exclude(redeemed=0),
'allowed': self.objects.filter(redeemed=0),
'forbidden': self.objects.exclude(redeemed=0),
})
elif request.POST.get('action') == 'delete_confirm':
log_entries = []
to_delete = []
to_update = []
for obj in self.get_queryset():
for obj in self.objects:
if obj.allow_delete():
log_entries.append(obj.log_action('pretix.voucher.deleted', user=self.request.user, save=False))
to_delete.append(obj.pk)
@@ -653,14 +632,12 @@ class VoucherBulkAction(VoucherQueryMixin, EventPermissionRequiredMixin, View):
'bulk': True
}, save=False))
obj.max_usages = min(obj.redeemed, obj.max_usages)
to_update.append(obj)
obj.save(update_fields=['max_usages'])
if to_delete:
CartPosition.objects.filter(addon_to__voucher_id__in=to_delete).delete()
CartPosition.objects.filter(voucher_id__in=to_delete).delete()
Voucher.objects.filter(pk__in=to_delete).delete()
if to_update:
Voucher.objects.bulk_update(to_update, ['max_usages'])
LogEntry.bulk_create_and_postprocess(log_entries)
messages.success(request, _('The selected vouchers have been deleted or disabled.'))
@@ -671,117 +648,3 @@ class VoucherBulkAction(VoucherQueryMixin, EventPermissionRequiredMixin, View):
'organizer': self.request.event.organizer.slug,
'event': self.request.event.slug,
})
class VoucherBulkUpdateView(VoucherQueryMixin, EventPermissionRequiredMixin, FormView):
template_name = 'pretixcontrol/vouchers/bulk_edit.html'
permission = 'event.vouchers:write'
context_object_name = 'vouchers'
form_class = VoucherBulkEditForm
def get_queryset(self):
return super().get_queryset().prefetch_related(None).order_by()
def get(self, request, *args, **kwargs):
return HttpResponse(status=405)
@cached_property
def is_submitted(self):
# Usually, django considers a form "bound" / "submitted" on every POST request. However, this view is always
# called with POST method, even if just to pass the selection of objects to work on, so we want to modify
# that behavior
return '_bulk' in self.request.POST
def get_form_kwargs(self):
initial = {}
mixed_values = set()
qs = self.get_queryset().annotate()
fields = (
'valid_until', 'block_quota', 'allow_ignore_quota', 'value', 'tag', 'comment', 'max_usages',
'min_usages', 'price_mode', 'subevent', 'show_hidden_items', 'all_addons_included', 'all_bundles_included',
'budget',
)
for f in fields:
existing_values = list(qs.order_by(f).values(f).annotate(c=Count('*')))
if len(existing_values) == 1:
initial[f] = existing_values[0][f]
elif len(existing_values) > 1:
mixed_values.add(f)
if f == "max_usages":
initial[f] = 1
else:
initial[f] = None
existing_values = list(qs.order_by("item", "variation", "quota").values("item", "variation", "quota").annotate(c=Count('*')))
if len(existing_values) == 1:
i = existing_values[0]
if i["quota"]:
initial["itemvar"] = f'q-{i["quota"]}'
elif i["variation"]:
initial["itemvar"] = f'{i["item"]}-{i["variation"]}'
elif i["item"]:
initial["itemvar"] = f'{i["item"]}'
else:
initial["itemvar"] = None
elif len(existing_values) > 1:
mixed_values.add("itemvar")
initial["itemvar"] = None
kwargs = super().get_form_kwargs()
kwargs['event'] = self.request.event
kwargs['prefix'] = 'bulkedit'
kwargs['initial'] = initial
kwargs['queryset'] = self.get_queryset()
kwargs['mixed_values'] = mixed_values
if not self.is_submitted:
kwargs['data'] = None
kwargs['files'] = None
return kwargs
def get_success_url(self):
return reverse('control:event.vouchers', kwargs={
'organizer': self.request.event.organizer.slug,
'event': self.request.event.slug,
})
def form_valid(self, form):
log_entries = []
# Main form
form.save()
data = {
k: v
for k, v in form.cleaned_data.items()
if k in form.changed_data
}
data['_raw_bulk_data'] = self.request.POST.dict()
for obj in self.get_queryset():
log_entries.append(
obj.log_action('pretix.voucher.changed', data=data, user=self.request.user, save=False)
)
LogEntry.bulk_create_and_postprocess(log_entries)
messages.success(self.request, _('Your changes have been saved.'))
return super().form_valid(form)
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['vouchers'] = self.get_queryset()
ctx['bulk_selected'] = self.request.POST.getlist("_bulk")
return ctx
@transaction.atomic
def post(self, request, *args, **kwargs):
form = self.get_form()
is_valid = (
self.is_submitted and
form.is_valid()
)
if is_valid:
return self.form_valid(form)
else:
if self.is_submitted:
messages.error(self.request, _('We could not save your changes. See below for details.'))
return self.form_invalid(form)

View File

@@ -1,63 +0,0 @@
#
# 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/>.
#
import os
import re
import shlex
from compressor.exceptions import FilterError
from compressor.filters import CompilerFilter
from django.conf import settings
class VueCompiler(CompilerFilter):
# Based on work (c) Laura Klünder in https://github.com/codingcatgirl/django-vue-rollup
# Released under Apache License 2.0
def __init__(self, content, attrs, **kwargs):
config_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'static', 'npm_dir')
node_path = os.path.join(settings.STATIC_ROOT, 'node_prefix', 'node_modules')
self.rollup_bin = os.path.join(node_path, 'rollup', 'dist', 'bin', 'rollup')
rollup_config = os.path.join(config_dir, 'rollup.config.js')
if not os.path.exists(self.rollup_bin) and not settings.DEBUG:
raise FilterError("Rollup not installed or pretix not built properly, please run 'make npminstall' in source root.")
command = (
' '.join((
'NODE_PATH=' + shlex.quote(node_path),
shlex.quote(self.rollup_bin),
'-c',
shlex.quote(rollup_config))
) +
' --input {infile} -n {export_name} --file {outfile}'
)
super().__init__(content, command=command, **kwargs)
def input(self, **kwargs):
if self.filename is None:
raise FilterError('VueCompiler can only compile files, not inline code.')
if not os.path.exists(self.rollup_bin):
raise FilterError("Rollup not installed, please run 'make npminstall' in source root.")
self.options += (('export_name', re.sub(
r'^([a-z])|[^a-z0-9A-Z]+([a-zA-Z0-9])?',
lambda s: s.group(0)[-1].upper(),
os.path.basename(self.filename).split('.')[0]
)),)
return super().input(**kwargs)

View File

@@ -25,7 +25,7 @@ import time
from django.conf import settings
from django.contrib.auth import login as auth_login
from django.contrib.gis import geoip2
from django.contrib.gis.geoip2 import GeoIP2
from django.core.cache import cache
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
@@ -63,20 +63,14 @@ def get_user_agent_hash(request):
_geoip = None
def get_geoip() -> geoip2.GeoIP2:
# See https://code.djangoproject.com/ticket/36988#ticket
def _get_country(request):
global _geoip
geoip2.SUPPORTED_DATABASE_TYPES.add("Geoacumen-Country")
if not _geoip:
_geoip = geoip2.GeoIP2()
return _geoip
_geoip = GeoIP2()
def _get_country(request):
try:
res = get_geoip().country(get_client_ip(request))
res = _geoip.country(get_client_ip(request))
except AddressNotFoundError:
return None
return res['country_code']

View File

@@ -5,7 +5,7 @@ msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-03-17 14:05+0000\n"
"PO-Revision-Date: 2026-03-18 02:00+0000\n"
"PO-Revision-Date: 2026-03-17 14:27+0000\n"
"Last-Translator: Raphael Michel <michel@rami.io>\n"
"Language-Team: German <https://translate.pretix.eu/projects/pretix/pretix/"
"de/>\n"
@@ -8453,7 +8453,7 @@ msgstr "Zahlungseinstellungen"
#: pretix/base/permissions.py:203
#: pretix/control/templates/pretixcontrol/event/tax.html:120
msgid "Tax settings"
msgstr "Steuereinstellungen"
msgstr "Steuer-Einstellungen"
#: pretix/base/permissions.py:209
msgid "Invoicing settings"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-03-17 14:05+0000\n"
"PO-Revision-Date: 2026-03-18 02:00+0000\n"
"PO-Revision-Date: 2026-03-17 14:34+0000\n"
"Last-Translator: Raphael Michel <michel@rami.io>\n"
"Language-Team: German (informal) <https://translate.pretix.eu/projects/"
"pretix/pretix/de_Informal/>\n"
@@ -8446,7 +8446,7 @@ msgstr "Zahlungseinstellungen"
#: pretix/base/permissions.py:203
#: pretix/control/templates/pretixcontrol/event/tax.html:120
msgid "Tax settings"
msgstr "Steuereinstellungen"
msgstr "Steuer-Einstellungen"
#: pretix/base/permissions.py:209
msgid "Invoicing settings"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-03-17 14:05+0000\n"
"PO-Revision-Date: 2026-03-18 12:23+0000\n"
"PO-Revision-Date: 2026-03-03 20:00+0000\n"
"Last-Translator: CVZ-es <damien.bremont@casadevelazquez.org>\n"
"Language-Team: Spanish <https://translate.pretix.eu/projects/pretix/pretix/"
"es/>\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.16.2\n"
"X-Generator: Weblate 5.16.1\n"
#: pretix/_base_settings.py:87
msgid "English"
@@ -408,8 +408,10 @@ msgstr ""
#: pretix/api/serializers/organizer.py:482
#: pretix/control/views/organizer.py:1039
#, fuzzy
#| msgid "Account information"
msgid "Account invitation"
msgstr "Invitación a crear una cuenta"
msgstr "Información de la cuenta"
#: pretix/api/serializers/organizer.py:503
#: pretix/control/views/organizer.py:1138
@@ -3964,9 +3966,10 @@ msgid "Peppol participant ID"
msgstr "Identificador de participante Peppol"
#: pretix/base/invoicing/peppol.py:211
#, fuzzy
#| msgid "The Peppol participant ID is not registered on the Peppol network."
msgid "The Peppol participant ID does not match your VAT ID."
msgstr ""
"El identificador de participante de Peppol no coincide con el número de IVA."
msgstr "El ID de participante de Peppol no está registrado en la red Peppol."
#: pretix/base/invoicing/peppol.py:214
msgctxt "peppol_invoice"
@@ -4988,7 +4991,7 @@ msgstr ""
#: pretix/base/models/event.py:607 pretix/base/models/organizer.py:94
msgid "The slug may only contain letters, numbers, dots and dashes."
msgstr ""
"El nombre de archivo solo puede contener letras, números, puntos y guiones."
"La URL semántica solo puede contener letras, números, puntos y guiones."
#: pretix/base/models/event.py:624 pretix/base/models/event.py:1551
msgid "Show in lists"
@@ -6890,8 +6893,10 @@ msgstr ""
"pocos minutos para activar para todos los usuarios."
#: pretix/base/models/organizer.py:378
#, fuzzy
#| msgid "Event permissions"
msgid "All event permissions"
msgstr "Todos los permisos del evento"
msgstr "Permisos para eventos"
#: pretix/base/models/organizer.py:379
#: pretix/control/templates/pretixcontrol/organizers/team_edit.html:34
@@ -6899,8 +6904,10 @@ msgid "Event permissions"
msgstr "Permisos para eventos"
#: pretix/base/models/organizer.py:380
#, fuzzy
#| msgid "Organizer permissions"
msgid "All organizer permissions"
msgstr "Todos los permisos de organizador"
msgstr "Permisos del organizador"
#: pretix/base/models/organizer.py:381
#: pretix/control/templates/pretixcontrol/organizers/team_edit.html:25
@@ -8407,32 +8414,36 @@ msgstr "Su archivo de diseño no es un diseño válido. Mensaje de error: {}"
#: pretix/base/permissions.py:314 pretix/base/permissions.py:331
msgctxt "permission_level"
msgid "View"
msgstr "Ver"
msgstr ""
#: pretix/base/permissions.py:164 pretix/base/permissions.py:169
#: pretix/base/permissions.py:174 pretix/base/permissions.py:179
#: pretix/base/permissions.py:286 pretix/base/permissions.py:315
#, fuzzy
#| msgid "Save and check"
msgctxt "permission_level"
msgid "View and change"
msgstr "Ver y modificar"
msgstr "Guardar y verificar"
#: pretix/base/permissions.py:168
#, fuzzy
#| msgid "API tokens"
msgid "API only"
msgstr "Solo API"
msgstr "Tokens de API"
#: pretix/base/permissions.py:173
msgid ""
"Menu item will only show up if the user has permission for general settings."
msgstr ""
"Esta opción del menú solo aparecerá si el usuario tiene permiso para acceder "
"a la configuración general."
#: pretix/base/permissions.py:177 pretix/base/permissions.py:231
#: pretix/base/permissions.py:285 pretix/base/permissions.py:313
#: pretix/base/permissions.py:330
#, fuzzy
#| msgid "Revoke access"
msgctxt "permission_level"
msgid "No access"
msgstr "Sin acceso"
msgstr "Revocar el acceso"
#: pretix/base/permissions.py:188
#: pretix/control/templates/pretixcontrol/event/settings.html:7
@@ -8446,8 +8457,6 @@ msgid ""
"This includes access to all settings not listed explicitly below, including "
"plugin settings."
msgstr ""
"Esto incluye el acceso a todos los ajustes que no se enumeran explícitamente "
"a continuación, incluidos los ajustes de los complementos."
#: pretix/base/permissions.py:197
#: pretix/control/templates/pretixcontrol/event/payment.html:6
@@ -8461,79 +8470,100 @@ msgid "Tax settings"
msgstr "Configuración de impuestos"
#: pretix/base/permissions.py:209
#, fuzzy
#| msgid "Invoice settings"
msgid "Invoicing settings"
msgstr "Configuración de facturación"
msgstr "Configuración de la factura"
#: pretix/base/permissions.py:215
#, fuzzy
#| msgctxt "subevent"
#| msgid "Event series date added"
msgid "Event series dates"
msgstr "Fechas de la serie de eventos"
msgstr "Fechas de la serie de eventos añadidas"
#: pretix/base/permissions.py:221
#, fuzzy
#| msgid "Product name and variation"
msgid "Products, quotas and questions"
msgstr "Productos, cuotas y preguntas"
msgstr "Nombre del producto y variación"
#: pretix/base/permissions.py:224
msgid "Also includes related objects like categories or discounts."
msgstr "También incluye elementos relacionados, como categorías o descuentos."
msgstr ""
#: pretix/base/permissions.py:232
#, fuzzy
#| msgid "All check-ins"
msgctxt "permission_level"
msgid "Only check-in"
msgstr "Solo check-in"
msgstr "Todos los check-ins"
#: pretix/base/permissions.py:233
#, fuzzy
#| msgid "View full log"
msgctxt "permission_level"
msgid "View all"
msgstr "Ver todo"
msgstr "Ver log completo"
#: pretix/base/permissions.py:234
#, fuzzy
#| msgid "Valid check-in"
msgctxt "permission_level"
msgid "View all and check-in"
msgstr "Ver todo y check-in"
msgstr "Check-in válido"
#: pretix/base/permissions.py:235
#, fuzzy
#| msgid "View all upcoming events"
msgctxt "permission_level"
msgid "View all and change"
msgstr "Ver todo y modificar"
msgstr "Ver todos los próximos eventos"
#: pretix/base/permissions.py:236
msgid "Includes the ability to cancel and refund individual orders."
msgstr "Incluye la posibilidad de cancelar y reembolsar pedidos individuales."
msgstr ""
#: pretix/base/permissions.py:238
#, fuzzy
#| msgid "An entry has been added to the waiting list."
msgid "Also includes related objects like the waiting list."
msgstr "También incluye elementos relacionados, como la lista de espera."
msgstr "Se ha añadido una entrada a la lista de espera."
#: pretix/base/permissions.py:248
#, fuzzy
#| msgid "Generate cancellation"
msgid "Full event or date cancellation"
msgstr "Cancelación total del evento o de la fecha"
msgstr "Generar cancelación"
#: pretix/base/permissions.py:252
#, fuzzy
#| msgid "Sale not allowed"
msgctxt "permission_level"
msgid "Not allowed"
msgstr "No está permitido"
msgstr "Venta no permitida"
#: pretix/base/permissions.py:253
#, fuzzy
#| msgid "Allowed titles"
msgctxt "permission_level"
msgid "Allowed"
msgstr "Permitido"
msgstr "Titulos permitidos"
#: pretix/base/permissions.py:268
msgctxt "permission_level"
msgid "Access existing events"
msgstr "Acceder a los eventos existentes"
msgstr ""
#: pretix/base/permissions.py:269
msgctxt "permission_level"
msgid "Access existing and create new events"
msgstr "Acceder a eventos existentes y crear nuevos eventos"
msgstr ""
#: pretix/base/permissions.py:271
msgid ""
"The level of access to events is determined in detail by the settings below."
msgstr ""
"El nivel de acceso a los eventos se determina en detalle mediante los "
"ajustes que se indican a continuación."
#: pretix/base/permissions.py:275 pretix/control/navigation.py:143
#: pretix/control/navigation.py:462 pretix/control/navigation.py:512
@@ -8552,17 +8582,12 @@ msgid ""
"This includes access to all organizer-level functionality not listed "
"explicitly below, including plugin settings."
msgstr ""
"Esto incluye el acceso a todas las funciones de nivel de organizador que no "
"se enumeran explícitamente a continuación, incluida la configuración de los "
"complementos."
#: pretix/base/permissions.py:287
msgid ""
"Includes the ability to give someone (including oneself) additional "
"permissions."
msgstr ""
"Incluye la posibilidad de conceder permisos adicionales a otra persona "
"(incluido uno mismo)."
#: pretix/base/permissions.py:298 pretix/control/navigation.py:608
#: pretix/control/templates/pretixcontrol/organizers/customers.html:6
@@ -8580,12 +8605,12 @@ msgid ""
"Includes the ability to give access to events and data oneself does not have "
"access to."
msgstr ""
"Incluye la posibilidad de conceder acceso a eventos y datos a los que uno "
"mismo no tiene acceso."
#: pretix/base/permissions.py:321
#, fuzzy
#| msgid "Seating plan"
msgid "Seating plans"
msgstr "Planes de asientos"
msgstr "Plan de asientos"
#: pretix/base/permissions.py:327 pretix/control/navigation.py:712
#: pretix/control/templates/pretixcontrol/organizers/outgoing_mails.html:8
@@ -9229,12 +9254,12 @@ msgid "Czech National Bank"
msgstr "Banco Nacional Checo"
#: pretix/base/services/export.py:94 pretix/base/services/export.py:154
#, fuzzy
#| msgid "You do not have sufficient permission to perform this export."
msgid ""
"Export not found or you do not have sufficient permission to perform this "
"export."
msgstr ""
"No se ha encontrado la exportación o no tienes los permisos suficientes para "
"realizarla."
msgstr "No tiene permiso suficiente para realizar esta exportación."
#: pretix/base/services/export.py:107 pretix/base/services/export.py:179
#: pretix/base/services/export.py:357
@@ -13710,7 +13735,25 @@ msgid "Contact"
msgstr "Contacto"
#: pretix/base/templates/pretixbase/email/shred_completed.txt:2
#, python-format
#, fuzzy, python-format
#| msgid ""
#| "Hello,\n"
#| "\n"
#| "we hereby confirm that the following data shredding job has been "
#| "completed:\n"
#| "\n"
#| "Organizer: %(organizer)s\n"
#| "\n"
#| "Event: %(event)s\n"
#| "\n"
#| "Data selection: %(shredders)s\n"
#| "\n"
#| "Start time: %(start_time)s (new data added after this time might not have "
#| "been deleted)\n"
#| "\n"
#| "Best regards,\n"
#| "\n"
#| "Your pretix team\n"
msgid ""
"Hello,\n"
"\n"
@@ -13745,7 +13788,7 @@ msgstr ""
"\n"
"Atentamente,\n"
"\n"
"El equipo de %(instance)s\n"
"Tu equipo de pretix\n"
#: pretix/base/templates/pretixbase/forms/widgets/checkbox_sales_channel_option.html:16
msgid ""
@@ -14307,12 +14350,16 @@ msgid "This is an event series"
msgstr "Esta es una serie de eventos"
#: pretix/control/forms/event.py:135 pretix/control/forms/event.py:363
#, fuzzy
#| msgid ""
#| "You do not have sufficient permission to enable plugins that need to be "
#| "enabled for the entire organizer account."
msgid ""
"You do not have a sufficient level of access on the event you selected to "
"copy it to the desired organizer."
msgstr ""
"No tiene los permisos necesarios para copiar el evento que ha seleccionado "
"al organizador deseado."
"No tienes permisos suficientes para habilitar los plugins que deben "
"habilitarse para toda su cuenta de organizador."
#: pretix/control/forms/event.py:143
msgid ""
@@ -14392,8 +14439,6 @@ msgid ""
"You cannot choose a team that would give you more access than you have on "
"the event you are copying."
msgstr ""
"No puede elegir un equipo que le otorgue más permisos de los que ya tiene en "
"el evento que está copiando."
#: pretix/control/forms/event.py:344
msgid "Copy configuration from"
@@ -14409,8 +14454,6 @@ msgid ""
"You cannot choose an event on which you have less access than the team you "
"selected in the previous step."
msgstr ""
"No puede elegir un evento al que tenga menos derechos de acceso que el "
"equipo que seleccionó en el paso anterior."
#: pretix/control/forms/event.py:387 pretix/control/forms/item.py:1304
#: pretix/control/forms/subevents.py:411
@@ -16773,7 +16816,7 @@ msgstr "Sólo puede establecer un dominio de organizador."
#: pretix/control/forms/organizer.py:322
msgid "Provided by a plugin"
msgstr "Proporcionado por un plugin"
msgstr ""
#: pretix/control/forms/organizer.py:438
msgid ""
@@ -17665,8 +17708,10 @@ msgid "The order has been denied (comment: \"{comment}\")."
msgstr "El pedido ha sido denegado (comentario: \"{comment}\")."
#: pretix/control/logdisplay.py:521
#, fuzzy
#| msgid "The order has been overpaid."
msgid "The customer VAT ID has been verified."
msgstr "Se ha verificado el número de identificación fiscal del cliente."
msgstr "El pedido ha sido pagado de más."
#: pretix/control/logdisplay.py:522
#, python-brace-format
@@ -17970,9 +18015,10 @@ msgid "{user} has been invited to the team."
msgstr "{user} ha sido invitado al equipo."
#: pretix/control/logdisplay.py:644
#, python-brace-format
#, fuzzy, python-brace-format
#| msgid "Invite for {user} has been resent."
msgid "Invite for {user} has been deleted."
msgstr "Se ha eliminado la invitación para {user}."
msgstr "La invitación para {user} ha sido reenviada."
#: pretix/control/logdisplay.py:645
#, python-brace-format
@@ -20292,7 +20338,21 @@ msgid "Add property"
msgstr "Añadir propiedad"
#: pretix/control/templates/pretixcontrol/email/confirmation_code.txt:1
#, python-format
#, fuzzy, python-format
#| msgid ""
#| "Hello,\n"
#| "\n"
#| "%(reason)s\n"
#| "\n"
#| " %(code)s\n"
#| "\n"
#| "Please do never give this code to another person. Our support team will "
#| "never ask for this code.\n"
#| "\n"
#| "If this code was not requested by you, please contact us immediately.\n"
#| "\n"
#| "Best regards,\n"
#| "Your pretix team\n"
msgid ""
"Hello,\n"
"\n"
@@ -20321,7 +20381,7 @@ msgstr ""
"inmediato.\n"
"\n"
"Atentamente,\n"
"El equipo de %(instance)s\n"
"El equipo de pretix\n"
#: pretix/control/templates/pretixcontrol/email/email_setup.txt:1
#, python-format
@@ -20359,7 +20419,17 @@ msgstr ""
"Tu equipo de %(instance)s\n"
#: pretix/control/templates/pretixcontrol/email/forgot.txt:1
#, python-format
#, fuzzy, python-format
#| msgid ""
#| "Hello,\n"
#| "\n"
#| "you requested a new password. Please go to the following page to reset "
#| "your password:\n"
#| "\n"
#| "%(url)s\n"
#| "\n"
#| "Best regards, \n"
#| "Your pretix team\n"
msgid ""
"Hello,\n"
"\n"
@@ -20379,10 +20449,27 @@ msgstr ""
"%(url)s \n"
"\n"
"Saludos, \n"
"El equipo de %(instance)s\n"
"El equipo de pretix\n"
#: pretix/control/templates/pretixcontrol/email/invitation.txt:1
#, python-format
#, fuzzy, python-format
#| msgid ""
#| "Hello,\n"
#| "\n"
#| "you have been invited to a team on pretix, a platform to perform event\n"
#| "ticket sales.\n"
#| "\n"
#| "Organizer: %(organizer)s\n"
#| "Team: %(team)s\n"
#| "\n"
#| "If you want to join that team, just click on the following link:\n"
#| "%(url)s\n"
#| "\n"
#| "If you do not want to join, you can safely ignore or delete this email.\n"
#| "\n"
#| "Best regards, \n"
#| "\n"
#| "Your pretix team\n"
msgid ""
"Hello,\n"
"\n"
@@ -20404,8 +20491,7 @@ msgid ""
msgstr ""
"Hola, \n"
"\n"
"usted ha sido invitado a un equipo de %(instance)s, una plataforma para "
"realizar \n"
"usted ha sido invitado a un equipo en pretix, una plataforma para realizar \n"
"ventas de entradas de eventos. \n"
"\n"
"Organizador: %(organizer)s\n"
@@ -20418,7 +20504,7 @@ msgstr ""
"\n"
"Saludos cordiales, \n"
"\n"
"El equipo de %(instance)s\n"
"Su equipo Pretix\n"
#: pretix/control/templates/pretixcontrol/email/login_notice.txt:1
#, python-format
@@ -20456,7 +20542,24 @@ msgstr ""
"El equipo de %(instance)s\n"
#: pretix/control/templates/pretixcontrol/email/security_notice.txt:1
#, python-format
#, fuzzy, python-format
#| msgid ""
#| "Hello,\n"
#| "\n"
#| "this is to inform you that the account information of your pretix account "
#| "has been\n"
#| "changed. In particular, the following changes have been performed:\n"
#| "\n"
#| "%(messages)s\n"
#| "\n"
#| "If this change was not performed by you, please contact us immediately.\n"
#| "\n"
#| "You can review and change your account settings here:\n"
#| "\n"
#| "%(url)s\n"
#| "\n"
#| "Best regards, \n"
#| "Your pretix team\n"
msgid ""
"Hello,\n"
"\n"
@@ -20477,8 +20580,8 @@ msgid ""
msgstr ""
"Hola, \n"
"\n"
"esto es para informarle que la información de su cuenta del equipo de %"
"(instance)s ha sido cambiada. \n"
"esto es para informarle que la información de su cuenta pretix ha sido "
"cambiada . \n"
"En particular, se han realizado las siguientes modificaciones: \n"
"\n"
"%(messages)s\n"
@@ -20491,7 +20594,7 @@ msgstr ""
"%(url)s \n"
"\n"
"Saludos cordiales, \n"
"El equipo de %(instance)s\n"
"su equipo de Pretix\n"
#: pretix/control/templates/pretixcontrol/email_setup.html:8
#: pretix/control/templates/pretixcontrol/email_setup_simple.html:8
@@ -20761,8 +20864,10 @@ msgstr ""
"entradas, puede hacerlo a través de esta opción."
#: pretix/control/templates/pretixcontrol/event/dangerzone.html:51
#, fuzzy
#| msgid "Permissions"
msgid "No permission"
msgstr "No tiene permiso"
msgstr "Permisos"
#: pretix/control/templates/pretixcontrol/event/dangerzone.html:59
#: pretix/control/templates/pretixcontrol/event/dangerzone.html:72
@@ -23321,8 +23426,10 @@ msgid "Edit question"
msgstr "Editar pregunta"
#: pretix/control/templates/pretixcontrol/items/question.html:55
#, fuzzy
#| msgid "You do not have permission to view this content."
msgid "No permission to view answers."
msgstr "No tiene permiso para ver las respuestas."
msgstr "No tienes permiso para ver este contenido."
#: pretix/control/templates/pretixcontrol/items/question.html:63
msgid "No matching answers found."
@@ -24971,8 +25078,10 @@ msgstr "No hay próxima ejecución programada"
#: pretix/control/templates/pretixcontrol/orders/export.html:37
#: pretix/control/templates/pretixcontrol/organizers/export.html:37
#, fuzzy
#| msgid "Exporter not found"
msgid "Exporter not found or no permission"
msgstr "No se ha encontrado el exportador o no se dispone de permiso"
msgstr "Exportador no encontrado"
#: pretix/control/templates/pretixcontrol/orders/export.html:42
#: pretix/control/templates/pretixcontrol/organizers/export.html:42
@@ -25015,8 +25124,10 @@ msgstr "Recomendado para nuevos usuarios"
#: pretix/control/templates/pretixcontrol/orders/export.html:120
#: pretix/control/templates/pretixcontrol/organizers/export.html:120
#, fuzzy
#| msgid "There are no add-ons available for this product."
msgid "There are no exporters available for you."
msgstr "No hay exportadores disponibles para usted."
msgstr "No hay complementos disponibles para este producto."
#: pretix/control/templates/pretixcontrol/orders/export_delete.html:4
#: pretix/control/templates/pretixcontrol/orders/export_delete.html:6
@@ -26967,7 +27078,7 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/pdf/index.html:274
#, python-format
msgid "max. %(size)s, smaller is better"
msgstr "máx. %(size)s; cuanto más pequeño, mejor"
msgstr ""
#: pretix/control/templates/pretixcontrol/pdf/index.html:282
msgid "Download current background"
@@ -29750,12 +29861,16 @@ msgstr ""
"los errores."
#: pretix/control/views/orders.py:2798 pretix/control/views/organizer.py:2131
#, fuzzy
#| msgid ""
#| "Your user account does not have sufficient permission to run this report, "
#| "therefore you cannot schedule it."
msgid ""
"Your user account does not have sufficient permission to run this report, "
"therefore you cannot change it."
msgstr ""
"Su cuenta de usuario no tiene los permisos suficientes para ejecutar este "
"informe, por lo que no puede modificarlo."
"Su cuenta de usuario no tiene permisos suficientes para ejecutar este "
"informe, por lo que no puede programarlo."
#: pretix/control/views/orders.py:2807 pretix/control/views/organizer.py:2140
msgid ""
@@ -33830,7 +33945,7 @@ msgstr "iDEAL via Stripe"
#: pretix/plugins/stripe/payment.py:1572
msgid "iDEAL | Wero"
msgstr "iDEAL | Wero"
msgstr ""
#: pretix/plugins/stripe/payment.py:1575
msgid ""
@@ -36815,9 +36930,6 @@ msgid ""
"the shop that affect quotas, such as the validity period of carts and "
"vouchers."
msgstr ""
"Tenga en cuenta que la hora modificada no se tiene en cuenta en aquellos "
"aspectos de la tienda que afectan a los límites, como el periodo de validez "
"de los carritos y los vales."
#: pretix/presale/templates/pretixpresale/event/timemachine.html:31
msgid "Enable time machine"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-03-17 14:06+0000\n"
"PO-Revision-Date: 2026-03-18 12:23+0000\n"
"PO-Revision-Date: 2026-01-27 14:51+0000\n"
"Last-Translator: CVZ-es <damien.bremont@casadevelazquez.org>\n"
"Language-Team: Spanish <https://translate.pretix.eu/projects/pretix/pretix-"
"js/es/>\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.16.2\n"
"X-Generator: Weblate 5.15.2\n"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
@@ -60,7 +60,7 @@ msgstr "PayPal Paga Después"
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:41
msgid "iDEAL | Wero"
msgstr "iDEAL | Wero"
msgstr ""
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:42
msgid "SEPA Direct Debit"

View File

@@ -4,16 +4,16 @@ msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-03-17 14:05+0000\n"
"PO-Revision-Date: 2026-03-18 12:23+0000\n"
"PO-Revision-Date: 2026-03-03 20:00+0000\n"
"Last-Translator: CVZ-es <damien.bremont@casadevelazquez.org>\n"
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix/"
"fr/>\n"
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix/fr/"
">\n"
"Language: fr\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 5.16.2\n"
"X-Generator: Weblate 5.16.1\n"
#: pretix/_base_settings.py:87
msgid "English"
@@ -409,8 +409,10 @@ msgstr ""
#: pretix/api/serializers/organizer.py:482
#: pretix/control/views/organizer.py:1039
#, fuzzy
#| msgid "Account information"
msgid "Account invitation"
msgstr "Invitation à créer un compte"
msgstr "Informations sur le compte"
#: pretix/api/serializers/organizer.py:503
#: pretix/control/views/organizer.py:1138
@@ -3969,8 +3971,12 @@ msgid "Peppol participant ID"
msgstr "Identifiant participant Peppol"
#: pretix/base/invoicing/peppol.py:211
#, fuzzy
#| msgid "The Peppol participant ID is not registered on the Peppol network."
msgid "The Peppol participant ID does not match your VAT ID."
msgstr "L'identifiant Peppol ne correspond pas à votre numéro de TVA."
msgstr ""
"L'identifiant Peppol du participant n'est pas enregistré sur le réseau "
"Peppol."
#: pretix/base/invoicing/peppol.py:214
msgctxt "peppol_invoice"
@@ -6923,8 +6929,10 @@ msgstr ""
"les utilisateurs."
#: pretix/base/models/organizer.py:378
#, fuzzy
#| msgid "Event permissions"
msgid "All event permissions"
msgstr "Toutes les autorisations relatives aux événements"
msgstr "Autorisations de l'événement"
#: pretix/base/models/organizer.py:379
#: pretix/control/templates/pretixcontrol/organizers/team_edit.html:34
@@ -6932,8 +6940,10 @@ msgid "Event permissions"
msgstr "Autorisations de l'événement"
#: pretix/base/models/organizer.py:380
#, fuzzy
#| msgid "Organizer permissions"
msgid "All organizer permissions"
msgstr "Toutes les autorisations d'organisateur"
msgstr "Autorisations de l'organisateur"
#: pretix/base/models/organizer.py:381
#: pretix/control/templates/pretixcontrol/organizers/team_edit.html:25
@@ -8457,32 +8467,36 @@ msgstr ""
#: pretix/base/permissions.py:314 pretix/base/permissions.py:331
msgctxt "permission_level"
msgid "View"
msgstr "Voir"
msgstr ""
#: pretix/base/permissions.py:164 pretix/base/permissions.py:169
#: pretix/base/permissions.py:174 pretix/base/permissions.py:179
#: pretix/base/permissions.py:286 pretix/base/permissions.py:315
#, fuzzy
#| msgid "Save and check"
msgctxt "permission_level"
msgid "View and change"
msgstr "Afficher et modifier"
msgstr "Enregistrer et vérifier"
#: pretix/base/permissions.py:168
#, fuzzy
#| msgid "API tokens"
msgid "API only"
msgstr "API uniquement"
msgstr "Tokens API"
#: pretix/base/permissions.py:173
msgid ""
"Menu item will only show up if the user has permission for general settings."
msgstr ""
"Cet élément de menu n'apparaîtra que si l'utilisateur dispose d'une "
"autorisation pour les paramètres généraux."
#: pretix/base/permissions.py:177 pretix/base/permissions.py:231
#: pretix/base/permissions.py:285 pretix/base/permissions.py:313
#: pretix/base/permissions.py:330
#, fuzzy
#| msgid "Revoke access"
msgctxt "permission_level"
msgid "No access"
msgstr "Accès impossible"
msgstr "Révoquer l'accès"
#: pretix/base/permissions.py:188
#: pretix/control/templates/pretixcontrol/event/settings.html:7
@@ -8496,8 +8510,6 @@ msgid ""
"This includes access to all settings not listed explicitly below, including "
"plugin settings."
msgstr ""
"Cela inclut l'accès à tous les paramètres qui ne sont pas explicitement "
"mentionnés ci-dessous, y compris les paramètres des extensions."
#: pretix/base/permissions.py:197
#: pretix/control/templates/pretixcontrol/event/payment.html:6
@@ -8511,81 +8523,100 @@ msgid "Tax settings"
msgstr "Paramètres relatifs aux taxes"
#: pretix/base/permissions.py:209
#, fuzzy
#| msgid "Invoice settings"
msgid "Invoicing settings"
msgstr "Paramètres de facturation"
#: pretix/base/permissions.py:215
#, fuzzy
#| msgctxt "subevent"
#| msgid "Event series date added"
msgid "Event series dates"
msgstr "Dates de la série d'événements"
msgstr "Une nouvelle date a été ajouté"
#: pretix/base/permissions.py:221
#, fuzzy
#| msgid "Product name and variation"
msgid "Products, quotas and questions"
msgstr "Produits, quotas et questions"
msgstr "Dénomination et variantes du produit"
#: pretix/base/permissions.py:224
msgid "Also includes related objects like categories or discounts."
msgstr ""
"Cela inclut également les éléments associés, tels que les catégories ou les "
"remises."
#: pretix/base/permissions.py:232
#, fuzzy
#| msgid "All check-ins"
msgctxt "permission_level"
msgid "Only check-in"
msgstr "Enregistrement uniquement"
msgstr "Tous les enregistrements"
#: pretix/base/permissions.py:233
#, fuzzy
#| msgid "View full log"
msgctxt "permission_level"
msgid "View all"
msgstr "Tout afficher"
msgstr "Voir le journal complet"
#: pretix/base/permissions.py:234
#, fuzzy
#| msgid "Valid check-in"
msgctxt "permission_level"
msgid "View all and check-in"
msgstr "Tout afficher et les check-in"
msgstr "Enregistrement valide"
#: pretix/base/permissions.py:235
#, fuzzy
#| msgid "View all upcoming events"
msgctxt "permission_level"
msgid "View all and change"
msgstr "Tout afficher et modifier"
msgstr "Voir tous les événements à venir"
#: pretix/base/permissions.py:236
msgid "Includes the ability to cancel and refund individual orders."
msgstr "Permet d'annuler et de rembourser des commandes individuelles."
msgstr ""
#: pretix/base/permissions.py:238
#, fuzzy
#| msgid "An entry has been added to the waiting list."
msgid "Also includes related objects like the waiting list."
msgstr "Comprend également les éléments connexes, tels que la liste d'attente."
msgstr "Une entrée a été ajoutée à la liste d'attente."
#: pretix/base/permissions.py:248
#, fuzzy
#| msgid "Generate cancellation"
msgid "Full event or date cancellation"
msgstr "Annulation complète de l'événement ou de la date"
msgstr "Générer annulation"
#: pretix/base/permissions.py:252
#, fuzzy
#| msgid "Sale not allowed"
msgctxt "permission_level"
msgid "Not allowed"
msgstr "Interdit"
msgstr "Vente non autorisée"
#: pretix/base/permissions.py:253
#, fuzzy
#| msgid "Allowed titles"
msgctxt "permission_level"
msgid "Allowed"
msgstr "Autorisé"
msgstr "Titres autorisés"
#: pretix/base/permissions.py:268
msgctxt "permission_level"
msgid "Access existing events"
msgstr "Accéder aux événements existants"
msgstr ""
#: pretix/base/permissions.py:269
msgctxt "permission_level"
msgid "Access existing and create new events"
msgstr "Accéder aux événements existants et en créer de nouveaux"
msgstr ""
#: pretix/base/permissions.py:271
msgid ""
"The level of access to events is determined in detail by the settings below."
msgstr ""
"Le niveau d'accès aux événements est défini en détail par les paramètres ci-"
"dessous."
#: pretix/base/permissions.py:275 pretix/control/navigation.py:143
#: pretix/control/navigation.py:462 pretix/control/navigation.py:512
@@ -8604,17 +8635,12 @@ msgid ""
"This includes access to all organizer-level functionality not listed "
"explicitly below, including plugin settings."
msgstr ""
"Cela inclut l'accès à toutes les fonctionnalités de niveau organisateur qui "
"ne sont pas explicitement mentionnées ci-dessous, y compris les paramètres "
"des extensions."
#: pretix/base/permissions.py:287
msgid ""
"Includes the ability to give someone (including oneself) additional "
"permissions."
msgstr ""
"Permet d'accorder des autorisations supplémentaires à une autre personne (y "
"compris à soi-même)."
#: pretix/base/permissions.py:298 pretix/control/navigation.py:608
#: pretix/control/templates/pretixcontrol/organizers/customers.html:6
@@ -8632,12 +8658,12 @@ msgid ""
"Includes the ability to give access to events and data oneself does not have "
"access to."
msgstr ""
"Permet de donner accès à des événements et à des données auxquels on n'a pas "
"soi-même accès."
#: pretix/base/permissions.py:321
#, fuzzy
#| msgid "Seating plan"
msgid "Seating plans"
msgstr "Plans de salle"
msgstr "Plan de salle"
#: pretix/base/permissions.py:327 pretix/control/navigation.py:712
#: pretix/control/templates/pretixcontrol/organizers/outgoing_mails.html:8
@@ -9288,12 +9314,14 @@ msgid "Czech National Bank"
msgstr "Banque nationale tchèque"
#: pretix/base/services/export.py:94 pretix/base/services/export.py:154
#, fuzzy
#| msgid "You do not have sufficient permission to perform this export."
msgid ""
"Export not found or you do not have sufficient permission to perform this "
"export."
msgstr ""
"L'exportation est introuvable ou vous ne disposez pas des autorisations "
"nécessaires pour effectuer cette exportation."
"Vous ne disposez pas des autorisations suffisantes pour effectuer cette "
"exportation."
#: pretix/base/services/export.py:107 pretix/base/services/export.py:179
#: pretix/base/services/export.py:357
@@ -13832,7 +13860,25 @@ msgid "Contact"
msgstr "Contact"
#: pretix/base/templates/pretixbase/email/shred_completed.txt:2
#, python-format
#, fuzzy, python-format
#| msgid ""
#| "Hello,\n"
#| "\n"
#| "we hereby confirm that the following data shredding job has been "
#| "completed:\n"
#| "\n"
#| "Organizer: %(organizer)s\n"
#| "\n"
#| "Event: %(event)s\n"
#| "\n"
#| "Data selection: %(shredders)s\n"
#| "\n"
#| "Start time: %(start_time)s (new data added after this time might not have "
#| "been deleted)\n"
#| "\n"
#| "Best regards,\n"
#| "\n"
#| "Your pretix team\n"
msgid ""
"Hello,\n"
"\n"
@@ -13867,7 +13913,7 @@ msgstr ""
"\n"
"Sinceres salutations\n"
"\n"
"Votre équipe pretix%(instance)s\n"
"Votre équipe pretix\n"
#: pretix/base/templates/pretixbase/forms/widgets/checkbox_sales_channel_option.html:16
msgid ""
@@ -14436,12 +14482,16 @@ msgid "This is an event series"
msgstr "C'est une série d'événements"
#: pretix/control/forms/event.py:135 pretix/control/forms/event.py:363
#, fuzzy
#| msgid ""
#| "You do not have sufficient permission to enable plugins that need to be "
#| "enabled for the entire organizer account."
msgid ""
"You do not have a sufficient level of access on the event you selected to "
"copy it to the desired organizer."
msgstr ""
"Vous ne disposez pas des droits d'accès suffisants pour copier l'événement "
"que vous avez sélectionné vers l'organisateur souhaité."
"Vous ne disposez pas des autorisations suffisantes pour activer les plugins "
"qui doivent être activés pour l'ensemble du compte organisateur."
#: pretix/control/forms/event.py:143
msgid ""
@@ -14524,9 +14574,6 @@ msgid ""
"You cannot choose a team that would give you more access than you have on "
"the event you are copying."
msgstr ""
"Vous ne pouvez pas choisir une équipe qui vous donnerait plus de droits "
"d'accès que ceux dont vous disposez actuellement pour l'événement que vous "
"copiez."
#: pretix/control/forms/event.py:344
msgid "Copy configuration from"
@@ -14542,9 +14589,6 @@ msgid ""
"You cannot choose an event on which you have less access than the team you "
"selected in the previous step."
msgstr ""
"Vous ne pouvez pas sélectionner un événement pour lequel vous disposez de "
"moins de droits d'accès que l'équipe que vous avez choisie à l'étape "
"précédente."
#: pretix/control/forms/event.py:387 pretix/control/forms/item.py:1304
#: pretix/control/forms/subevents.py:411
@@ -15499,7 +15543,7 @@ msgstr "Recherche d'un participant…"
#: pretix/control/forms/filter.py:2039
#: pretix/plugins/checkinlists/exporters.py:106
msgid "Check-in status"
msgstr "État de l'enregistrement"
msgstr "Statut d'enregistrement"
#: pretix/control/forms/filter.py:2041
#: pretix/plugins/checkinlists/exporters.py:108
@@ -15512,7 +15556,7 @@ msgstr "Tous les participants"
#: pretix/plugins/checkinlists/exporters.py:109
#: pretix/plugins/checkinlists/exporters.py:501
msgid "Checked in"
msgstr "Enregistrement effectué"
msgstr "Enregistré"
#: pretix/control/forms/filter.py:2043
#: pretix/plugins/checkinlists/exporters.py:110
@@ -15530,7 +15574,7 @@ msgstr "Enregistré mais laissé"
#: pretix/control/templates/pretixcontrol/checkin/index.html:178
#: pretix/plugins/checkinlists/exporters.py:112
msgid "Not checked in"
msgstr "Enregistrement non effectué"
msgstr "Non enregistré"
#: pretix/control/forms/filter.py:2064
msgctxt "subevent"
@@ -16925,7 +16969,7 @@ msgstr "Vous ne pouvez définir qu'un seul domaine d'organisateur."
#: pretix/control/forms/organizer.py:322
msgid "Provided by a plugin"
msgstr "Fourni par un plugin"
msgstr ""
#: pretix/control/forms/organizer.py:438
msgid ""
@@ -17812,8 +17856,10 @@ msgid "The order has been denied (comment: \"{comment}\")."
msgstr "Lordonnance a été refusée (commentaire : \"{comment}\")."
#: pretix/control/logdisplay.py:521
#, fuzzy
#| msgid "The order has been overpaid."
msgid "The customer VAT ID has been verified."
msgstr "Le numéro d'identification TVA du client a été vérifié."
msgstr "La commande a été payée en trop."
#: pretix/control/logdisplay.py:522
#, python-brace-format
@@ -18114,9 +18160,10 @@ msgid "{user} has been invited to the team."
msgstr "{user} a été invité dans l'équipe."
#: pretix/control/logdisplay.py:644
#, python-brace-format
#, fuzzy, python-brace-format
#| msgid "Invite for {user} has been resent."
msgid "Invite for {user} has been deleted."
msgstr "L'invitation destinée à {user} a été supprimée."
msgstr "L'invitation pour {user} a été renvoyée."
#: pretix/control/logdisplay.py:645
#, python-brace-format
@@ -20437,7 +20484,21 @@ msgid "Add property"
msgstr "Ajouter une propriété"
#: pretix/control/templates/pretixcontrol/email/confirmation_code.txt:1
#, python-format
#, fuzzy, python-format
#| msgid ""
#| "Hello,\n"
#| "\n"
#| "%(reason)s\n"
#| "\n"
#| " %(code)s\n"
#| "\n"
#| "Please do never give this code to another person. Our support team will "
#| "never ask for this code.\n"
#| "\n"
#| "If this code was not requested by you, please contact us immediately.\n"
#| "\n"
#| "Best regards,\n"
#| "Your pretix team\n"
msgid ""
"Hello,\n"
"\n"
@@ -20466,7 +20527,7 @@ msgstr ""
"immédiatement.\n"
"\n"
"Cordialement,\n"
"Votre équipe pretix%(instance)s\n"
"Léquipe pretix\n"
#: pretix/control/templates/pretixcontrol/email/email_setup.txt:1
#, python-format
@@ -20504,7 +20565,17 @@ msgstr ""
"Votre équipe %(instance)s\n"
#: pretix/control/templates/pretixcontrol/email/forgot.txt:1
#, python-format
#, fuzzy, python-format
#| msgid ""
#| "Hello,\n"
#| "\n"
#| "you requested a new password. Please go to the following page to reset "
#| "your password:\n"
#| "\n"
#| "%(url)s\n"
#| "\n"
#| "Best regards, \n"
#| "Your pretix team\n"
msgid ""
"Hello,\n"
"\n"
@@ -20524,10 +20595,27 @@ msgstr ""
"%(url)s\n"
"\n"
"Sincères salutations, \n"
"Votre équipe pretix%(instance)s\n"
"Votre équipe Pretix\n"
#: pretix/control/templates/pretixcontrol/email/invitation.txt:1
#, python-format
#, fuzzy, python-format
#| msgid ""
#| "Hello,\n"
#| "\n"
#| "you have been invited to a team on pretix, a platform to perform event\n"
#| "ticket sales.\n"
#| "\n"
#| "Organizer: %(organizer)s\n"
#| "Team: %(team)s\n"
#| "\n"
#| "If you want to join that team, just click on the following link:\n"
#| "%(url)s\n"
#| "\n"
#| "If you do not want to join, you can safely ignore or delete this email.\n"
#| "\n"
#| "Best regards, \n"
#| "\n"
#| "Your pretix team\n"
msgid ""
"Hello,\n"
"\n"
@@ -20549,8 +20637,8 @@ msgid ""
msgstr ""
"Bonjour,\n"
"\n"
"vous avez été invité à faire partie de l'équipe pretix%(instance)s, une "
"plateforme pour réaliser un événement\n"
"vous avez été invité à faire partie d'une équipe sur pretix, une plateforme "
"pour réaliser un événement\n"
"de vente de billets.\n"
"\n"
"Organisateur: %(organizer)s\n"
@@ -20564,7 +20652,7 @@ msgstr ""
"\n"
"Sincères salutations, \n"
"\n"
"Votre équipe pretix%(instance)s\n"
"Votre équipe Pretix\n"
#: pretix/control/templates/pretixcontrol/email/login_notice.txt:1
#, python-format
@@ -20602,7 +20690,24 @@ msgstr ""
"Votre équipe %(instance)s\n"
#: pretix/control/templates/pretixcontrol/email/security_notice.txt:1
#, python-format
#, fuzzy, python-format
#| msgid ""
#| "Hello,\n"
#| "\n"
#| "this is to inform you that the account information of your pretix account "
#| "has been\n"
#| "changed. In particular, the following changes have been performed:\n"
#| "\n"
#| "%(messages)s\n"
#| "\n"
#| "If this change was not performed by you, please contact us immediately.\n"
#| "\n"
#| "You can review and change your account settings here:\n"
#| "\n"
#| "%(url)s\n"
#| "\n"
#| "Best regards, \n"
#| "Your pretix team\n"
msgid ""
"Hello,\n"
"\n"
@@ -20623,8 +20728,8 @@ msgid ""
msgstr ""
"Bonjour,\n"
"\n"
"Ceci est pour vous informer que les informations de votre compte %(instance)"
"s ont été\n"
"Ceci est pour vous informer que les informations de votre compte pretix ont "
"été\n"
"modifié. Les modifications suivantes ont été apportées:\n"
"\n"
"%(messages)s\n"
@@ -20637,7 +20742,7 @@ msgstr ""
"%(url)s\n"
"\n"
"Sincères salutations, \n"
"Votre équipe pretix%(instance)s\n"
"Votre équipe Pretix\n"
#: pretix/control/templates/pretixcontrol/email_setup.html:8
#: pretix/control/templates/pretixcontrol/email_setup_simple.html:8
@@ -20905,8 +21010,10 @@ msgstr ""
"tous les billets, vous pouvez le faire via cette option."
#: pretix/control/templates/pretixcontrol/event/dangerzone.html:51
#, fuzzy
#| msgid "Permissions"
msgid "No permission"
msgstr "Pas d'autorisation"
msgstr "Autorisations"
#: pretix/control/templates/pretixcontrol/event/dangerzone.html:59
#: pretix/control/templates/pretixcontrol/event/dangerzone.html:72
@@ -23487,8 +23594,10 @@ msgid "Edit question"
msgstr "Modifier la question"
#: pretix/control/templates/pretixcontrol/items/question.html:55
#, fuzzy
#| msgid "You do not have permission to view this content."
msgid "No permission to view answers."
msgstr "Vous n'êtes pas autorisé à consulter les réponses."
msgstr "Vous n'avez pas l'autorisation de consulter ce contenu."
#: pretix/control/templates/pretixcontrol/items/question.html:63
msgid "No matching answers found."
@@ -25154,8 +25263,10 @@ msgstr "Pas de prochaine exécution programmée"
#: pretix/control/templates/pretixcontrol/orders/export.html:37
#: pretix/control/templates/pretixcontrol/organizers/export.html:37
#, fuzzy
#| msgid "Exporter not found"
msgid "Exporter not found or no permission"
msgstr "Exportateur introuvable ou autorisation refusée"
msgstr "Exportateur introuvable"
#: pretix/control/templates/pretixcontrol/orders/export.html:42
#: pretix/control/templates/pretixcontrol/organizers/export.html:42
@@ -25198,8 +25309,10 @@ msgstr "Recommandé pour les nouveaux utilisateurs"
#: pretix/control/templates/pretixcontrol/orders/export.html:120
#: pretix/control/templates/pretixcontrol/organizers/export.html:120
#, fuzzy
#| msgid "There are no add-ons available for this product."
msgid "There are no exporters available for you."
msgstr "Aucun exportateur n'est disponible pour vous."
msgstr "Il n' y a pas d'add-ons disponibles pour ce produit."
#: pretix/control/templates/pretixcontrol/orders/export_delete.html:4
#: pretix/control/templates/pretixcontrol/orders/export_delete.html:6
@@ -27164,7 +27277,7 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/pdf/index.html:274
#, python-format
msgid "max. %(size)s, smaller is better"
msgstr "max. %(size)s, le plus petit possible"
msgstr ""
#: pretix/control/templates/pretixcontrol/pdf/index.html:282
msgid "Download current background"
@@ -29976,12 +30089,16 @@ msgstr ""
"plus de détails sur les erreurs."
#: pretix/control/views/orders.py:2798 pretix/control/views/organizer.py:2131
#, fuzzy
#| msgid ""
#| "Your user account does not have sufficient permission to run this report, "
#| "therefore you cannot schedule it."
msgid ""
"Your user account does not have sufficient permission to run this report, "
"therefore you cannot change it."
msgstr ""
"Votre compte utilisateur ne dispose pas des autorisations nécessaires pour "
"exécuter ce rapport ; vous ne pouvez donc pas le modifier."
"Votre compte utilisateur ne dispose pas des autorisations suffisantes pour "
"exécuter ce rapport, vous ne pouvez donc pas le planifier."
#: pretix/control/views/orders.py:2807 pretix/control/views/organizer.py:2140
msgid ""
@@ -34095,7 +34212,7 @@ msgstr "iDEAL via Stripe"
#: pretix/plugins/stripe/payment.py:1572
msgid "iDEAL | Wero"
msgstr "iDEAL | Wero"
msgstr ""
#: pretix/plugins/stripe/payment.py:1575
msgid ""
@@ -37136,9 +37253,6 @@ msgid ""
"the shop that affect quotas, such as the validity period of carts and "
"vouchers."
msgstr ""
"Veuillez noter que la modification de l'heure n'est pas prise en compte pour "
"les aspects de la boutique qui ont une incidence sur les quotas, tels que la "
"durée de validité des paniers et des bons d'achat."
#: pretix/presale/templates/pretixpresale/event/timemachine.html:31
msgid "Enable time machine"

View File

@@ -7,7 +7,7 @@ msgstr ""
"Project-Id-Version: French\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-03-17 14:06+0000\n"
"PO-Revision-Date: 2026-03-18 12:23+0000\n"
"PO-Revision-Date: 2026-01-27 14:51+0000\n"
"Last-Translator: CVZ-es <damien.bremont@casadevelazquez.org>\n"
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix-js/"
"fr/>\n"
@@ -16,7 +16,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 5.16.2\n"
"X-Generator: Weblate 5.15.2\n"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
@@ -59,7 +59,7 @@ msgstr "PayPal Pay Later"
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:41
msgid "iDEAL | Wero"
msgstr "iDEAL | Wero"
msgstr ""
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:42
msgid "SEPA Direct Debit"

View File

@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-03-17 14:05+0000\n"
"PO-Revision-Date: 2026-03-27 09:03+0000\n"
"Last-Translator: Ivano Voghera <ivano.voghera@gmail.com>\n"
"PO-Revision-Date: 2026-02-10 16:49+0000\n"
"Last-Translator: Michele Pagnozzi <michele.pagnozzi@gmail.com>\n"
"Language-Team: Italian <https://translate.pretix.eu/projects/pretix/pretix/"
"it/>\n"
"Language: it\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.16.2\n"
"X-Generator: Weblate 5.15.2\n"
#: pretix/_base_settings.py:87
msgid "English"
@@ -412,8 +412,9 @@ msgstr ""
#: pretix/api/serializers/organizer.py:482
#: pretix/control/views/organizer.py:1039
#, fuzzy
msgid "Account invitation"
msgstr "Invito account"
msgstr "Informazioni account modificate"
#: pretix/api/serializers/organizer.py:503
#: pretix/control/views/organizer.py:1138
@@ -655,16 +656,22 @@ msgid "Customer account anonymized"
msgstr "Account del cliente anonimizzato"
#: pretix/api/webhooks.py:470
#, fuzzy
#| msgid "Gift card code"
msgid "Gift card added"
msgstr "Carta regalo aggiunta"
msgstr "Codice carta regalo"
#: pretix/api/webhooks.py:474
#, fuzzy
#| msgid "Gift card code"
msgid "Gift card modified"
msgstr "Carta regalo modificata"
msgstr "Codice carta regalo"
#: pretix/api/webhooks.py:478
#, fuzzy
#| msgid "Gift card transactions"
msgid "Gift card used in transaction"
msgstr "Carta regalo usata nella transazione"
msgstr "Transazioni con carta regalo"
#: pretix/base/addressvalidation.py:100 pretix/base/addressvalidation.py:103
#: pretix/base/addressvalidation.py:108 pretix/base/forms/questions.py:1054
@@ -1230,7 +1237,7 @@ msgstr "Caricamento dei file di risposta alle domande"
#: pretix/plugins/reports/exporters.py:666
msgctxt "export_category"
msgid "Order data"
msgstr "Data dell'ordine"
msgstr "Dati dell'ordine"
#: pretix/base/exporters/answers.py:56
msgid ""
@@ -2317,10 +2324,6 @@ msgid ""
"contain at least one position of this product. The order totals etc. still "
"include all products contained in the order."
msgstr ""
"Se non ne viene selezionato nessuno, saranno inclusi tutti i prodotti. Gli "
"ordini vengono inclusi se contengono almeno un articolo di questo prodotto. "
"I totali dell'ordine ecc. includeranno comunque tutti i prodotti contenuti "
"nell'ordine."
#: pretix/base/exporters/orderlist.py:283
#: pretix/base/exporters/orderlist.py:479
@@ -2587,12 +2590,16 @@ msgid "Voucher"
msgstr "Voucher"
#: pretix/base/exporters/orderlist.py:655
#, fuzzy
#| msgid "Voucher deleted"
msgid "Voucher budget usage"
msgstr "Utilizzo budget del buono"
msgstr "Buono eliminato"
#: pretix/base/exporters/orderlist.py:656
#, fuzzy
#| msgid "Voucher"
msgid "Voucher tag"
msgstr "Voucher tag"
msgstr "Voucher"
#: pretix/base/exporters/orderlist.py:657
msgid "Pseudonymization ID"
@@ -3411,34 +3418,39 @@ msgid "Street and Number"
msgstr "Indirizzo e numero civico"
#: pretix/base/forms/questions.py:893
#, python-brace-format
#, fuzzy, python-brace-format
#| msgid "Please enter a shorter name."
msgid "Please enter a date between {min} and {max}."
msgstr "Per favore inserisci una data inclusa tra {min} e {max}."
msgstr "Per favore inserisci un nome più breve."
#: pretix/base/forms/questions.py:899
#, python-brace-format
#, fuzzy, python-brace-format
#| msgid "Please enter a valid sales channel."
msgid "Please enter a date no earlier than {min}."
msgstr "Per favore inserisci una data successiva a {min}."
msgstr "Inserire un canale di vendita valido."
#: pretix/base/forms/questions.py:904
#, python-brace-format
#, fuzzy, python-brace-format
#| msgid "Please enter a shorter name."
msgid "Please enter a date no later than {max}."
msgstr "Per favore inserisci una data precedente a {max}."
msgstr "Per favore inserisci un nome più breve."
#: pretix/base/forms/questions.py:942
#, python-brace-format
msgid "Please enter a date and time between {min} and {max}."
msgstr "Per favore inserisci data e orario compresi tra {min} e {max}."
msgstr ""
#: pretix/base/forms/questions.py:948
#, python-brace-format
#, fuzzy, python-brace-format
#| msgid "Please enter a valid sales channel."
msgid "Please enter a date and time no earlier than {min}."
msgstr "Per favore inserisci data e orario successivi a {min}."
msgstr "Inserire un canale di vendita valido."
#: pretix/base/forms/questions.py:953
#, python-brace-format
#, fuzzy, python-brace-format
#| msgid "Please enter the correct result."
msgid "Please enter a date and time no later than {max}."
msgstr "Per favore inserisci data e orario precedenti a {max}."
msgstr "Per favore inserisci il risultato corretto."
#: pretix/base/forms/questions.py:1172
msgid ""
@@ -3945,7 +3957,7 @@ msgstr ""
#: pretix/base/invoicing/peppol.py:166
msgid "The Peppol participant ID is not registered on the Peppol network."
msgstr "L'ID partecipante Peppol non è registrato nella rete Peppol."
msgstr ""
#: pretix/base/invoicing/peppol.py:192
msgid "Peppol participant ID"
@@ -3953,7 +3965,7 @@ msgstr "ID partecipante Peppol"
#: pretix/base/invoicing/peppol.py:211
msgid "The Peppol participant ID does not match your VAT ID."
msgstr "L'ID partecipante Peppol non corrisponde alla tua Partita IVA."
msgstr ""
#: pretix/base/invoicing/peppol.py:214
msgctxt "peppol_invoice"
@@ -4324,7 +4336,7 @@ msgstr "Permetti di superare la quota"
#: pretix/control/templates/pretixcontrol/vouchers/detail.html:70
#: pretix/control/views/vouchers.py:121
msgid "Price effect"
msgstr "Variazione di prezzo"
msgstr ""
#: pretix/base/modelimport_vouchers.py:150
#, fuzzy, python-brace-format
@@ -5327,6 +5339,7 @@ msgstr "trasmesso"
#: pretix/control/templates/pretixcontrol/organizers/outgoing_mail.html:44
#: pretix/control/templates/pretixcontrol/organizers/outgoing_mails.html:145
#: pretix/plugins/sendmail/models.py:51
#, fuzzy
msgid "failed"
msgstr "fallito"
@@ -5388,7 +5401,7 @@ msgstr "Categoria normale"
#: pretix/base/models/items.py:115 pretix/control/forms/item.py:111
msgid "Normal + cross-selling category"
msgstr "Normale + Categoria cross-selling"
msgstr "Categoria normale + cross-selling"
#: pretix/base/models/items.py:116 pretix/control/forms/item.py:106
msgid "Cross-selling category"
@@ -6436,13 +6449,13 @@ msgstr "Fine"
#: pretix/control/templates/pretixcontrol/organizers/outgoing_mail.html:38
#: pretix/control/templates/pretixcontrol/organizers/outgoing_mails.html:139
msgid "queued"
msgstr "in coda"
msgstr ""
#: pretix/base/models/mail.py:53
#: pretix/control/templates/pretixcontrol/organizers/outgoing_mail.html:40
#: pretix/control/templates/pretixcontrol/organizers/outgoing_mails.html:141
msgid "being sent"
msgstr "invio in corso"
msgstr ""
#: pretix/base/models/mail.py:54
#, fuzzy
@@ -6454,25 +6467,25 @@ msgstr "Voce in lista d'attesa"
#: pretix/control/templates/pretixcontrol/organizers/outgoing_mail.html:48
#: pretix/control/templates/pretixcontrol/organizers/outgoing_mails.html:149
msgid "withheld"
msgstr "trattenuto"
msgstr ""
#: pretix/base/models/mail.py:57
#: pretix/control/templates/pretixcontrol/organizers/outgoing_mail.html:50
#: pretix/control/templates/pretixcontrol/organizers/outgoing_mails.html:151
msgid "aborted"
msgstr "annullato"
msgstr ""
#: pretix/base/models/mail.py:58
#: pretix/control/templates/pretixcontrol/organizers/outgoing_mail.html:52
#: pretix/control/templates/pretixcontrol/organizers/outgoing_mails.html:153
msgid "sent"
msgstr "inviato"
msgstr ""
#: pretix/base/models/mail.py:59
#: pretix/control/templates/pretixcontrol/organizers/outgoing_mail.html:46
#: pretix/control/templates/pretixcontrol/organizers/outgoing_mails.html:147
msgid "bounced"
msgstr "rimbalzato"
msgstr ""
#: pretix/base/models/memberships.py:44
#: pretix/presale/templates/pretixpresale/organizers/customer_memberships.html:28
@@ -6676,7 +6689,7 @@ msgstr "confermato"
#: pretix/base/models/orders.py:1734
msgctxt "payment_state"
msgid "canceled"
msgstr "cancellato"
msgstr "annullato"
#: pretix/base/models/orders.py:1735
msgctxt "payment_state"
@@ -6907,7 +6920,7 @@ msgstr "Ammissione all'evento"
#: pretix/base/models/organizer.py:379
#: pretix/control/templates/pretixcontrol/organizers/team_edit.html:34
msgid "Event permissions"
msgstr "Permessi dell'evento"
msgstr ""
#: pretix/base/models/organizer.py:380
#, fuzzy
@@ -6917,7 +6930,7 @@ msgstr "Impostazioni account"
#: pretix/base/models/organizer.py:381
#: pretix/control/templates/pretixcontrol/organizers/team_edit.html:25
msgid "Organizer permissions"
msgstr "Permessi dell'organizzatore"
msgstr ""
#: pretix/base/models/organizer.py:401
#, python-format
@@ -8422,7 +8435,7 @@ msgstr "Il tuo file di layout non è un layout valido. Messaggio di errore: {}"
#: pretix/base/permissions.py:314 pretix/base/permissions.py:331
msgctxt "permission_level"
msgid "View"
msgstr "Vista"
msgstr ""
#: pretix/base/permissions.py:164 pretix/base/permissions.py:169
#: pretix/base/permissions.py:174 pretix/base/permissions.py:179
@@ -8434,14 +8447,12 @@ msgstr "Salva modifiche"
#: pretix/base/permissions.py:168
msgid "API only"
msgstr "Solo API"
msgstr ""
#: pretix/base/permissions.py:173
msgid ""
"Menu item will only show up if the user has permission for general settings."
msgstr ""
"La voce di menu apparirà solo se l'utente ha il permesso per le impostazioni "
"generali."
#: pretix/base/permissions.py:177 pretix/base/permissions.py:231
#: pretix/base/permissions.py:285 pretix/base/permissions.py:313
@@ -8463,14 +8474,12 @@ msgid ""
"This includes access to all settings not listed explicitly below, including "
"plugin settings."
msgstr ""
"Include l'accesso a tutte le impostazioni non elencate esplicitamente di "
"seguito, comprese le impostazioni dei plugin."
#: pretix/base/permissions.py:197
#: pretix/control/templates/pretixcontrol/event/payment.html:6
#: pretix/control/templates/pretixcontrol/event/payment_provider.html:5
msgid "Payment settings"
msgstr "Impostazioni di pagamento"
msgstr ""
#: pretix/base/permissions.py:203
#: pretix/control/templates/pretixcontrol/event/tax.html:120
@@ -8479,8 +8488,10 @@ msgid "Tax settings"
msgstr "Impostazioni account"
#: pretix/base/permissions.py:209
#, fuzzy
#| msgid "Login settings"
msgid "Invoicing settings"
msgstr "Impostazioni Fattura"
msgstr "Impostazioni login"
#: pretix/base/permissions.py:215
#, fuzzy
@@ -8497,7 +8508,7 @@ msgstr "Nome prodotto e variante"
#: pretix/base/permissions.py:224
msgid "Also includes related objects like categories or discounts."
msgstr "Include anche gli oggetti correlati come categorie o sconti."
msgstr ""
#: pretix/base/permissions.py:232
#, fuzzy
@@ -8508,7 +8519,7 @@ msgstr "Filtra per stato"
#: pretix/base/permissions.py:233
msgctxt "permission_level"
msgid "View all"
msgstr "Vedi tutto"
msgstr ""
#: pretix/base/permissions.py:234
#, fuzzy
@@ -8519,15 +8530,15 @@ msgstr "Filtra per stato"
#: pretix/base/permissions.py:235
msgctxt "permission_level"
msgid "View all and change"
msgstr "Vedi e modifica tutto"
msgstr ""
#: pretix/base/permissions.py:236
msgid "Includes the ability to cancel and refund individual orders."
msgstr "Include il permesso di cancellare e rimborsare ordini individuali."
msgstr ""
#: pretix/base/permissions.py:238
msgid "Also includes related objects like the waiting list."
msgstr "Include anche gli oggetti correlati come la lista d'attesa."
msgstr ""
#: pretix/base/permissions.py:248
#, fuzzy
@@ -8551,19 +8562,17 @@ msgstr "Tasse"
#: pretix/base/permissions.py:268
msgctxt "permission_level"
msgid "Access existing events"
msgstr "Accedere agli eventi esistenti"
msgstr ""
#: pretix/base/permissions.py:269
msgctxt "permission_level"
msgid "Access existing and create new events"
msgstr "Accedere agli eventi esistenti e crearne di nuovi"
msgstr ""
#: pretix/base/permissions.py:271
msgid ""
"The level of access to events is determined in detail by the settings below."
msgstr ""
"Il livello di accesso agli eventi è determinato nel dettaglio dalle "
"impostazioni sottostanti."
#: pretix/base/permissions.py:275 pretix/control/navigation.py:143
#: pretix/control/navigation.py:462 pretix/control/navigation.py:512
@@ -8582,8 +8591,6 @@ msgid ""
"This includes access to all organizer-level functionality not listed "
"explicitly below, including plugin settings."
msgstr ""
"Include l'accesso a tutte le funzionalità a livello di organizzatore non "
"elencate esplicitamente di seguito, comprese le impostazioni dei plugin."
#: pretix/base/permissions.py:287
msgid ""
@@ -8618,7 +8625,7 @@ msgstr "Piano dei posti a sedere"
#: pretix/base/permissions.py:327 pretix/control/navigation.py:712
#: pretix/control/templates/pretixcontrol/organizers/outgoing_mails.html:8
msgid "Outgoing emails"
msgstr "Email in uscita"
msgstr ""
#: pretix/base/plugins.py:138
#: pretix/control/templates/pretixcontrol/event/quick_setup.html:132
@@ -10090,11 +10097,6 @@ msgid ""
"placing an order, the restriction only becomes active after the customer "
"account is activated."
msgstr ""
"Se abilitato, gli utenti che erano autenticati al momento dell'acquisto "
"dovranno effettuare l'accesso anche per consultare le informazioni sul "
"proprio ordine. Se un account cliente viene creato durante l'effettuazione "
"di un ordine, la restrizione diventerà attiva solo dopo l'attivazione "
"dell'account stesso."
#: pretix/base/settings.py:203
msgid "Match orders based on email address"
@@ -13188,9 +13190,6 @@ msgid ""
"logged email contents. This will also remove the association to customer "
"accounts."
msgstr ""
"Questo rimuoverà tutti gli indirizzi email dagli ordini e dai partecipanti, "
"così come il contenuto delle email registrate. Rimuoverà inoltre "
"l'associazione agli account cliente."
#: pretix/base/shredder.py:373
msgid ""
@@ -16580,19 +16579,19 @@ msgstr "Salva modifiche"
#: pretix/control/forms/rrule.py:35
msgid "year(s)"
msgstr "anno/i"
msgstr ""
#: pretix/control/forms/rrule.py:36
msgid "month(s)"
msgstr "mese/i"
msgstr "mese(i)"
#: pretix/control/forms/rrule.py:37
msgid "week(s)"
msgstr "settimana/e"
msgstr "settimana (e)"
#: pretix/control/forms/rrule.py:38
msgid "day(s)"
msgstr "giorno/i"
msgstr ""
#: pretix/control/forms/rrule.py:43
msgid "Interval"
@@ -16629,7 +16628,7 @@ msgstr ""
#: pretix/control/forms/rrule.py:111 pretix/control/forms/rrule.py:150
#: pretix/presale/templates/pretixpresale/fragment_calendar_nav.html:20
msgid "Day"
msgstr "Giorno"
msgstr ""
#: pretix/control/forms/rrule.py:113 pretix/control/forms/rrule.py:152
msgid "Weekend day"
@@ -17231,8 +17230,9 @@ msgid ""
msgstr ""
#: pretix/control/logdisplay.py:529
#, fuzzy
msgid "The customer account has been changed."
msgstr "L'account cliente è stato modificato."
msgstr "La data dell'evento è stata modificata."
#: pretix/control/logdisplay.py:530
msgid "The order locale has been changed."
@@ -18914,7 +18914,7 @@ msgstr ""
#: pretix/presale/templates/pretixpresale/postmessage.html:27
#: pretix/presale/templates/pretixpresale/waiting.html:42
msgid "If this takes longer than a few minutes, please contact us."
msgstr "Se questa operazione richiede alcuni minuti, si prega di contattarci."
msgstr ""
#: pretix/control/templates/pretixcontrol/boxoffice/payment.html:4
#: pretix/control/templates/pretixcontrol/organizers/devices.html:75
@@ -20687,6 +20687,8 @@ msgid "Payment reminder"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/mail.html:108
#, fuzzy
#| msgid "Payment confirmed"
msgid "Payment failed"
msgstr "Pagamento rifiutato"
@@ -20737,7 +20739,7 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/event/payment.html:78
msgctxt "unit"
msgid "days"
msgstr "giorni"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/payment_provider.html:13
#: pretix/control/templates/pretixcontrol/events/create_base.html:24
@@ -21614,7 +21616,7 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/fragment_quota_box_paid.html:3
#, python-format
msgid "Currently available: %(num)s"
msgstr "Attualmente disponibili: %(num)s"
msgstr ""
#: pretix/control/templates/pretixcontrol/global_license.html:8
msgid ""
@@ -21860,8 +21862,6 @@ msgid ""
"This product is currently not being sold since you configured below that it "
"should only be available in a certain timeframe."
msgstr ""
"Questo prodotto non è attualmente in vendita, poiché è stato configurato per "
"essere disponibile solo in un determinato intervallo di tempo."
#: pretix/control/templates/pretixcontrol/item/base.html:41
msgid ""
@@ -22367,8 +22367,6 @@ msgstr "Crea un nuovo organizzatore"
msgid ""
"Currently unavailable since a limited timeframe for this product has been set"
msgstr ""
"Attualmente non disponibile poiché è stato impostato un intervallo di tempo "
"limitato per questo prodotto"
#: pretix/control/templates/pretixcontrol/items/discounts.html:119
msgid "Condition:"
@@ -22865,13 +22863,12 @@ msgstr ""
#: pretix/presale/templates/pretixpresale/event/order.html:483
#: pretix/presale/templates/pretixpresale/event/order_cancel.html:7
msgid "Cancel order"
msgstr "Elimina ordine"
msgstr ""
#: pretix/control/templates/pretixcontrol/order/cancel.html:12
#: pretix/control/templates/pretixcontrol/order/deny.html:11
msgid "Do you really want to cancel this order? You cannot revert this action."
msgstr ""
"Vuoi davvero eliminare questo ordine? Questa azione non può essere annullata."
#: pretix/control/templates/pretixcontrol/order/cancel.html:16
msgid ""
@@ -22890,12 +22887,12 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/order/cancel.html:51
#: pretix/presale/templates/pretixpresale/event/order_cancel.html:220
msgid "Yes, cancel order"
msgstr "Si, elimina ordine"
msgstr ""
#: pretix/control/templates/pretixcontrol/order/cancellation_request_delete.html:4
#: pretix/control/templates/pretixcontrol/order/cancellation_request_delete.html:8
msgid "Ignore cancellation request"
msgstr "Ignora la richiesta di cancellazione"
msgstr ""
#: pretix/control/templates/pretixcontrol/order/cancellation_request_delete.html:10
msgid ""
@@ -23005,8 +23002,9 @@ msgid ""
msgstr ""
#: pretix/control/templates/pretixcontrol/order/change.html:220
#, fuzzy
msgid "Ticket block"
msgstr "Biglietto bloccato"
msgstr "Codice biglietto"
#: pretix/control/templates/pretixcontrol/order/change.html:226
msgid "Blocked due to external constraints"
@@ -25153,8 +25151,9 @@ msgid "Organizer logs"
msgstr "Organizzatore"
#: pretix/control/templates/pretixcontrol/organizers/mail.html:60
#, fuzzy
msgid "Customer account registration"
msgstr "Registrazione account cliente"
msgstr "Invia ordine"
#: pretix/control/templates/pretixcontrol/organizers/mail.html:63
#, fuzzy
@@ -26183,7 +26182,7 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/subevents/bulk.html:10
msgctxt "subevent"
msgid "Create multiple dates"
msgstr "Crea date multiple"
msgstr ""
#: pretix/control/templates/pretixcontrol/subevents/bulk.html:35
#: pretix/control/templates/pretixcontrol/subevents/bulk.html:146
@@ -26276,9 +26275,10 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/subevents/bulk_edit.html:9
#: pretix/control/templates/pretixcontrol/subevents/bulk_edit.html:12
#, fuzzy
msgctxt "subevent"
msgid "Change multiple dates"
msgstr "Cambia date multiple"
msgstr "Solo ordini pagati"
#: pretix/control/templates/pretixcontrol/subevents/bulk_edit.html:154
msgid "Item prices"
@@ -27208,12 +27208,6 @@ msgid ""
"quota is available) or you can press the big button below this text to send "
"out as many vouchers as currently possible to the persons who waited longest."
msgstr ""
"Hai configurato il sistema affinché i voucher <strong>non</strong> vengano "
"inviati automaticamente. Puoi inviarli singolarmente nell'ordine che "
"preferisci cliccando sui pulsanti accanto a ogni riga di questa tabella (se "
"la quota è sufficiente), oppure puoi premere il pulsante grande sotto questo "
"testo per inviare quanti più voucher possibile alle persone in attesa da più "
"tempo."
#: pretix/control/templates/pretixcontrol/waitinglist/index.html:79
msgid "Send as many vouchers as possible"
@@ -33299,9 +33293,10 @@ msgstr ""
#: pretix/presale/forms/renderers.py:66
#: pretix/presale/templates/pretixpresale/event/fragment_voucher_form.html:14
#, fuzzy
msgctxt "form"
msgid "required"
msgstr "obbligatorio"
msgstr "Carrello scaduto"
#: pretix/presale/ical.py:87 pretix/presale/ical.py:146
#: pretix/presale/ical.py:182
@@ -33634,7 +33629,7 @@ msgstr "Invia ordine"
#: pretix/presale/templates/pretixpresale/event/checkout_customer.html:19
msgid "Log in with a customer account"
msgstr "Accedi con un account cliente"
msgstr ""
#: pretix/presale/templates/pretixpresale/event/checkout_customer.html:26
msgid "You are currently logged in with the following credentials."
@@ -34007,7 +34002,7 @@ msgstr ""
#: pretix/presale/templates/pretixpresale/event/fragment_availability.html:25
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html:91
msgid "FULLY BOOKED"
msgstr "TUTTO PRENOTATO"
msgstr ""
#: pretix/presale/templates/pretixpresale/event/fragment_availability.html:37
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html:101
@@ -34519,7 +34514,7 @@ msgstr ""
#: pretix/presale/templates/pretixpresale/event/fragment_quota_left.html:4
#, python-format
msgid "%(num)s currently available"
msgstr "%(num)s attualmente disponibili"
msgstr ""
#: pretix/presale/templates/pretixpresale/event/fragment_subevent_calendar.html:5
#: pretix/presale/templates/pretixpresale/event/fragment_subevent_calendar_week.html:5
@@ -34756,14 +34751,18 @@ msgstr ""
"completo."
#: pretix/presale/templates/pretixpresale/event/order.html:56
#, fuzzy
#| msgid ""
#| "Please bookmark or save the link to this exact page if you want to access "
#| "your order later. We also sent you an email containing the link to the "
#| "address you specified."
msgid ""
"Please bookmark or save the link to this exact page if you want to access "
"your order later. We also sent you an email to the address you specified "
"containing the link to this page."
msgstr ""
"Salva questa pagina nei preferiti se vuoi accedere al tuo ordine in seguito. "
"Ti abbiamo anche inviato una email all'indirizzo che hai indicato contenente "
"il link a questa pagina."
"Salva questa pagina nei preferiti se vuoi accedervi in seguito. Ti abbiamo "
"anche inviato una email contenente il link a questa pagina."
#: pretix/presale/templates/pretixpresale/event/order.html:60
#, fuzzy
@@ -34878,7 +34877,7 @@ msgstr "Riferimento Interno"
#: pretix/presale/templates/pretixpresale/event/order.html:354
msgctxt "action"
msgid "Change or cancel your order"
msgstr "Modifica o cancella il tuo ordine"
msgstr ""
#: pretix/presale/templates/pretixpresale/event/order.html:356
#, fuzzy
@@ -34888,9 +34887,10 @@ msgid "Change your order"
msgstr "Cambia ordine"
#: pretix/presale/templates/pretixpresale/event/order.html:358
#, fuzzy
msgctxt "action"
msgid "Cancel your order"
msgstr "Cancella il tuo ordine"
msgstr "Solo ordini pagati"
#: pretix/presale/templates/pretixpresale/event/order.html:366
msgid ""
@@ -34984,7 +34984,7 @@ msgstr ""
#: pretix/presale/templates/pretixpresale/event/order.html:472
msgid "You can cancel this order using the following button."
msgstr "Puoi cancellare questo ordine usando il bottone di seguito."
msgstr ""
#: pretix/presale/templates/pretixpresale/event/order_cancel.html:11
#, python-format
@@ -35271,7 +35271,7 @@ msgstr "Devi selezionare almeno %(number)s prodotti."
#: pretix/presale/templates/pretixpresale/event/waitinglist.html:5
msgid "Add me to the waiting list"
msgstr "Aggiungimi alla lista d'attesa"
msgstr ""
#: pretix/presale/templates/pretixpresale/event/waitinglist.html:22
#, python-format
@@ -35280,10 +35280,6 @@ msgid ""
"waiting list. If we notify you, you'll have %(hours)s hours time to buy a "
"ticket until we assign it to the next person on the list."
msgstr ""
"Se i biglietti dovessero tornare disponibili, informeremo le prime persone "
"in lista d'attesa. Se ti avvisiamo, avrai %(hours)s ore di tempo per "
"acquistare un biglietto prima che venga assegnato alla persona successiva in "
"lista."
#: pretix/presale/templates/pretixpresale/event/waitinglist.html:28
msgid ""
@@ -35291,9 +35287,6 @@ msgid ""
"you need to add yourself to the waiting list multiple times. There is no "
"guarantee that you will receive a certain number of tickets."
msgstr ""
"Tieni presente che riceverai un solo biglietto. Se hai bisogno di più "
"biglietti, dovrai iscriverti alla lista d'attesa più volte. Non è garantito "
"che riceverai un numero esatto di biglietti."
#: pretix/presale/templates/pretixpresale/event/waitinglist.html:32
msgid ""
@@ -35302,10 +35295,6 @@ msgid ""
"email addresses. There is no guarantee that you will receive a certain "
"number of tickets."
msgstr ""
"Tieni presente che riceverai un solo biglietto. Se hai bisogno di più "
"biglietti, dovrai iscriverti alla lista d'attesa più volte utilizzando "
"indirizzi email diversi. Non è garantito che riceverai un numero esatto di "
"biglietti."
#: pretix/presale/templates/pretixpresale/event/waitinglist.html:38
msgid ""
@@ -35313,17 +35302,15 @@ msgid ""
"been added to the waiting list. We will only contact you once a spot opens "
"up."
msgstr ""
"<strong>Non</strong> riceverai un'email di conferma dopo essere stato "
"aggiunto alla lista d'attesa. Ti contatteremo solo quando si libererà un "
"posto."
#: pretix/presale/templates/pretixpresale/event/waitinglist.html:44
msgid "Add me to the list"
msgstr "Aggiungimi alla lista"
msgstr ""
#: pretix/presale/templates/pretixpresale/event/waitinglist_remove.html:5
#, fuzzy
msgid "Remove me from the waiting list"
msgstr "Cancellami dalla lista d'attesa"
msgstr "Informazioni dell'ordine modificate"
#: pretix/presale/templates/pretixpresale/event/waitinglist_remove.html:9
msgid ""
@@ -35333,9 +35320,10 @@ msgid ""
msgstr ""
#: pretix/presale/templates/pretixpresale/event/waitinglist_remove.html:16
#, fuzzy
msgctxt "waitinglist"
msgid "Yes, remove my ticket"
msgstr "Si, cancella i miei biglietti"
msgstr "Aggiungi o rimuovi biglietti"
#: pretix/presale/templates/pretixpresale/fragment_calendar.html:7
msgid "Calendar"
@@ -35359,8 +35347,9 @@ msgstr "(continua)"
#: pretix/presale/templates/pretixpresale/fragment_event_list_status.html:14
#: pretix/presale/templates/pretixpresale/fragment_week_calendar.html:58
#: pretix/presale/views/widget.py:440
#, fuzzy
msgid "Few tickets left"
msgstr "Pochi biglietti rimasti"
msgstr "Abilita formato biglietti"
#: pretix/presale/templates/pretixpresale/fragment_calendar.html:92
#: pretix/presale/templates/pretixpresale/fragment_day_calendar.html:97
@@ -35408,9 +35397,10 @@ msgid "iCal"
msgstr "iCal"
#: pretix/presale/templates/pretixpresale/fragment_day_calendar.html:25
#, fuzzy
msgctxt "day calendar"
msgid "Single events"
msgstr "Singoli eventi"
msgstr "Prezzo netto"
#: pretix/presale/templates/pretixpresale/fragment_day_calendar.html:79
msgctxt "timerange"
@@ -35436,12 +35426,13 @@ msgstr ""
#: pretix/presale/templates/pretixpresale/fragment_login_status.html:5
msgid "customer account"
msgstr "account cliente"
msgstr ""
#: pretix/presale/templates/pretixpresale/fragment_login_status.html:8
#: pretix/presale/templates/pretixpresale/fragment_login_status.html:9
#, fuzzy
msgid "View customer account"
msgstr "Vedi account cliente"
msgstr "Vedi un'altra data"
#: pretix/presale/templates/pretixpresale/fragment_modals.html:18
#, fuzzy
@@ -35639,8 +35630,9 @@ msgid "Change account information"
msgstr "Le tue informazioni"
#: pretix/presale/templates/pretixpresale/organizers/customer_base.html:41
#, fuzzy
msgid "customer account information"
msgstr "Informazioni account cliente"
msgstr "Invia ordine"
#: pretix/presale/templates/pretixpresale/organizers/customer_giftcards.html:28
#, fuzzy, python-format
@@ -35798,7 +35790,7 @@ msgstr "Ripeti la nuova password"
#: pretix/presale/templates/pretixpresale/organizers/index.html:11
msgid "Event list"
msgstr "Lista eventi"
msgstr ""
#: pretix/presale/templates/pretixpresale/organizers/index.html:35
msgid "Past events"
@@ -35809,9 +35801,10 @@ msgid "Upcoming events"
msgstr "Prossimi eventi"
#: pretix/presale/templates/pretixpresale/organizers/index.html:56
#, fuzzy
msgctxt "subevent"
msgid "Multiple dates"
msgstr "Date multiple"
msgstr "Solo ordini pagati"
#: pretix/presale/templates/pretixpresale/organizers/index.html:105
msgid "No archived events found."
@@ -36154,8 +36147,6 @@ msgid ""
"You cannot add yourself to the waiting list as this product is currently "
"available."
msgstr ""
"Non puoi aggiungerti alla lista d'attesa in quanto questo prodotto è "
"attualmente disponibile."
#: pretix/presale/views/waiting.py:180
#, python-brace-format

View File

@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-03-17 14:06+0000\n"
"PO-Revision-Date: 2026-03-25 14:14+0000\n"
"Last-Translator: Pietro Isotti <isottipietro@gmail.com>\n"
"PO-Revision-Date: 2026-02-10 16:49+0000\n"
"Last-Translator: Raffaele Doretto <ced@comune.portogruaro.ve.it>\n"
"Language-Team: Italian <https://translate.pretix.eu/projects/pretix/pretix-"
"js/it/>\n"
"Language: it\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.16.2\n"
"X-Generator: Weblate 5.15.2\n"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
@@ -310,8 +310,9 @@ msgid "Ticket code revoked/changed"
msgstr "Codice biglietto annullato/modificato"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:63
#, fuzzy
msgid "Ticket blocked"
msgstr "Biglietto bloccato"
msgstr "Biglietto non pagato"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:64
msgid "Ticket not valid at this time"
@@ -428,7 +429,7 @@ msgstr ""
#: pretix/static/pretixbase/js/asynctask.js:276
msgid "If this takes longer than a few minutes, please contact us."
msgstr "Se questa operazione richiede alcuni minuti, si prega di contattarci."
msgstr ""
#: pretix/static/pretixbase/js/asynctask.js:331
msgid "Close message"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-03-17 14:05+0000\n"
"PO-Revision-Date: 2026-03-23 21:00+0000\n"
"PO-Revision-Date: 2026-03-09 12:52+0000\n"
"Last-Translator: Hijiri Umemoto <hijiri@umemoto.org>\n"
"Language-Team: Japanese <https://translate.pretix.eu/projects/pretix/pretix/"
"ja/>\n"
@@ -399,8 +399,10 @@ msgstr ""
#: pretix/api/serializers/organizer.py:482
#: pretix/control/views/organizer.py:1039
#, fuzzy
#| msgid "Account information"
msgid "Account invitation"
msgstr "アカウントに招待"
msgstr "アカウント情報"
#: pretix/api/serializers/organizer.py:503
#: pretix/control/views/organizer.py:1138
@@ -3924,8 +3926,10 @@ msgid "Peppol participant ID"
msgstr "Peppol参加者ID"
#: pretix/base/invoicing/peppol.py:211
#, fuzzy
#| msgid "The Peppol participant ID is not registered on the Peppol network."
msgid "The Peppol participant ID does not match your VAT ID."
msgstr "Peppol参加者IDがVAT IDと一致しません。"
msgstr "Peppol参加者IDはPeppolネットワークに登録されていません。"
#: pretix/base/invoicing/peppol.py:214
msgctxt "peppol_invoice"
@@ -6762,8 +6766,10 @@ msgstr ""
"かかる場合があります。"
#: pretix/base/models/organizer.py:378
#, fuzzy
#| msgid "Event permissions"
msgid "All event permissions"
msgstr "すべてのイベントの権限"
msgstr "イベントの権限"
#: pretix/base/models/organizer.py:379
#: pretix/control/templates/pretixcontrol/organizers/team_edit.html:34
@@ -6771,8 +6777,10 @@ msgid "Event permissions"
msgstr "イベントの権限"
#: pretix/base/models/organizer.py:380
#, fuzzy
#| msgid "Organizer permissions"
msgid "All organizer permissions"
msgstr "全ての主催者の権限"
msgstr "主催者の権限"
#: pretix/base/models/organizer.py:381
#: pretix/control/templates/pretixcontrol/organizers/team_edit.html:25
@@ -8236,30 +8244,36 @@ msgstr ""
#: pretix/base/permissions.py:314 pretix/base/permissions.py:331
msgctxt "permission_level"
msgid "View"
msgstr "見る"
msgstr ""
#: pretix/base/permissions.py:164 pretix/base/permissions.py:169
#: pretix/base/permissions.py:174 pretix/base/permissions.py:179
#: pretix/base/permissions.py:286 pretix/base/permissions.py:315
#, fuzzy
#| msgid "Save and check"
msgctxt "permission_level"
msgid "View and change"
msgstr "確認と変更"
msgstr "保存してチェックします"
#: pretix/base/permissions.py:168
#, fuzzy
#| msgid "API tokens"
msgid "API only"
msgstr "APIのみ"
msgstr "APIトークン"
#: pretix/base/permissions.py:173
msgid ""
"Menu item will only show up if the user has permission for general settings."
msgstr "メニュー項目は、ユーザーが一般設定の権限を持っている場合にのみ表示されます。"
msgstr ""
#: pretix/base/permissions.py:177 pretix/base/permissions.py:231
#: pretix/base/permissions.py:285 pretix/base/permissions.py:313
#: pretix/base/permissions.py:330
#, fuzzy
#| msgid "Revoke access"
msgctxt "permission_level"
msgid "No access"
msgstr "アクセスなし"
msgstr "アクセスを取り消す"
#: pretix/base/permissions.py:188
#: pretix/control/templates/pretixcontrol/event/settings.html:7
@@ -8273,8 +8287,6 @@ msgid ""
"This includes access to all settings not listed explicitly below, including "
"plugin settings."
msgstr ""
"これには、以下に明示的に記載されていないすべての設定(プラグイン設定を含む)"
"へのアクセスが含まれます。"
#: pretix/base/permissions.py:197
#: pretix/control/templates/pretixcontrol/event/payment.html:6
@@ -8288,77 +8300,100 @@ msgid "Tax settings"
msgstr "税の設定"
#: pretix/base/permissions.py:209
#, fuzzy
#| msgid "Invoice settings"
msgid "Invoicing settings"
msgstr "請求の設定"
msgstr "請求の設定"
#: pretix/base/permissions.py:215
#, fuzzy
#| msgctxt "subevent"
#| msgid "Event series date added"
msgid "Event series dates"
msgstr "イベントシリーズ日程"
msgstr "イベントシリーズ日程が追加されました"
#: pretix/base/permissions.py:221
#, fuzzy
#| msgid "Product name and variation"
msgid "Products, quotas and questions"
msgstr "製品、クオータおよび質問"
msgstr "製品名とバリエーション"
#: pretix/base/permissions.py:224
msgid "Also includes related objects like categories or discounts."
msgstr "カテゴリや割引などの関連オブジェクトも含まれます。"
msgstr ""
#: pretix/base/permissions.py:232
#, fuzzy
#| msgid "All check-ins"
msgctxt "permission_level"
msgid "Only check-in"
msgstr "チェックインのみ"
msgstr "すべてのチェックイン"
#: pretix/base/permissions.py:233
#, fuzzy
#| msgid "View full log"
msgctxt "permission_level"
msgid "View all"
msgstr "すべてを表示"
msgstr "フルログを表示します"
#: pretix/base/permissions.py:234
#, fuzzy
#| msgid "Valid check-in"
msgctxt "permission_level"
msgid "View all and check-in"
msgstr "すべてを表示してチェックイン"
msgstr "有効なチェックイン"
#: pretix/base/permissions.py:235
#, fuzzy
#| msgid "View all upcoming events"
msgctxt "permission_level"
msgid "View all and change"
msgstr "すべてを表示して変更する"
msgstr "すべての予定されているイベントを表示します"
#: pretix/base/permissions.py:236
msgid "Includes the ability to cancel and refund individual orders."
msgstr "個別の注文をキャンセルおよび返金する機能が含まれています。"
msgstr ""
#: pretix/base/permissions.py:238
#, fuzzy
#| msgid "An entry has been added to the waiting list."
msgid "Also includes related objects like the waiting list."
msgstr "待機リストなどの関連オブジェクトも含まれます。"
msgstr "空席待ちリストに登録が追加されました。"
#: pretix/base/permissions.py:248
#, fuzzy
#| msgid "Generate cancellation"
msgid "Full event or date cancellation"
msgstr "イベント全体または日程のキャンセル"
msgstr "キャンセルを生成します"
#: pretix/base/permissions.py:252
#, fuzzy
#| msgid "Sale not allowed"
msgctxt "permission_level"
msgid "Not allowed"
msgstr "許可されていません"
msgstr "販売は許可されていません"
#: pretix/base/permissions.py:253
#, fuzzy
#| msgid "Allowed titles"
msgctxt "permission_level"
msgid "Allowed"
msgstr "許可されている"
msgstr "選択可能な敬称"
#: pretix/base/permissions.py:268
msgctxt "permission_level"
msgid "Access existing events"
msgstr "既存のイベントにアクセス"
msgstr ""
#: pretix/base/permissions.py:269
msgctxt "permission_level"
msgid "Access existing and create new events"
msgstr "既存のイベントにアクセスし、新しいイベントを作成する"
msgstr ""
#: pretix/base/permissions.py:271
msgid ""
"The level of access to events is determined in detail by the settings below."
msgstr "イベントへのアクセスレベルは、以下の設定によって詳細に決定されます。"
msgstr ""
#: pretix/base/permissions.py:275 pretix/control/navigation.py:143
#: pretix/control/navigation.py:462 pretix/control/navigation.py:512
@@ -8377,14 +8412,12 @@ msgid ""
"This includes access to all organizer-level functionality not listed "
"explicitly below, including plugin settings."
msgstr ""
"これには、以下に明示的に記載されていないすべてのオーガナイザーレベル機能への"
"アクセスが含まれ、プラグイン設定も含まれます。"
#: pretix/base/permissions.py:287
msgid ""
"Includes the ability to give someone (including oneself) additional "
"permissions."
msgstr "自分自身を含む誰かに追加の権限を付与する機能が含まれています。"
msgstr ""
#: pretix/base/permissions.py:298 pretix/control/navigation.py:608
#: pretix/control/templates/pretixcontrol/organizers/customers.html:6
@@ -8402,12 +8435,12 @@ msgid ""
"Includes the ability to give access to events and data oneself does not have "
"access to."
msgstr ""
"自らがアクセスできないイベントやデータへのアクセス権を付与する機能が含まれま"
"す。"
#: pretix/base/permissions.py:321
#, fuzzy
#| msgid "Seating plan"
msgid "Seating plans"
msgstr "座席プラン"
msgstr "座席"
#: pretix/base/permissions.py:327 pretix/control/navigation.py:712
#: pretix/control/templates/pretixcontrol/organizers/outgoing_mails.html:8
@@ -9010,12 +9043,12 @@ msgid "Czech National Bank"
msgstr "チェコ国立銀行"
#: pretix/base/services/export.py:94 pretix/base/services/export.py:154
#, fuzzy
#| msgid "You do not have sufficient permission to perform this export."
msgid ""
"Export not found or you do not have sufficient permission to perform this "
"export."
msgstr ""
"エクスポートが見つからないか、エクスポートを実行するための十分な権限がありま"
"せん。"
msgstr "このエクスポートを実行するための十分な権限がありません。"
#: pretix/base/services/export.py:107 pretix/base/services/export.py:179
#: pretix/base/services/export.py:357
@@ -13301,7 +13334,25 @@ msgid "Contact"
msgstr "連絡先"
#: pretix/base/templates/pretixbase/email/shred_completed.txt:2
#, python-format
#, fuzzy, python-format
#| msgid ""
#| "Hello,\n"
#| "\n"
#| "we hereby confirm that the following data shredding job has been "
#| "completed:\n"
#| "\n"
#| "Organizer: %(organizer)s\n"
#| "\n"
#| "Event: %(event)s\n"
#| "\n"
#| "Data selection: %(shredders)s\n"
#| "\n"
#| "Start time: %(start_time)s (new data added after this time might not have "
#| "been deleted)\n"
#| "\n"
#| "Best regards,\n"
#| "\n"
#| "Your pretix team\n"
msgid ""
"Hello,\n"
"\n"
@@ -13335,7 +13386,7 @@ msgstr ""
"\n"
"敬具\n"
"\n"
"担当 %(instance)sチーム\n"
"担当 pretix チーム\n"
#: pretix/base/templates/pretixbase/forms/widgets/checkbox_sales_channel_option.html:16
msgid ""
@@ -13889,12 +13940,16 @@ msgid "This is an event series"
msgstr "これはイベントシリーズです"
#: pretix/control/forms/event.py:135 pretix/control/forms/event.py:363
#, fuzzy
#| msgid ""
#| "You do not have sufficient permission to enable plugins that need to be "
#| "enabled for the entire organizer account."
msgid ""
"You do not have a sufficient level of access on the event you selected to "
"copy it to the desired organizer."
msgstr ""
"選択したイベントに対して、目的の主催者にコピーするための十分なアクセス権があ"
"りません。"
"主催者アカウント全体で有効化する必要があるプラグインを有効にするのに十分な権"
"限がありません。"
#: pretix/control/forms/event.py:143
msgid ""
@@ -13973,8 +14028,6 @@ msgid ""
"You cannot choose a team that would give you more access than you have on "
"the event you are copying."
msgstr ""
"コピーしているイベントに対して、より多くのアクセス権が付与されるチームを選択"
"することはできません。"
#: pretix/control/forms/event.py:344
msgid "Copy configuration from"
@@ -13990,8 +14043,6 @@ msgid ""
"You cannot choose an event on which you have less access than the team you "
"selected in the previous step."
msgstr ""
"前のステップで選択したチームよりもアクセス権が少ないイベントを選択することは"
"できません。"
#: pretix/control/forms/event.py:387 pretix/control/forms/item.py:1304
#: pretix/control/forms/subevents.py:411
@@ -14941,7 +14992,7 @@ msgstr "すべての参加者"
#: pretix/plugins/checkinlists/exporters.py:109
#: pretix/plugins/checkinlists/exporters.py:501
msgid "Checked in"
msgstr "チェックイン済み"
msgstr "チェックインしました"
#: pretix/control/forms/filter.py:2043
#: pretix/plugins/checkinlists/exporters.py:110
@@ -14959,7 +15010,7 @@ msgstr "チェックインしたが退出済み"
#: pretix/control/templates/pretixcontrol/checkin/index.html:178
#: pretix/plugins/checkinlists/exporters.py:112
msgid "Not checked in"
msgstr "チェックイン未了"
msgstr "チェックイン"
#: pretix/control/forms/filter.py:2064
msgctxt "subevent"
@@ -16296,7 +16347,7 @@ msgstr "1つの組織ドメインのみを設定できます。"
#: pretix/control/forms/organizer.py:322
msgid "Provided by a plugin"
msgstr "プラグインによって提供"
msgstr ""
#: pretix/control/forms/organizer.py:438
msgid ""
@@ -17165,8 +17216,10 @@ msgid "The order has been denied (comment: \"{comment}\")."
msgstr "注文は拒否されました(コメント:\"{comment}\")。"
#: pretix/control/logdisplay.py:521
#, fuzzy
#| msgid "The order has been overpaid."
msgid "The customer VAT ID has been verified."
msgstr "顧客のVAT IDが確認されました。"
msgstr "注文が過払いされました。"
#: pretix/control/logdisplay.py:522
#, python-brace-format
@@ -17445,9 +17498,10 @@ msgid "{user} has been invited to the team."
msgstr "{user} がチームに招待されました。"
#: pretix/control/logdisplay.py:644
#, python-brace-format
#, fuzzy, python-brace-format
#| msgid "Invite for {user} has been resent."
msgid "Invite for {user} has been deleted."
msgstr "{user}への招待が削除されました。"
msgstr "{user}への招待状が再送されました。"
#: pretix/control/logdisplay.py:645
#, python-brace-format
@@ -19725,7 +19779,21 @@ msgid "Add property"
msgstr "プロパティを追加"
#: pretix/control/templates/pretixcontrol/email/confirmation_code.txt:1
#, python-format
#, fuzzy, python-format
#| msgid ""
#| "Hello,\n"
#| "\n"
#| "%(reason)s\n"
#| "\n"
#| " %(code)s\n"
#| "\n"
#| "Please do never give this code to another person. Our support team will "
#| "never ask for this code.\n"
#| "\n"
#| "If this code was not requested by you, please contact us immediately.\n"
#| "\n"
#| "Best regards,\n"
#| "Your pretix team\n"
msgid ""
"Hello,\n"
"\n"
@@ -19753,7 +19821,7 @@ msgstr ""
"このコードにお心当たりがない場合は、直ちにご連絡ください。\n"
"\n"
"よろしくお願いいたします。\n"
"%(instance)sチーム\n"
"pretixチーム\n"
#: pretix/control/templates/pretixcontrol/email/email_setup.txt:1
#, python-format
@@ -19791,7 +19859,17 @@ msgstr ""
"%(instance)s チーム\n"
#: pretix/control/templates/pretixcontrol/email/forgot.txt:1
#, python-format
#, fuzzy, python-format
#| msgid ""
#| "Hello,\n"
#| "\n"
#| "you requested a new password. Please go to the following page to reset "
#| "your password:\n"
#| "\n"
#| "%(url)s\n"
#| "\n"
#| "Best regards, \n"
#| "Your pretix team\n"
msgid ""
"Hello,\n"
"\n"
@@ -19811,10 +19889,27 @@ msgstr ""
"%(url)s\n"
"\n"
"よろしくお願いします、 \n"
"あなたの%(instance)sチーム\n"
"あなたのpretixチーム\n"
#: pretix/control/templates/pretixcontrol/email/invitation.txt:1
#, python-format
#, fuzzy, python-format
#| msgid ""
#| "Hello,\n"
#| "\n"
#| "you have been invited to a team on pretix, a platform to perform event\n"
#| "ticket sales.\n"
#| "\n"
#| "Organizer: %(organizer)s\n"
#| "Team: %(team)s\n"
#| "\n"
#| "If you want to join that team, just click on the following link:\n"
#| "%(url)s\n"
#| "\n"
#| "If you do not want to join, you can safely ignore or delete this email.\n"
#| "\n"
#| "Best regards, \n"
#| "\n"
#| "Your pretix team\n"
msgid ""
"Hello,\n"
"\n"
@@ -19836,7 +19931,7 @@ msgid ""
msgstr ""
"こんにちは、\n"
"\n"
"あなたは、%(instance)sというイベントチケット販売プラットフォームの\n"
"あなたは、pretixというイベントチケット販売プラットフォームの\n"
"チームに招待されました。\n"
"\n"
"主催者: %(organizer)s\n"
@@ -19849,7 +19944,7 @@ msgstr ""
"\n"
"よろしくお願いします。\n"
"\n"
"%(instance)sチーム\n"
"pretixチーム\n"
#: pretix/control/templates/pretixcontrol/email/login_notice.txt:1
#, python-format
@@ -19886,7 +19981,24 @@ msgstr ""
"%(instance)sチーム\n"
#: pretix/control/templates/pretixcontrol/email/security_notice.txt:1
#, python-format
#, fuzzy, python-format
#| msgid ""
#| "Hello,\n"
#| "\n"
#| "this is to inform you that the account information of your pretix account "
#| "has been\n"
#| "changed. In particular, the following changes have been performed:\n"
#| "\n"
#| "%(messages)s\n"
#| "\n"
#| "If this change was not performed by you, please contact us immediately.\n"
#| "\n"
#| "You can review and change your account settings here:\n"
#| "\n"
#| "%(url)s\n"
#| "\n"
#| "Best regards, \n"
#| "Your pretix team\n"
msgid ""
"Hello,\n"
"\n"
@@ -19907,21 +20019,20 @@ msgid ""
msgstr ""
"こんにちは\n"
"\n"
"あなたの%(instance)sのアカウント情報が変更されましたので、お知らせいたします"
"。\n"
"お客様のpretixアカウント情報が変更されましたので、お知らせいたします。\n"
"具体的には、以下の変更が行われました:\n"
"\n"
"%(messages)s\n"
"\n"
"もしもこの変更がお客様自身によるものでない場合は、直ちにお問い合わせくださ"
"。\n"
"もしもこの変更がお客様自身によるものでない場合は、直ちにお問い合わせくださ"
"。\n"
"\n"
"アカウント設定の確認や変更はこちらから行えます:\n"
"\n"
"%(url)s\n"
"\n"
"以上、よろしくお願いいたします。 \n"
"%(instance)sチームより\n"
"pretixチームより\n"
#: pretix/control/templates/pretixcontrol/email_setup.html:8
#: pretix/control/templates/pretixcontrol/email_setup_simple.html:8
@@ -20176,8 +20287,10 @@ msgstr ""
"ルと払い戻しを行うことができます。"
#: pretix/control/templates/pretixcontrol/event/dangerzone.html:51
#, fuzzy
#| msgid "Permissions"
msgid "No permission"
msgstr "権限なし"
msgstr "権限"
#: pretix/control/templates/pretixcontrol/event/dangerzone.html:59
#: pretix/control/templates/pretixcontrol/event/dangerzone.html:72
@@ -22655,8 +22768,10 @@ msgid "Edit question"
msgstr "質問を編集"
#: pretix/control/templates/pretixcontrol/items/question.html:55
#, fuzzy
#| msgid "You do not have permission to view this content."
msgid "No permission to view answers."
msgstr "回答を閲覧する権限がありません。"
msgstr "このコンテンツを表示する権限がありません。"
#: pretix/control/templates/pretixcontrol/items/question.html:63
msgid "No matching answers found."
@@ -24267,8 +24382,10 @@ msgstr "次の実行は予定されていない"
#: pretix/control/templates/pretixcontrol/orders/export.html:37
#: pretix/control/templates/pretixcontrol/organizers/export.html:37
#, fuzzy
#| msgid "Exporter not found"
msgid "Exporter not found or no permission"
msgstr "エクスポーターが見つからない、または権限がありません"
msgstr "エクスポーターが見つかりません"
#: pretix/control/templates/pretixcontrol/orders/export.html:42
#: pretix/control/templates/pretixcontrol/organizers/export.html:42
@@ -24311,8 +24428,10 @@ msgstr "新規ユーザー向けのおすすめです"
#: pretix/control/templates/pretixcontrol/orders/export.html:120
#: pretix/control/templates/pretixcontrol/organizers/export.html:120
#, fuzzy
#| msgid "There are no add-ons available for this product."
msgid "There are no exporters available for you."
msgstr "ご利用いただけるエクスポーターがありません。"
msgstr "この製品に利用可能なアドオンはありません。"
#: pretix/control/templates/pretixcontrol/orders/export_delete.html:4
#: pretix/control/templates/pretixcontrol/orders/export_delete.html:6
@@ -26210,7 +26329,7 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/pdf/index.html:274
#, python-format
msgid "max. %(size)s, smaller is better"
msgstr "最大%(size)sで、より少ない数値が望ましい"
msgstr ""
#: pretix/control/templates/pretixcontrol/pdf/index.html:282
msgid "Download current background"
@@ -28904,12 +29023,16 @@ msgstr ""
"い。"
#: pretix/control/views/orders.py:2798 pretix/control/views/organizer.py:2131
#, fuzzy
#| msgid ""
#| "Your user account does not have sufficient permission to run this report, "
#| "therefore you cannot schedule it."
msgid ""
"Your user account does not have sufficient permission to run this report, "
"therefore you cannot change it."
msgstr ""
"あなたのユーザーアカウントにはこのレポートを実行する十分な権限がありませんの"
"で、変更することはできません。"
"ユーザーアカウントにはこのレポートを実行するのに十分な権限がないため、スケ"
"ジュールを設定できません。"
#: pretix/control/views/orders.py:2807 pretix/control/views/organizer.py:2140
msgid ""
@@ -32872,7 +32995,7 @@ msgstr "Stripe経由でiDEAL"
#: pretix/plugins/stripe/payment.py:1572
msgid "iDEAL | Wero"
msgstr "iDEAL | Wero"
msgstr ""
#: pretix/plugins/stripe/payment.py:1575
msgid ""
@@ -35782,8 +35905,6 @@ msgid ""
"the shop that affect quotas, such as the validity period of carts and "
"vouchers."
msgstr ""
"変更された時間は、カートやバウチャーの有効期間など、クォータに影響を与える"
"ショップの項目については考慮されませんので、ご了承ください。"
#: pretix/presale/templates/pretixpresale/event/timemachine.html:31
msgid "Enable time machine"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-03-17 14:06+0000\n"
"PO-Revision-Date: 2026-03-23 21:00+0000\n"
"PO-Revision-Date: 2026-02-23 10:00+0000\n"
"Last-Translator: Hijiri Umemoto <hijiri@umemoto.org>\n"
"Language-Team: Japanese <https://translate.pretix.eu/projects/pretix/pretix-"
"js/ja/>\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Weblate 5.16.2\n"
"X-Generator: Weblate 5.16\n"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
@@ -60,7 +60,7 @@ msgstr "PayPal後払い"
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:41
msgid "iDEAL | Wero"
msgstr "iDEAL | Wero"
msgstr ""
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:42
msgid "SEPA Direct Debit"

View File

@@ -7,10 +7,10 @@ msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-03-17 14:05+0000\n"
"PO-Revision-Date: 2026-03-18 12:23+0000\n"
"PO-Revision-Date: 2026-03-14 22:00+0000\n"
"Last-Translator: Ruud Hendrickx <ruud@leckxicon.eu>\n"
"Language-Team: Dutch <https://translate.pretix.eu/projects/pretix/pretix/nl/>"
"\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"
@@ -407,8 +407,10 @@ msgstr ""
#: pretix/api/serializers/organizer.py:482
#: pretix/control/views/organizer.py:1039
#, fuzzy
#| msgid "Account information"
msgid "Account invitation"
msgstr "Uitnodiging voor account"
msgstr "Accountinformatie"
#: pretix/api/serializers/organizer.py:503
#: pretix/control/views/organizer.py:1138
@@ -3946,8 +3948,10 @@ msgid "Peppol participant ID"
msgstr "Peppol-deelnemer-ID"
#: pretix/base/invoicing/peppol.py:211
#, fuzzy
#| msgid "The Peppol participant ID is not registered on the Peppol network."
msgid "The Peppol participant ID does not match your VAT ID."
msgstr "Het Peppol-deelnemersnummer komt niet overeen met uw btw-nummer."
msgstr "De Peppol-deelnemer-ID is niet geregistreerd op het Peppol-netwerk."
#: pretix/base/invoicing/peppol.py:214
msgctxt "peppol_invoice"
@@ -6870,8 +6874,10 @@ msgstr ""
"minuten duren voordat de instelling voor alle gebruikers van kracht wordt."
#: pretix/base/models/organizer.py:378
#, fuzzy
#| msgid "Event permissions"
msgid "All event permissions"
msgstr "Alle evenementrechten"
msgstr "Evenementrechten"
#: pretix/base/models/organizer.py:379
#: pretix/control/templates/pretixcontrol/organizers/team_edit.html:34
@@ -6879,8 +6885,10 @@ msgid "Event permissions"
msgstr "Evenementrechten"
#: pretix/base/models/organizer.py:380
#, fuzzy
#| msgid "Organizer permissions"
msgid "All organizer permissions"
msgstr "Alle rechten van de organisator"
msgstr "Organisatorrechten"
#: pretix/base/models/organizer.py:381
#: pretix/control/templates/pretixcontrol/organizers/team_edit.html:25
@@ -8385,32 +8393,36 @@ msgstr "Uw lay-outbestand is geen geldige lay-out. Foutmelding: {}"
#: pretix/base/permissions.py:314 pretix/base/permissions.py:331
msgctxt "permission_level"
msgid "View"
msgstr "Lezen"
msgstr ""
#: pretix/base/permissions.py:164 pretix/base/permissions.py:169
#: pretix/base/permissions.py:174 pretix/base/permissions.py:179
#: pretix/base/permissions.py:286 pretix/base/permissions.py:315
#, fuzzy
#| msgid "Save and check"
msgctxt "permission_level"
msgid "View and change"
msgstr "Bekijken en wijzigen"
msgstr "Opslaan en controleren"
#: pretix/base/permissions.py:168
#, fuzzy
#| msgid "API tokens"
msgid "API only"
msgstr "alleen API"
msgstr "API-tokens"
#: pretix/base/permissions.py:173
msgid ""
"Menu item will only show up if the user has permission for general settings."
msgstr ""
"Dit menu-item wordt alleen weergegeven als de gebruiker rechten heeft voor "
"algemene instellingen."
#: pretix/base/permissions.py:177 pretix/base/permissions.py:231
#: pretix/base/permissions.py:285 pretix/base/permissions.py:313
#: pretix/base/permissions.py:330
#, fuzzy
#| msgid "Revoke access"
msgctxt "permission_level"
msgid "No access"
msgstr "Geen toegang"
msgstr "Toegang intrekken"
#: pretix/base/permissions.py:188
#: pretix/control/templates/pretixcontrol/event/settings.html:7
@@ -8424,8 +8436,6 @@ msgid ""
"This includes access to all settings not listed explicitly below, including "
"plugin settings."
msgstr ""
"Dit omvat toegang tot alle instellingen die hieronder niet expliciet worden "
"vermeld, inclusief instellingen voor plug-ins."
#: pretix/base/permissions.py:197
#: pretix/control/templates/pretixcontrol/event/payment.html:6
@@ -8439,81 +8449,100 @@ msgid "Tax settings"
msgstr "Belastinginstellingen"
#: pretix/base/permissions.py:209
#, fuzzy
#| msgid "Invoice settings"
msgid "Invoicing settings"
msgstr "Factuurinstellingen"
#: pretix/base/permissions.py:215
#, fuzzy
#| msgctxt "subevent"
#| msgid "Event series date added"
msgid "Event series dates"
msgstr "Datums van de evenementenreeks"
msgstr "Evenementenreeks: datum toegevoegd"
#: pretix/base/permissions.py:221
#, fuzzy
#| msgid "Product name and variation"
msgid "Products, quotas and questions"
msgstr "Producten, quota en vragen"
msgstr "Productnaam en variant"
#: pretix/base/permissions.py:224
msgid "Also includes related objects like categories or discounts."
msgstr "Omvat ook gerelateerde objecten zoals categorieën of kortingen."
msgstr ""
#: pretix/base/permissions.py:232
#, fuzzy
#| msgid "All check-ins"
msgctxt "permission_level"
msgid "Only check-in"
msgstr "Alleen inchecken"
msgstr "Alle check-ins"
#: pretix/base/permissions.py:233
#, fuzzy
#| msgid "View full log"
msgctxt "permission_level"
msgid "View all"
msgstr "Alles bekijken"
msgstr "Toon volledige log"
#: pretix/base/permissions.py:234
#, fuzzy
#| msgid "Valid check-in"
msgctxt "permission_level"
msgid "View all and check-in"
msgstr "Alles bekijken en inchecken"
msgstr "Geldige check-in"
#: pretix/base/permissions.py:235
#, fuzzy
#| msgid "View all upcoming events"
msgctxt "permission_level"
msgid "View all and change"
msgstr "Alles bekijken en wijzigen"
msgstr "Bekijk alle aankomende evenementen"
#: pretix/base/permissions.py:236
msgid "Includes the ability to cancel and refund individual orders."
msgstr ""
"Biedt de mogelijkheid om afzonderlijke bestellingen te annuleren en terug te "
"betalen."
#: pretix/base/permissions.py:238
#, fuzzy
#| msgid "An entry has been added to the waiting list."
msgid "Also includes related objects like the waiting list."
msgstr "Bevat ook aanverwante objecten, zoals de wachtlijst."
msgstr "Er is een inschrijving toegevoegd aan de wachtlijst."
#: pretix/base/permissions.py:248
#, fuzzy
#| msgid "Generate cancellation"
msgid "Full event or date cancellation"
msgstr "Volledige annulering van het evenement of de datum"
msgstr "Genereer annulering"
#: pretix/base/permissions.py:252
#, fuzzy
#| msgid "Sale not allowed"
msgctxt "permission_level"
msgid "Not allowed"
msgstr "Niet toegestaan"
msgstr "Verkoop niet toegestaan"
#: pretix/base/permissions.py:253
#, fuzzy
#| msgid "Allowed titles"
msgctxt "permission_level"
msgid "Allowed"
msgstr "Toegestaan"
msgstr "Toegestane titels"
#: pretix/base/permissions.py:268
msgctxt "permission_level"
msgid "Access existing events"
msgstr "Toegang tot bestaande evenementen"
msgstr ""
#: pretix/base/permissions.py:269
msgctxt "permission_level"
msgid "Access existing and create new events"
msgstr "Toegang tot bestaande evenementen en nieuwe evenementen aanmaken"
msgstr ""
#: pretix/base/permissions.py:271
msgid ""
"The level of access to events is determined in detail by the settings below."
msgstr ""
"De toegang tot evenementen wordt in detail bepaald door de onderstaande "
"instellingen."
#: pretix/base/permissions.py:275 pretix/control/navigation.py:143
#: pretix/control/navigation.py:462 pretix/control/navigation.py:512
@@ -8532,15 +8561,12 @@ msgid ""
"This includes access to all organizer-level functionality not listed "
"explicitly below, including plugin settings."
msgstr ""
"Dit omvat toegang tot alle functies op organisatieniveau die hieronder niet "
"expliciet worden vermeld, inclusief de instellingen van plug-ins."
#: pretix/base/permissions.py:287
msgid ""
"Includes the ability to give someone (including oneself) additional "
"permissions."
msgstr ""
"Biedt de mogelijkheid om iemand (inclusief uzelf) extra rechten te geven."
#: pretix/base/permissions.py:298 pretix/control/navigation.py:608
#: pretix/control/templates/pretixcontrol/organizers/customers.html:6
@@ -8558,12 +8584,12 @@ msgid ""
"Includes the ability to give access to events and data oneself does not have "
"access to."
msgstr ""
"Biedt de mogelijkheid om toegang te verlenen tot evenementen en gegevens "
"waar men zelf geen toegang toe heeft."
#: pretix/base/permissions.py:321
#, fuzzy
#| msgid "Seating plan"
msgid "Seating plans"
msgstr "Zaalplannen"
msgstr "Zaalplan"
#: pretix/base/permissions.py:327 pretix/control/navigation.py:712
#: pretix/control/templates/pretixcontrol/organizers/outgoing_mails.html:8
@@ -9198,12 +9224,12 @@ msgid "Czech National Bank"
msgstr "Tsjechische Nationale Bank"
#: pretix/base/services/export.py:94 pretix/base/services/export.py:154
#, fuzzy
#| msgid "You do not have sufficient permission to perform this export."
msgid ""
"Export not found or you do not have sufficient permission to perform this "
"export."
msgstr ""
"De export is niet gevonden of u hebt onvoldoende rechten om deze export uit "
"te voeren."
msgstr "U bent niet gemachtigd om deze export uit te voeren."
#: pretix/base/services/export.py:107 pretix/base/services/export.py:179
#: pretix/base/services/export.py:357
@@ -13658,7 +13684,25 @@ msgid "Contact"
msgstr "Contact"
#: pretix/base/templates/pretixbase/email/shred_completed.txt:2
#, python-format
#, fuzzy, python-format
#| msgid ""
#| "Hello,\n"
#| "\n"
#| "we hereby confirm that the following data shredding job has been "
#| "completed:\n"
#| "\n"
#| "Organizer: %(organizer)s\n"
#| "\n"
#| "Event: %(event)s\n"
#| "\n"
#| "Data selection: %(shredders)s\n"
#| "\n"
#| "Start time: %(start_time)s (new data added after this time might not have "
#| "been deleted)\n"
#| "\n"
#| "Best regards,\n"
#| "\n"
#| "Your pretix team\n"
msgid ""
"Hello,\n"
"\n"
@@ -13693,7 +13737,7 @@ msgstr ""
"\n"
"Met vriendelijke groeten,\n"
"\n"
"Het %(instance)s-team\n"
"Het pretix-team\n"
#: pretix/base/templates/pretixbase/forms/widgets/checkbox_sales_channel_option.html:16
msgid ""
@@ -14254,12 +14298,16 @@ msgid "This is an event series"
msgstr "Dit is een evenementenreeks"
#: pretix/control/forms/event.py:135 pretix/control/forms/event.py:363
#, fuzzy
#| msgid ""
#| "You do not have sufficient permission to enable plugins that need to be "
#| "enabled for the entire organizer account."
msgid ""
"You do not have a sufficient level of access on the event you selected to "
"copy it to the desired organizer."
msgstr ""
"U hebt onvoldoende rechten voor het evenement dat u hebt geselecteerd om het "
"naar de gewenste organisator te kopiëren."
"U hebt niet voldoende rechten om plug-ins in te schakelen die voor het hele "
"organisatoraccount moeten worden ingeschakeld."
#: pretix/control/forms/event.py:143
msgid ""
@@ -14338,8 +14386,6 @@ msgid ""
"You cannot choose a team that would give you more access than you have on "
"the event you are copying."
msgstr ""
"U kunt geen team kiezen dat u meer toegangsrechten zou geven dan u nu hebt "
"voor het evenement dat u kopieert."
#: pretix/control/forms/event.py:344
msgid "Copy configuration from"
@@ -14355,8 +14401,6 @@ msgid ""
"You cannot choose an event on which you have less access than the team you "
"selected in the previous step."
msgstr ""
"U kunt geen evenement kiezen waarvoor u minder toegangsrechten hebt dan het "
"team dat u in de vorige stap geselecteerd hebt."
#: pretix/control/forms/event.py:387 pretix/control/forms/item.py:1304
#: pretix/control/forms/subevents.py:411
@@ -15313,7 +15357,7 @@ msgstr "Zoek deelnemer…"
#: pretix/control/forms/filter.py:2039
#: pretix/plugins/checkinlists/exporters.py:106
msgid "Check-in status"
msgstr "Check-in-status"
msgstr "Incheckstatus"
#: pretix/control/forms/filter.py:2041
#: pretix/plugins/checkinlists/exporters.py:108
@@ -16726,7 +16770,7 @@ msgstr "Er kan maximaal 1 organisatordomein gekozen worden."
#: pretix/control/forms/organizer.py:322
msgid "Provided by a plugin"
msgstr "Beschikbaar gesteld via een plug-in"
msgstr ""
#: pretix/control/forms/organizer.py:438
msgid ""
@@ -17602,8 +17646,10 @@ msgid "The order has been denied (comment: \"{comment}\")."
msgstr "De bestelling is geweigerd (commentaar: \"{comment}\")."
#: pretix/control/logdisplay.py:521
#, fuzzy
#| msgid "The order has been overpaid."
msgid "The customer VAT ID has been verified."
msgstr "Het btw-nummer van de klant is geverifieerd."
msgstr "Er is te veel betaald voor de bestelling."
#: pretix/control/logdisplay.py:522
#, python-brace-format
@@ -17905,9 +17951,10 @@ msgid "{user} has been invited to the team."
msgstr "{user} is uitgenodigd voor het team."
#: pretix/control/logdisplay.py:644
#, python-brace-format
#, fuzzy, python-brace-format
#| msgid "Invite for {user} has been resent."
msgid "Invite for {user} has been deleted."
msgstr "De uitnodiging voor {user} is verwijderd."
msgstr "De uitnodiging voor {user} is opnieuw verstuurd."
#: pretix/control/logdisplay.py:645
#, python-brace-format
@@ -20212,7 +20259,21 @@ msgid "Add property"
msgstr "Eigenschap toevoegen"
#: pretix/control/templates/pretixcontrol/email/confirmation_code.txt:1
#, python-format
#, fuzzy, python-format
#| msgid ""
#| "Hello,\n"
#| "\n"
#| "%(reason)s\n"
#| "\n"
#| " %(code)s\n"
#| "\n"
#| "Please do never give this code to another person. Our support team will "
#| "never ask for this code.\n"
#| "\n"
#| "If this code was not requested by you, please contact us immediately.\n"
#| "\n"
#| "Best regards,\n"
#| "Your pretix team\n"
msgid ""
"Hello,\n"
"\n"
@@ -20241,7 +20302,7 @@ msgstr ""
"op.\n"
"\n"
"Groeten,\n"
"Uw %(instance)s-team\n"
"Uw pretix-team\n"
#: pretix/control/templates/pretixcontrol/email/email_setup.txt:1
#, python-format
@@ -20277,7 +20338,17 @@ msgstr ""
"Uw %(instance)s-team\n"
#: pretix/control/templates/pretixcontrol/email/forgot.txt:1
#, python-format
#, fuzzy, python-format
#| msgid ""
#| "Hello,\n"
#| "\n"
#| "you requested a new password. Please go to the following page to reset "
#| "your password:\n"
#| "\n"
#| "%(url)s\n"
#| "\n"
#| "Best regards, \n"
#| "Your pretix team\n"
msgid ""
"Hello,\n"
"\n"
@@ -20297,10 +20368,27 @@ msgstr ""
"%(url)s\n"
"\n"
"Met vriendelijke groet,\n"
"Het %(instance)s-team\n"
"Het pretix-team\n"
#: pretix/control/templates/pretixcontrol/email/invitation.txt:1
#, python-format
#, fuzzy, python-format
#| msgid ""
#| "Hello,\n"
#| "\n"
#| "you have been invited to a team on pretix, a platform to perform event\n"
#| "ticket sales.\n"
#| "\n"
#| "Organizer: %(organizer)s\n"
#| "Team: %(team)s\n"
#| "\n"
#| "If you want to join that team, just click on the following link:\n"
#| "%(url)s\n"
#| "\n"
#| "If you do not want to join, you can safely ignore or delete this email.\n"
#| "\n"
#| "Best regards, \n"
#| "\n"
#| "Your pretix team\n"
msgid ""
"Hello,\n"
"\n"
@@ -20322,7 +20410,7 @@ msgid ""
msgstr ""
"Hallo,\n"
"\n"
"U bent uitgenodigd voor een team op %(instance)s, een platform om tickets\n"
"U bent uitgenodigd voor een team op pretix, een platform om tickets\n"
"te verkopen.\n"
"\n"
"Organisator: %(organizer)s\n"
@@ -20335,7 +20423,7 @@ msgstr ""
"\n"
"Met vriendelijke groet,\n"
"\n"
"Het %(instance)s-team\n"
"Het pretix-team\n"
#: pretix/control/templates/pretixcontrol/email/login_notice.txt:1
#, python-format
@@ -20373,7 +20461,24 @@ msgstr ""
"Het %(instance)s-team\n"
#: pretix/control/templates/pretixcontrol/email/security_notice.txt:1
#, python-format
#, fuzzy, python-format
#| msgid ""
#| "Hello,\n"
#| "\n"
#| "this is to inform you that the account information of your pretix account "
#| "has been\n"
#| "changed. In particular, the following changes have been performed:\n"
#| "\n"
#| "%(messages)s\n"
#| "\n"
#| "If this change was not performed by you, please contact us immediately.\n"
#| "\n"
#| "You can review and change your account settings here:\n"
#| "\n"
#| "%(url)s\n"
#| "\n"
#| "Best regards, \n"
#| "Your pretix team\n"
msgid ""
"Hello,\n"
"\n"
@@ -20394,7 +20499,7 @@ msgid ""
msgstr ""
"Hallo,\n"
"\n"
"U ontvangt deze e-mail omdat er wijzigingen zijn gemaakt in uw %(instance)s-"
"U ontvangt deze e-mail omdat er wijzigingen zijn gemaakt in uw pretix-"
"account.\n"
"De volgende wijzigingen zijn gemaakt:\n"
"\n"
@@ -20408,7 +20513,7 @@ msgstr ""
"%(url)s\n"
"\n"
"Met vriendelijke groet,\n"
"Het %(instance)s-team\n"
"Het pretix-team\n"
#: pretix/control/templates/pretixcontrol/email_setup.html:8
#: pretix/control/templates/pretixcontrol/email_setup_simple.html:8
@@ -20675,8 +20780,10 @@ msgstr ""
"kunt u dit via deze optie doen."
#: pretix/control/templates/pretixcontrol/event/dangerzone.html:51
#, fuzzy
#| msgid "Permissions"
msgid "No permission"
msgstr "Geen toestemming"
msgstr "Machtigingen"
#: pretix/control/templates/pretixcontrol/event/dangerzone.html:59
#: pretix/control/templates/pretixcontrol/event/dangerzone.html:72
@@ -23224,8 +23331,10 @@ msgid "Edit question"
msgstr "Vraag bewerken"
#: pretix/control/templates/pretixcontrol/items/question.html:55
#, fuzzy
#| msgid "You do not have permission to view this content."
msgid "No permission to view answers."
msgstr "U bent niet gemachtigd om de antwoorden te bekijken."
msgstr "U hebt geen toestemming om deze inhoud te bekijken."
#: pretix/control/templates/pretixcontrol/items/question.html:63
msgid "No matching answers found."
@@ -24222,9 +24331,8 @@ msgid ""
"this product was part of the discount calculation for a different product in "
"this order."
msgstr ""
"De prijs van dit product is verlaagd vanwege een automatische korting, of "
"dit product is meegenomen in de kortingsberekening voor een ander product in "
"deze bestelling."
"De prijs van dit product is verminderd door een automatische korting, of dit "
"product is deel van een korting voor een ander product op deze bestelling."
#: pretix/control/templates/pretixcontrol/order/index.html:496
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:103
@@ -24878,8 +24986,10 @@ msgstr "Geen volgende uitvoering gepland"
#: pretix/control/templates/pretixcontrol/orders/export.html:37
#: pretix/control/templates/pretixcontrol/organizers/export.html:37
#, fuzzy
#| msgid "Exporter not found"
msgid "Exporter not found or no permission"
msgstr "Exporteur niet gevonden of geen toestemming"
msgstr "Exporteerder niet gevonden"
#: pretix/control/templates/pretixcontrol/orders/export.html:42
#: pretix/control/templates/pretixcontrol/organizers/export.html:42
@@ -24922,8 +25032,10 @@ msgstr "Aangeraden voor nieuwe gebruikers"
#: pretix/control/templates/pretixcontrol/orders/export.html:120
#: pretix/control/templates/pretixcontrol/organizers/export.html:120
#, fuzzy
#| msgid "There are no add-ons available for this product."
msgid "There are no exporters available for you."
msgstr "Er zijn geen exporters voor u beschikbaar."
msgstr "Er zijn geen add-ons beschikbaar voor dit product."
#: pretix/control/templates/pretixcontrol/orders/export_delete.html:4
#: pretix/control/templates/pretixcontrol/orders/export_delete.html:6
@@ -26861,7 +26973,7 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/pdf/index.html:274
#, python-format
msgid "max. %(size)s, smaller is better"
msgstr "max. %(size)s; hoe kleiner, hoe beter"
msgstr ""
#: pretix/control/templates/pretixcontrol/pdf/index.html:282
msgid "Download current background"
@@ -29639,12 +29751,16 @@ msgstr ""
"Er was een probleem met het verwerken van uw invoer. Zie onder voor details."
#: pretix/control/views/orders.py:2798 pretix/control/views/organizer.py:2131
#, fuzzy
#| msgid ""
#| "Your user account does not have sufficient permission to run this report, "
#| "therefore you cannot schedule it."
msgid ""
"Your user account does not have sufficient permission to run this report, "
"therefore you cannot change it."
msgstr ""
"Uw gebruikersaccount heeft onvoldoende rechten om dit rapport uit te voeren. "
"Daarom kunt u het niet wijzigen."
"Daarom kunt u het niet plannen."
#: pretix/control/views/orders.py:2807 pretix/control/views/organizer.py:2140
msgid ""
@@ -33700,7 +33816,7 @@ msgstr "iDEAL via Stripe"
#: pretix/plugins/stripe/payment.py:1572
msgid "iDEAL | Wero"
msgstr "iDEAL | Wero"
msgstr ""
#: pretix/plugins/stripe/payment.py:1575
msgid ""
@@ -36686,9 +36802,6 @@ msgid ""
"the shop that affect quotas, such as the validity period of carts and "
"vouchers."
msgstr ""
"Let op: de gewijzigde tijd wordt niet meegenomen bij aspecten van de winkel "
"die van invloed zijn op quota, zoals de geldigheidsduur van winkelwagentjes "
"en kortingsbonnen."
#: pretix/presale/templates/pretixpresale/event/timemachine.html:31
msgid "Enable time machine"

View File

@@ -7,7 +7,7 @@ msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-03-17 14:06+0000\n"
"PO-Revision-Date: 2026-03-18 12:23+0000\n"
"PO-Revision-Date: 2026-02-19 22:00+0000\n"
"Last-Translator: Ruud Hendrickx <ruud@leckxicon.eu>\n"
"Language-Team: Dutch <https://translate.pretix.eu/projects/pretix/pretix-js/"
"nl/>\n"
@@ -16,7 +16,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.16.2\n"
"X-Generator: Weblate 5.16\n"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
@@ -59,7 +59,7 @@ msgstr "PayPal - Later betalen"
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:41
msgid "iDEAL | Wero"
msgstr "iDEAL | Wero"
msgstr ""
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:42
msgid "SEPA Direct Debit"

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-03-17 14:06+0000\n"
"PO-Revision-Date: 2026-03-18 14:50+0000\n"
"PO-Revision-Date: 2026-02-21 03:00+0000\n"
"Last-Translator: Ruud Hendrickx <ruud@leckxicon.eu>\n"
"Language-Team: Dutch (Belgium) <https://translate.pretix.eu/projects/pretix/"
"pretix-js/nl_BE/>\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.16.2\n"
"X-Generator: Weblate 5.16\n"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
@@ -60,7 +60,7 @@ msgstr "PayPal - Later betalen"
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:41
msgid "iDEAL | Wero"
msgstr "iDEAL | Wero"
msgstr ""
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:42
msgid "SEPA Direct Debit"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-03-17 14:05+0000\n"
"PO-Revision-Date: 2026-03-18 14:50+0000\n"
"PO-Revision-Date: 2026-03-14 22:00+0000\n"
"Last-Translator: Ruud Hendrickx <ruud@leckxicon.eu>\n"
"Language-Team: Dutch (informal) <https://translate.pretix.eu/projects/pretix/"
"pretix/nl_Informal/>\n"
@@ -410,8 +410,10 @@ msgstr ""
#: pretix/api/serializers/organizer.py:482
#: pretix/control/views/organizer.py:1039
#, fuzzy
#| msgid "Account information"
msgid "Account invitation"
msgstr "Uitnodiging voor account"
msgstr "Accountinformatie"
#: pretix/api/serializers/organizer.py:503
#: pretix/control/views/organizer.py:1138
@@ -3954,8 +3956,10 @@ msgid "Peppol participant ID"
msgstr "Peppol-deelnemers-ID"
#: pretix/base/invoicing/peppol.py:211
#, fuzzy
#| msgid "The Peppol participant ID is not registered on the Peppol network."
msgid "The Peppol participant ID does not match your VAT ID."
msgstr "Het Peppol-deelnemersnummer komt niet overeen met je btw-nummer."
msgstr "De Peppol-deelnemers-ID is niet geregistreerd op het Peppol-netwerk."
#: pretix/base/invoicing/peppol.py:214
msgctxt "peppol_invoice"
@@ -6882,8 +6886,10 @@ msgstr ""
"voordat de instelling voor alle gebruikers van kracht wordt."
#: pretix/base/models/organizer.py:378
#, fuzzy
#| msgid "Event permissions"
msgid "All event permissions"
msgstr "Alle evenementrechten"
msgstr "Evenementspermissies"
#: pretix/base/models/organizer.py:379
#: pretix/control/templates/pretixcontrol/organizers/team_edit.html:34
@@ -6891,8 +6897,10 @@ msgid "Event permissions"
msgstr "Evenementspermissies"
#: pretix/base/models/organizer.py:380
#, fuzzy
#| msgid "Organizer permissions"
msgid "All organizer permissions"
msgstr "Alle organisatorrechten"
msgstr "Organisatorpermissies"
#: pretix/base/models/organizer.py:381
#: pretix/control/templates/pretixcontrol/organizers/team_edit.html:25
@@ -8393,32 +8401,36 @@ msgstr "Uw lay-outbestand is geen geldige lay-out. Foutmelding: {}"
#: pretix/base/permissions.py:314 pretix/base/permissions.py:331
msgctxt "permission_level"
msgid "View"
msgstr "Lezen"
msgstr ""
#: pretix/base/permissions.py:164 pretix/base/permissions.py:169
#: pretix/base/permissions.py:174 pretix/base/permissions.py:179
#: pretix/base/permissions.py:286 pretix/base/permissions.py:315
#, fuzzy
#| msgid "Save and check"
msgctxt "permission_level"
msgid "View and change"
msgstr "Bekijken en wijzigen"
msgstr "Opslaan en controleren"
#: pretix/base/permissions.py:168
#, fuzzy
#| msgid "API tokens"
msgid "API only"
msgstr "alleen API"
msgstr "API-tokens"
#: pretix/base/permissions.py:173
msgid ""
"Menu item will only show up if the user has permission for general settings."
msgstr ""
"Dit menu-item wordt alleen weergegeven als de gebruiker rechten heeft voor "
"algemene instellingen."
#: pretix/base/permissions.py:177 pretix/base/permissions.py:231
#: pretix/base/permissions.py:285 pretix/base/permissions.py:313
#: pretix/base/permissions.py:330
#, fuzzy
#| msgid "Revoke access"
msgctxt "permission_level"
msgid "No access"
msgstr "Geen toegang"
msgstr "Toegang intrekken"
#: pretix/base/permissions.py:188
#: pretix/control/templates/pretixcontrol/event/settings.html:7
@@ -8432,8 +8444,6 @@ msgid ""
"This includes access to all settings not listed explicitly below, including "
"plugin settings."
msgstr ""
"Dit omvat toegang tot alle instellingen die hieronder niet expliciet worden "
"vermeld, inclusief instellingen voor plug-ins."
#: pretix/base/permissions.py:197
#: pretix/control/templates/pretixcontrol/event/payment.html:6
@@ -8447,81 +8457,100 @@ msgid "Tax settings"
msgstr "Belastinginstellingen"
#: pretix/base/permissions.py:209
#, fuzzy
#| msgid "Invoice settings"
msgid "Invoicing settings"
msgstr "Factureringsinstellingen"
msgstr "Factuurinstellingen"
#: pretix/base/permissions.py:215
#, fuzzy
#| msgctxt "subevent"
#| msgid "Event series date added"
msgid "Event series dates"
msgstr "Datums van de evenementenreeks"
msgstr "Evenementenreeks: datum toegevoegd"
#: pretix/base/permissions.py:221
#, fuzzy
#| msgid "Product name and variation"
msgid "Products, quotas and questions"
msgstr "Producten, quota en vragen"
msgstr "Productnaam en variant"
#: pretix/base/permissions.py:224
msgid "Also includes related objects like categories or discounts."
msgstr "Omvat ook gerelateerde objecten zoals categorieën of kortingen."
msgstr ""
#: pretix/base/permissions.py:232
#, fuzzy
#| msgid "All check-ins"
msgctxt "permission_level"
msgid "Only check-in"
msgstr "Alleen inchecken"
msgstr "Alle check-ins"
#: pretix/base/permissions.py:233
#, fuzzy
#| msgid "View full log"
msgctxt "permission_level"
msgid "View all"
msgstr "Alles bekijken"
msgstr "Toon volledige log"
#: pretix/base/permissions.py:234
#, fuzzy
#| msgid "Valid check-in"
msgctxt "permission_level"
msgid "View all and check-in"
msgstr "Alles bekijken en inchecken"
msgstr "Geldige check-in"
#: pretix/base/permissions.py:235
#, fuzzy
#| msgid "View all upcoming events"
msgctxt "permission_level"
msgid "View all and change"
msgstr "Alles bekijken en wijzigen"
msgstr "Bekijk alle aankomende evenementen"
#: pretix/base/permissions.py:236
msgid "Includes the ability to cancel and refund individual orders."
msgstr ""
"Biedt de mogelijkheid om afzonderlijke bestellingen te annuleren en terug te "
"betalen."
#: pretix/base/permissions.py:238
#, fuzzy
#| msgid "An entry has been added to the waiting list."
msgid "Also includes related objects like the waiting list."
msgstr "Bevat ook aanverwante objecten, zoals de wachtlijst."
msgstr "Een inschrijving is toegevoegd aan de wachtlijst."
#: pretix/base/permissions.py:248
#, fuzzy
#| msgid "Generate cancellation"
msgid "Full event or date cancellation"
msgstr "Volledige annulering van het evenement of de datum"
msgstr "Genereer annulering"
#: pretix/base/permissions.py:252
#, fuzzy
#| msgid "Sale not allowed"
msgctxt "permission_level"
msgid "Not allowed"
msgstr "Niet toegestaan"
msgstr "Verkoop niet toegestaan"
#: pretix/base/permissions.py:253
#, fuzzy
#| msgid "Allowed titles"
msgctxt "permission_level"
msgid "Allowed"
msgstr "Toegestaan"
msgstr "Te kiezen titels"
#: pretix/base/permissions.py:268
msgctxt "permission_level"
msgid "Access existing events"
msgstr "Toegang tot bestaande evenementen"
msgstr ""
#: pretix/base/permissions.py:269
msgctxt "permission_level"
msgid "Access existing and create new events"
msgstr "Toegang tot bestaande evenementen en nieuwe evenementen aanmaken"
msgstr ""
#: pretix/base/permissions.py:271
msgid ""
"The level of access to events is determined in detail by the settings below."
msgstr ""
"De toegang tot evenementen wordt in detail bepaald door de onderstaande "
"instellingen."
#: pretix/base/permissions.py:275 pretix/control/navigation.py:143
#: pretix/control/navigation.py:462 pretix/control/navigation.py:512
@@ -8540,15 +8569,12 @@ msgid ""
"This includes access to all organizer-level functionality not listed "
"explicitly below, including plugin settings."
msgstr ""
"Dit omvat toegang tot alle functies op organisatieniveau die hieronder niet "
"expliciet worden vermeld, inclusief de instellingen van plug-ins."
#: pretix/base/permissions.py:287
msgid ""
"Includes the ability to give someone (including oneself) additional "
"permissions."
msgstr ""
"Biedt de mogelijkheid om iemand (inclusief jezelf) extra rechten te geven."
#: pretix/base/permissions.py:298 pretix/control/navigation.py:608
#: pretix/control/templates/pretixcontrol/organizers/customers.html:6
@@ -8566,12 +8592,12 @@ msgid ""
"Includes the ability to give access to events and data oneself does not have "
"access to."
msgstr ""
"Biedt de mogelijkheid om toegang te verlenen tot evenementen en gegevens "
"waar je zelf geen toegang toe hebt."
#: pretix/base/permissions.py:321
#, fuzzy
#| msgid "Seating plan"
msgid "Seating plans"
msgstr "Zaalplannen"
msgstr "Zitplan"
#: pretix/base/permissions.py:327 pretix/control/navigation.py:712
#: pretix/control/templates/pretixcontrol/organizers/outgoing_mails.html:8
@@ -9207,12 +9233,12 @@ msgid "Czech National Bank"
msgstr "Tsjechische Nationale Bank"
#: pretix/base/services/export.py:94 pretix/base/services/export.py:154
#, fuzzy
#| msgid "You do not have sufficient permission to perform this export."
msgid ""
"Export not found or you do not have sufficient permission to perform this "
"export."
msgstr ""
"De export is niet gevonden of je hebt onvoldoende rechten om deze export uit "
"te voeren."
msgstr "Je hebt te weinig rechten om deze export uit te voeren."
#: pretix/base/services/export.py:107 pretix/base/services/export.py:179
#: pretix/base/services/export.py:357
@@ -13502,7 +13528,7 @@ msgstr ""
#: pretix/base/templates/403.html:4 pretix/base/templates/403.html:8
msgid "Permission denied"
msgstr "Toestemming geweigerd"
msgstr "Geen toestemming"
#: pretix/base/templates/403.html:9
msgid "You do not have access to this page."
@@ -13691,7 +13717,25 @@ msgid "Contact"
msgstr "Contact"
#: pretix/base/templates/pretixbase/email/shred_completed.txt:2
#, python-format
#, fuzzy, python-format
#| msgid ""
#| "Hello,\n"
#| "\n"
#| "we hereby confirm that the following data shredding job has been "
#| "completed:\n"
#| "\n"
#| "Organizer: %(organizer)s\n"
#| "\n"
#| "Event: %(event)s\n"
#| "\n"
#| "Data selection: %(shredders)s\n"
#| "\n"
#| "Start time: %(start_time)s (new data added after this time might not have "
#| "been deleted)\n"
#| "\n"
#| "Best regards,\n"
#| "\n"
#| "Your pretix team\n"
msgid ""
"Hello,\n"
"\n"
@@ -13725,7 +13769,7 @@ msgstr ""
"\n"
"Met vriendelijke groet,\n"
"\n"
"Het %(instance)s-team\n"
"Het pretix-team\n"
#: pretix/base/templates/pretixbase/forms/widgets/checkbox_sales_channel_option.html:16
msgid ""
@@ -14288,12 +14332,16 @@ msgid "This is an event series"
msgstr "Dit is een evenementenreeks"
#: pretix/control/forms/event.py:135 pretix/control/forms/event.py:363
#, fuzzy
#| msgid ""
#| "You do not have sufficient permission to enable plugins that need to be "
#| "enabled for the entire organizer account."
msgid ""
"You do not have a sufficient level of access on the event you selected to "
"copy it to the desired organizer."
msgstr ""
"Je hebt onvoldoende rechten voor het evenement dat je hebt geselecteerd om "
"het naar de gewenste organisator te kopiëren."
"Je hebt niet voldoende rechten om plug-ins in te schakelen die voor het hele "
"account van de organisator moeten worden ingeschakeld."
#: pretix/control/forms/event.py:143
msgid ""
@@ -14372,8 +14420,6 @@ msgid ""
"You cannot choose a team that would give you more access than you have on "
"the event you are copying."
msgstr ""
"Je kunt geen team selecteren dat je meer toegangsrechten zou geven dan het "
"evenement dat je kopieert."
#: pretix/control/forms/event.py:344
msgid "Copy configuration from"
@@ -14389,8 +14435,6 @@ msgid ""
"You cannot choose an event on which you have less access than the team you "
"selected in the previous step."
msgstr ""
"Je kunt geen evenement selecteren waarvoor je minder toegangsrechten hebt "
"dan het team dat je in de vorige stap geselecteerd hebt."
#: pretix/control/forms/event.py:387 pretix/control/forms/item.py:1304
#: pretix/control/forms/subevents.py:411
@@ -15346,7 +15390,7 @@ msgstr "Zoek gast…"
#: pretix/control/forms/filter.py:2039
#: pretix/plugins/checkinlists/exporters.py:106
msgid "Check-in status"
msgstr "Check-in-status"
msgstr "Incheckstatus"
#: pretix/control/forms/filter.py:2041
#: pretix/plugins/checkinlists/exporters.py:108
@@ -16755,7 +16799,7 @@ msgstr "Je kunt slechts één organisatiedomein instellen."
#: pretix/control/forms/organizer.py:322
msgid "Provided by a plugin"
msgstr "Beschikbaar gesteld via een plug-in"
msgstr ""
#: pretix/control/forms/organizer.py:438
msgid ""
@@ -17634,8 +17678,10 @@ msgid "The order has been denied (comment: \"{comment}\")."
msgstr "De bestelling is geweigerd (opmerking: \"{comment}\")."
#: pretix/control/logdisplay.py:521
#, fuzzy
#| msgid "The order has been overpaid."
msgid "The customer VAT ID has been verified."
msgstr "Het btw-nummer van de klant is geverifieerd."
msgstr "De bestelling is overbetaald."
#: pretix/control/logdisplay.py:522
#, python-brace-format
@@ -17937,9 +17983,10 @@ msgid "{user} has been invited to the team."
msgstr "{user} is uitgenodigd voor het team."
#: pretix/control/logdisplay.py:644
#, python-brace-format
#, fuzzy, python-brace-format
#| msgid "Invite for {user} has been resent."
msgid "Invite for {user} has been deleted."
msgstr "De uitnodiging voor {user} is verwijderd."
msgstr "De uitnodiging voor {user} is opnieuw verstuurd."
#: pretix/control/logdisplay.py:645
#, python-brace-format
@@ -20248,7 +20295,21 @@ msgid "Add property"
msgstr "Eigenschap toevoegen"
#: pretix/control/templates/pretixcontrol/email/confirmation_code.txt:1
#, python-format
#, fuzzy, python-format
#| msgid ""
#| "Hello,\n"
#| "\n"
#| "%(reason)s\n"
#| "\n"
#| " %(code)s\n"
#| "\n"
#| "Please do never give this code to another person. Our support team will "
#| "never ask for this code.\n"
#| "\n"
#| "If this code was not requested by you, please contact us immediately.\n"
#| "\n"
#| "Best regards,\n"
#| "Your pretix team\n"
msgid ""
"Hello,\n"
"\n"
@@ -20277,7 +20338,7 @@ msgstr ""
"contact op.\n"
"\n"
"Met vriendelijke groet,\n"
"Het %(instance)s-team\n"
"Het pretix-team\n"
#: pretix/control/templates/pretixcontrol/email/email_setup.txt:1
#, python-format
@@ -20314,7 +20375,17 @@ msgstr ""
"Het %(instance)s-team\n"
#: pretix/control/templates/pretixcontrol/email/forgot.txt:1
#, python-format
#, fuzzy, python-format
#| msgid ""
#| "Hello,\n"
#| "\n"
#| "you requested a new password. Please go to the following page to reset "
#| "your password:\n"
#| "\n"
#| "%(url)s\n"
#| "\n"
#| "Best regards, \n"
#| "Your pretix team\n"
msgid ""
"Hello,\n"
"\n"
@@ -20329,15 +20400,33 @@ msgstr ""
"Hallo,\n"
"\n"
"Je hebt een nieuw wachtwoord aangevraagd. Klik op de volgende link om je "
"wachtwoord opnieuw in te stellen:\n"
"wachtwoord opnieuw\n"
"in te stellen:\n"
"\n"
"%(url)s\n"
"\n"
"Met vriendelijke groet,\n"
"Het %(instance)s-team\n"
"Het pretix-team\n"
#: pretix/control/templates/pretixcontrol/email/invitation.txt:1
#, python-format
#, fuzzy, python-format
#| msgid ""
#| "Hello,\n"
#| "\n"
#| "you have been invited to a team on pretix, a platform to perform event\n"
#| "ticket sales.\n"
#| "\n"
#| "Organizer: %(organizer)s\n"
#| "Team: %(team)s\n"
#| "\n"
#| "If you want to join that team, just click on the following link:\n"
#| "%(url)s\n"
#| "\n"
#| "If you do not want to join, you can safely ignore or delete this email.\n"
#| "\n"
#| "Best regards, \n"
#| "\n"
#| "Your pretix team\n"
msgid ""
"Hello,\n"
"\n"
@@ -20359,7 +20448,7 @@ msgid ""
msgstr ""
"Hallo,\n"
"\n"
"Je bent uitgenodigd voor een team op %(instance)s, een platform om kaartjes\n"
"Je bent uitgenodigd voor een team op pretix, een platform om kaartjes\n"
"te verkopen.\n"
"\n"
"Organisator: %(organizer)s\n"
@@ -20372,7 +20461,7 @@ msgstr ""
"\n"
"Met vriendelijke groet,\n"
"\n"
"Het %(instance)s-team\n"
"Het pretix-team\n"
#: pretix/control/templates/pretixcontrol/email/login_notice.txt:1
#, python-format
@@ -20409,7 +20498,24 @@ msgstr ""
"Het team van %(instance)s\n"
#: pretix/control/templates/pretixcontrol/email/security_notice.txt:1
#, python-format
#, fuzzy, python-format
#| msgid ""
#| "Hello,\n"
#| "\n"
#| "this is to inform you that the account information of your pretix account "
#| "has been\n"
#| "changed. In particular, the following changes have been performed:\n"
#| "\n"
#| "%(messages)s\n"
#| "\n"
#| "If this change was not performed by you, please contact us immediately.\n"
#| "\n"
#| "You can review and change your account settings here:\n"
#| "\n"
#| "%(url)s\n"
#| "\n"
#| "Best regards, \n"
#| "Your pretix team\n"
msgid ""
"Hello,\n"
"\n"
@@ -20430,7 +20536,7 @@ msgid ""
msgstr ""
"Hallo,\n"
"\n"
"Je ontvangt deze e-mail omdat er wijzigingen zijn gemaakt in je %(instance)s-"
"Je ontvangt deze e-mail omdat er wijzigingen zijn gemaakt in je pretix-"
"account.\n"
"De volgende wijzigingen zijn gemaakt:\n"
"\n"
@@ -20444,7 +20550,7 @@ msgstr ""
"%(url)s\n"
"\n"
"Met vriendelijke groet,\n"
"Het %(instance)s-team\n"
"Het pretix-team\n"
#: pretix/control/templates/pretixcontrol/email_setup.html:8
#: pretix/control/templates/pretixcontrol/email_setup_simple.html:8
@@ -20715,8 +20821,10 @@ msgstr ""
"dat met deze optie doen."
#: pretix/control/templates/pretixcontrol/event/dangerzone.html:51
#, fuzzy
#| msgid "Permissions"
msgid "No permission"
msgstr "Geen toestemming"
msgstr "Permissies"
#: pretix/control/templates/pretixcontrol/event/dangerzone.html:59
#: pretix/control/templates/pretixcontrol/event/dangerzone.html:72
@@ -23267,8 +23375,10 @@ msgid "Edit question"
msgstr "Vraag bewerken"
#: pretix/control/templates/pretixcontrol/items/question.html:55
#, fuzzy
#| msgid "You do not have permission to view this content."
msgid "No permission to view answers."
msgstr "Je bent niet gemachtigd om de antwoorden te bekijken."
msgstr "Je hebt geen toestemming om deze inhoud te bekijken."
#: pretix/control/templates/pretixcontrol/items/question.html:63
msgid "No matching answers found."
@@ -24267,8 +24377,8 @@ msgid ""
"this order."
msgstr ""
"De prijs van dit product is verlaagd vanwege een automatische korting, of "
"dit product is meegenomen in de kortingsberekening voor een ander product in "
"deze bestelling."
"dit product maakte deel uit van de kortingsberekening voor een ander product "
"in deze bestelling."
#: pretix/control/templates/pretixcontrol/order/index.html:496
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:103
@@ -24924,8 +25034,10 @@ msgstr "Geen volgende run gepland"
#: pretix/control/templates/pretixcontrol/orders/export.html:37
#: pretix/control/templates/pretixcontrol/organizers/export.html:37
#, fuzzy
#| msgid "Exporter not found"
msgid "Exporter not found or no permission"
msgstr "Exporteur niet gevonden of geen toestemming"
msgstr "Exporteerder niet gevonden"
#: pretix/control/templates/pretixcontrol/orders/export.html:42
#: pretix/control/templates/pretixcontrol/organizers/export.html:42
@@ -24968,8 +25080,10 @@ msgstr "Aanbevolen voor nieuwe gebruikers"
#: pretix/control/templates/pretixcontrol/orders/export.html:120
#: pretix/control/templates/pretixcontrol/organizers/export.html:120
#, fuzzy
#| msgid "There are no add-ons available for this product."
msgid "There are no exporters available for you."
msgstr "Er zijn geen exporters voor jou beschikbaar."
msgstr "Er zijn geen add-ons beschikbaar voor dit product."
#: pretix/control/templates/pretixcontrol/orders/export_delete.html:4
#: pretix/control/templates/pretixcontrol/orders/export_delete.html:6
@@ -26915,7 +27029,7 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/pdf/index.html:274
#, python-format
msgid "max. %(size)s, smaller is better"
msgstr "max. %(size)s; hoe kleiner, hoe beter"
msgstr ""
#: pretix/control/templates/pretixcontrol/pdf/index.html:282
msgid "Download current background"
@@ -29698,12 +29812,16 @@ msgstr ""
"Er was een probleem met het verwerken van je invoer. Zie onder voor details."
#: pretix/control/views/orders.py:2798 pretix/control/views/organizer.py:2131
#, fuzzy
#| msgid ""
#| "Your user account does not have sufficient permission to run this report, "
#| "therefore you cannot schedule it."
msgid ""
"Your user account does not have sufficient permission to run this report, "
"therefore you cannot change it."
msgstr ""
"Je gebruikersaccount heeft onvoldoende rechten om dit rapport uit te voeren. "
"Daarom kun je het niet wijzigen."
"Je gebruikersaccount heeft onvoldoende rechten om dit rapport uit te voeren, "
"daarom kun je het niet plannen."
#: pretix/control/views/orders.py:2807 pretix/control/views/organizer.py:2140
msgid ""
@@ -33754,7 +33872,7 @@ msgstr "iDEAL via Stripe"
#: pretix/plugins/stripe/payment.py:1572
msgid "iDEAL | Wero"
msgstr "iDEAL | Wero"
msgstr ""
#: pretix/plugins/stripe/payment.py:1575
msgid ""
@@ -36740,9 +36858,6 @@ msgid ""
"the shop that affect quotas, such as the validity period of carts and "
"vouchers."
msgstr ""
"Let op: de gewijzigde tijd wordt niet meegenomen bij aspecten van de winkel "
"die van invloed zijn op quota, zoals de geldigheidsduur van winkelwagentjes "
"en kortingsbonnen."
#: pretix/presale/templates/pretixpresale/event/timemachine.html:31
msgid "Enable time machine"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-03-17 14:06+0000\n"
"PO-Revision-Date: 2026-03-18 14:50+0000\n"
"PO-Revision-Date: 2026-01-29 19:43+0000\n"
"Last-Translator: Ruud Hendrickx <ruud@leckxicon.eu>\n"
"Language-Team: Dutch (informal) <https://translate.pretix.eu/projects/pretix/"
"pretix-js/nl_Informal/>\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.16.2\n"
"X-Generator: Weblate 5.15.2\n"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
@@ -60,7 +60,7 @@ msgstr "PayPal Later betalen"
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:41
msgid "iDEAL | Wero"
msgstr "iDEAL | Wero"
msgstr ""
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:42
msgid "SEPA Direct Debit"

View File

@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-03-17 14:05+0000\n"
"PO-Revision-Date: 2026-03-25 08:00+0000\n"
"Last-Translator: Renne Rocha <renne@rocha.dev.br>\n"
"PO-Revision-Date: 2026-03-11 07:08+0000\n"
"Last-Translator: Pedro Orlando <scramblerdoodle@gmail.com>\n"
"Language-Team: Portuguese (Brazil) <https://translate.pretix.eu/projects/"
"pretix/pretix/pt_BR/>\n"
"Language: pt_BR\n"
@@ -405,8 +405,10 @@ msgstr ""
#: pretix/api/serializers/organizer.py:482
#: pretix/control/views/organizer.py:1039
#, fuzzy
#| msgid "Account information"
msgid "Account invitation"
msgstr "Convite de conta"
msgstr "Informações da conta"
#: pretix/api/serializers/organizer.py:503
#: pretix/control/views/organizer.py:1138
@@ -6838,8 +6840,10 @@ msgstr ""
"alguns minutos para entrar em vigor para todos os usuários."
#: pretix/base/models/organizer.py:378
#, fuzzy
#| msgid "Event permissions"
msgid "All event permissions"
msgstr "Todas as permissões de evento"
msgstr "Permissões de Evento"
#: pretix/base/models/organizer.py:379
#: pretix/control/templates/pretixcontrol/organizers/team_edit.html:34
@@ -6847,8 +6851,10 @@ msgid "Event permissions"
msgstr "Permissões de Evento"
#: pretix/base/models/organizer.py:380
#, fuzzy
#| msgid "Organizer permissions"
msgid "All organizer permissions"
msgstr "Todas as permissões de organizador"
msgstr "Permissões de Organizador"
#: pretix/base/models/organizer.py:381
#: pretix/control/templates/pretixcontrol/organizers/team_edit.html:25
@@ -8346,18 +8352,22 @@ msgstr "Seu arquivo de layout não é um layout válido. Mensagem de erro: {}"
#: pretix/base/permissions.py:314 pretix/base/permissions.py:331
msgctxt "permission_level"
msgid "View"
msgstr "Visualizar"
msgstr ""
#: pretix/base/permissions.py:164 pretix/base/permissions.py:169
#: pretix/base/permissions.py:174 pretix/base/permissions.py:179
#: pretix/base/permissions.py:286 pretix/base/permissions.py:315
#, fuzzy
#| msgid "Save and check"
msgctxt "permission_level"
msgid "View and change"
msgstr "Visualizar e alterar"
msgstr "Salvar e checar"
#: pretix/base/permissions.py:168
#, fuzzy
#| msgid "API tokens"
msgid "API only"
msgstr "Apenas API"
msgstr "Tokens de API"
#: pretix/base/permissions.py:173
msgid ""
@@ -8367,9 +8377,11 @@ msgstr ""
#: pretix/base/permissions.py:177 pretix/base/permissions.py:231
#: pretix/base/permissions.py:285 pretix/base/permissions.py:313
#: pretix/base/permissions.py:330
#, fuzzy
#| msgid "Revoke access"
msgctxt "permission_level"
msgid "No access"
msgstr "Sem acesso"
msgstr "Revogar acesso"
#: pretix/base/permissions.py:188
#: pretix/control/templates/pretixcontrol/event/settings.html:7
@@ -8396,8 +8408,10 @@ msgid "Tax settings"
msgstr "Configurações de impostos"
#: pretix/base/permissions.py:209
#, fuzzy
#| msgid "Invoice settings"
msgid "Invoicing settings"
msgstr "Configurações de faturamento"
msgstr "Configurações de fatura"
#: pretix/base/permissions.py:215
#, fuzzy
@@ -8407,40 +8421,52 @@ msgid "Event series dates"
msgstr "Data da série de eventos adicionada"
#: pretix/base/permissions.py:221
#, fuzzy
#| msgid "Product name and variation"
msgid "Products, quotas and questions"
msgstr "Produtos, cotas e perguntas"
msgstr "Nome do produto e variação"
#: pretix/base/permissions.py:224
msgid "Also includes related objects like categories or discounts."
msgstr ""
#: pretix/base/permissions.py:232
#, fuzzy
#| msgid "All check-ins"
msgctxt "permission_level"
msgid "Only check-in"
msgstr "Apenas check-in"
msgstr "Todos os check-ins"
#: pretix/base/permissions.py:233
#, fuzzy
#| msgid "View full log"
msgctxt "permission_level"
msgid "View all"
msgstr "Visualizar tudo"
msgstr "Ver log completo"
#: pretix/base/permissions.py:234
#, fuzzy
#| msgid "Valid check-in"
msgctxt "permission_level"
msgid "View all and check-in"
msgstr "Visualizar tudo e check-in"
msgstr "Check-in válido"
#: pretix/base/permissions.py:235
#, fuzzy
#| msgid "View all upcoming events"
msgctxt "permission_level"
msgid "View all and change"
msgstr "Visualizar tudo e alterar"
msgstr "Ver todos eventos futuros"
#: pretix/base/permissions.py:236
msgid "Includes the ability to cancel and refund individual orders."
msgstr "Incluir a habilidade de cancelar e reembolsar pedidos individuais."
msgstr ""
#: pretix/base/permissions.py:238
#, fuzzy
#| msgid "An entry has been added to the waiting list."
msgid "Also includes related objects like the waiting list."
msgstr "Também inclui objetos relacionados como a lista de espera."
msgstr "Uma entrada foi adicionada à lista de espera."
#: pretix/base/permissions.py:248
#, fuzzy
@@ -8449,14 +8475,18 @@ msgid "Full event or date cancellation"
msgstr "Gerar cancelamento"
#: pretix/base/permissions.py:252
#, fuzzy
#| msgid "Sale not allowed"
msgctxt "permission_level"
msgid "Not allowed"
msgstr "Não permitida"
msgstr "Venda não permitida"
#: pretix/base/permissions.py:253
#, fuzzy
#| msgid "Allowed titles"
msgctxt "permission_level"
msgid "Allowed"
msgstr "Permitida"
msgstr "Títulos permitidos"
#: pretix/base/permissions.py:268
msgctxt "permission_level"
@@ -23965,7 +23995,7 @@ msgstr "Reembolso por pagamento em excesso"
#: pretix/control/templates/pretixcontrol/order/index.html:118
#, python-format
msgid "This order is currently overpaid by %(amount)s."
msgstr "Este pedido está pago em excesso por %(amount)s."
msgstr "O pedidos está pago em excesso por %(amount)s."
#: pretix/control/templates/pretixcontrol/order/index.html:122
#, python-format
@@ -33264,7 +33294,7 @@ msgstr "iDEAL via Stripe"
#: pretix/plugins/stripe/payment.py:1572
msgid "iDEAL | Wero"
msgstr "iDEAL | Wero"
msgstr ""
#: pretix/plugins/stripe/payment.py:1575
msgid ""

View File

@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-03-17 14:06+0000\n"
"PO-Revision-Date: 2026-03-25 08:00+0000\n"
"PO-Revision-Date: 2026-01-26 22:00+0000\n"
"Last-Translator: Renne Rocha <renne@rocha.dev.br>\n"
"Language-Team: Portuguese (Brazil) <https://translate.pretix.eu/projects/"
"pretix/pretix-js/pt_BR/>\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 5.16.2\n"
"X-Generator: Weblate 5.15.2\n"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
@@ -60,7 +60,7 @@ msgstr "PayPal Pay Later"
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:41
msgid "iDEAL | Wero"
msgstr "iDEAL | Wero"
msgstr ""
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:42
msgid "SEPA Direct Debit"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-03-17 14:05+0000\n"
"PO-Revision-Date: 2026-03-27 09:03+0000\n"
"PO-Revision-Date: 2026-01-16 22:00+0000\n"
"Last-Translator: Linnea Thelander <linnea@coeo.events>\n"
"Language-Team: Swedish <https://translate.pretix.eu/projects/pretix/pretix/"
"sv/>\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.16.2\n"
"X-Generator: Weblate 5.15.2\n"
#: pretix/_base_settings.py:87
msgid "English"
@@ -35279,8 +35279,10 @@ msgid "We're now trying to book these add-ons for you!"
msgstr "Vi försöker nu reservera dessa tillval till dig!"
#: pretix/presale/templates/pretixpresale/event/checkout_addons.html:28
#, fuzzy
#| msgid "Additional settings"
msgid "Additional options for"
msgstr "Ytterligare val för"
msgstr "Ytterligare inställningar"
#: pretix/presale/templates/pretixpresale/event/checkout_addons.html:60
#, fuzzy
@@ -35626,10 +35628,13 @@ msgstr[0] "Du måste välja exakt ett alternativ från denna kategori."
msgstr[1] "Du måste välja %(min_count)s alternativ från denna kategori."
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html:26
#, python-format
#, fuzzy, python-format
#| msgid "You can choose %(max_count)s option from this category."
#| msgid_plural ""
#| "You can choose up to %(max_count)s options from this category."
msgid "You can choose one option from this category."
msgid_plural "You can choose up to %(max_count)s options from this category."
msgstr[0] "Du kan välja ett alternativ från denna kategori."
msgstr[0] "Du kan välja %(max_count)s alternativ från denna kategori."
msgstr[1] "Du kan välja upp till %(max_count)s alternativ från denna kategori."
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html:34
@@ -36324,11 +36329,12 @@ msgstr "Visa fullstor bild av %(item)s"
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html:131
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html:288
#, python-format
#, fuzzy, python-format
#| msgid "%(count)s elements"
msgid "%(amount)s× in your cart"
msgid_plural "%(amount)s× in your cart"
msgstr[0] "%(amount)sx i din kundvagn"
msgstr[1] "%(amount)sx i din kundvagn"
msgstr[0] "%(count)s element"
msgstr[1] "%(count)s element"
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html:209
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html:374

View File

@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-03-17 14:06+0000\n"
"PO-Revision-Date: 2026-03-26 14:29+0000\n"
"PO-Revision-Date: 2025-10-10 17:00+0000\n"
"Last-Translator: Linnea Thelander <linnea@coeo.events>\n"
"Language-Team: Swedish <https://translate.pretix.eu/projects/pretix/pretix-"
"js/sv/>\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.16.2\n"
"X-Generator: Weblate 5.13.3\n"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
@@ -1023,6 +1023,7 @@ msgid "Waiting list"
msgstr "Väntelista"
#: pretix/static/pretixpresale/js/widget/widget.js:55
#, fuzzy
msgctxt "widget"
msgid ""
"You currently have an active cart for this event. If you select more "

View File

@@ -503,7 +503,7 @@ class CSVCheckinList(CheckInListMixin, ListExporter):
if cl.include_pending:
headers.append(_('Paid'))
if form_data.get('secrets', False):
if form_data['secrets']:
headers.append(_('Secret'))
headers.append(_('Email'))
@@ -603,7 +603,7 @@ class CSVCheckinList(CheckInListMixin, ListExporter):
]
if cl.include_pending:
row.append(_('Yes') if op.order.status == Order.STATUS_PAID else _('No'))
if form_data.get('secrets', False):
if form_data['secrets']:
row.append(op.secret)
row.append(op.attendee_email or (op.addon_to.attendee_email if op.addon_to else '') or op.order.email or '')
row.append(str(op.order.phone) if op.order.phone else '')

View File

@@ -0,0 +1,271 @@
import type { I18nString, SubEvent } from './i18n'
const settingsEl = document.getElementById('api-settings')
const { urls } = JSON.parse(settingsEl.textContent || '{}') as { urls: {
lists: string
questions: string
} }
// interfaces generated from api docs
export interface PaginatedResponse<T> {
count: number
next: string | null
previous: string | null
results: T[]
}
export interface CheckinList {
id: number
name: string
all_products: boolean
limit_products: number[]
subevent: SubEvent | null
position_count?: number
checkin_count?: number
include_pending: boolean
allow_multiple_entries: boolean
allow_entry_after_exit: boolean
rules: Record<string, unknown>
exit_all_at: string | null
addon_match: boolean
ignore_in_statistics?: boolean
consider_tickets_used?: boolean
}
export interface Checkin {
id: number
list: number
datetime: string
type: 'entry' | 'exit'
gate: number | null
device: number | null
device_id: number | null
auto_checked_in: boolean
}
export interface Seat {
id: number
name: string
zone_name: string
row_name: string
row_label: string | null
seat_number: string
seat_label: string | null
seat_guid: string
}
export interface Position {
id: number
order: string
positionid: number
canceled?: boolean
item: { id?: number; name: I18nString; internal_name?: string; admission?: boolean }
variation: { id?: number; value: I18nString } | null
price: string
attendee_name: string
attendee_name_parts: Record<string, string>
attendee_email: string | null
company?: string | null
street?: string | null
zipcode?: string | null
city?: string | null
country?: string | null
state?: string | null
voucher?: number | null
voucher_budget_use?: string | null
tax_rate: string
tax_value: string
tax_code?: string | null
tax_rule: number | null
secret: string
addon_to: number | null
subevent: SubEvent | null
discount?: number | null
blocked: string[] | null
valid_from: string | null
valid_until: string | null
pseudonymization_id: string
seat: Seat | null
checkins: Checkin[]
downloads?: { output: string; url: string }[]
answers: Answer[]
pdf_data?: Record<string, unknown>
plugin_data?: Record<string, unknown>
// Additional fields from checkin list positions endpoint
order__status?: string
order__valid_if_pending?: boolean
order__require_approval?: boolean
order__locale?: string
require_attention?: boolean
addons?: Addon[]
}
export interface Answer {
question: number | AnswerQuestion
answer: string
question_identifier: string
options: number[]
option_identifiers: string[]
}
export interface AnswerQuestion {
id: number
question: I18nString
help_text?: I18nString
type: string
required: boolean
position: number
items: number[]
identifier: string
ask_during_checkin: boolean
show_during_checkin: boolean
hidden?: boolean
print_on_invoice?: boolean
options: QuestionOption[]
valid_number_min?: string | null
valid_number_max?: string | null
valid_date_min?: string | null
valid_date_max?: string | null
valid_datetime_min?: string | null
valid_datetime_max?: string | null
valid_file_portrait?: boolean
valid_string_length_max?: number | null
dependency_question?: number | null
dependency_values?: string[]
}
export interface QuestionOption {
id: number
identifier: string
position: number
answer: I18nString
}
export interface Addon {
item: { name: I18nString; internal_name?: string }
variation: { value: I18nString } | null
}
export interface CheckinStatusVariation {
id: number
value: string
checkin_count: number
position_count: number
}
export interface CheckinStatusItem {
id: number
name: string
checkin_count: number
admission: boolean
position_count: number
variations: CheckinStatusVariation[]
}
export interface CheckinStatus {
checkin_count: number
position_count: number
inside_count: number
event?: { name: string }
items?: CheckinStatusItem[]
}
export interface RedeemRequest {
questions_supported: boolean
canceled_supported: boolean
ignore_unpaid: boolean
type: 'entry' | 'exit'
answers: Record<string, string>
datetime?: string | null
force?: boolean
nonce?: string
}
export interface RedeemResponseList {
id: number
name: string
event: string
subevent: number | null
include_pending: boolean
}
export interface RedeemResponse {
status: 'ok' | 'error' | 'incomplete'
reason?: 'invalid' | 'unpaid' | 'blocked' | 'invalid_time' | 'canceled' | 'already_redeemed' | 'product' | 'rules' | 'ambiguous' | 'revoked' | 'unapproved' | 'error'
reason_explanation?: string | null
position?: Position
questions?: AnswerQuestion[]
checkin_texts?: string[]
require_attention?: boolean
list?: RedeemResponseList
}
const CSRF_TOKEN = document.querySelector<HTMLInputElement>('input[name=csrfmiddlewaretoken]')?.value ?? ''
function handleAuthError (response: Response): void {
if ([401, 403].includes(response.status)) {
window.location.href = '/control/login?next=' + encodeURIComponent(
window.location.pathname + window.location.search + window.location.hash
)
}
}
export const api = {
// generic fetch wrapper, not sure if this should be exposed
async fetch <T> (url: string, options?: RequestInit): Promise<T> {
const response = await fetch(url, options)
handleAuthError(response)
if (!response.ok && response.status !== 400 && response.status !== 404) {
throw new Error('HTTP status ' + response.status)
}
return response.json()
},
async fetchCheckinLists (endsAfter?: string): Promise<PaginatedResponse<CheckinList>> {
const cutoff = endsAfter ?? moment().subtract(8, 'hours').toISOString()
const url = `${urls.lists}?exclude=checkin_count&exclude=position_count&expand=subevent&ends_after=${cutoff}`
return api.fetch(url)
},
async fetchCheckinList (listId: string): Promise<CheckinList> {
return api.fetch(`${urls.lists}${listId}/?expand=subevent`)
},
async fetchNextPage<T> (nextUrl: string): Promise<PaginatedResponse<T>> {
return api.fetch(nextUrl)
},
async fetchStatus (listId: number): Promise<CheckinStatus> {
return api.fetch(`${urls.lists}${listId}/status/`)
},
async searchPositions (listId: number, query: string): Promise<PaginatedResponse<Position>> {
const url = `${urls.lists}${listId}/positions/?ignore_status=true&expand=subevent&expand=item&expand=variation&check_rules=true&search=${encodeURIComponent(query)}`
return api.fetch(url)
},
async redeemPosition (
listId: number,
positionId: string,
data: RedeemRequest,
untrusted: boolean = false
): Promise<RedeemResponse> {
let url = `${urls.lists}${listId}/positions/${encodeURIComponent(positionId)}/redeem/?expand=item&expand=subevent&expand=variation&expand=answers.question&expand=addons`
if (untrusted) url += '&untrusted_input=true'
const response = await fetch(url, {
method: 'POST',
headers: {
'X-CSRFToken': CSRF_TOKEN,
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
})
handleAuthError(response)
if (response.status === 404) {
return { status: 'error', reason: 'invalid' }
}
if (!response.ok && response.status !== 400) {
throw new Error('HTTP status ' + response.status)
}
return response.json()
}
}

View File

@@ -1,28 +1,21 @@
<template>
<a class="list-group-item" href="#" @click.prevent="$emit('selected', list)">
<div class="row">
<div class="col-md-6">
{{ list.name }}
</div>
<div class="col-md-6 text-muted">
{{ subevent }}
</div>
</div>
</a>
</template>
<script>
export default {
components: {},
props: {
list: Object
},
computed: {
subevent () {
if (!this.list.subevent) return '';
const name = i18nstring_localize(this.list.subevent.name)
const date = moment.utc(this.list.subevent.date_from).tz(this.$root.timezone).format(this.$root.datetime_format)
return `${name} · ${date}`
}
},
}
<script setup lang="ts">
import { computed } from 'vue'
import type { CheckinList } from '../api'
import { formatSubevent } from '../i18n'
const props = defineProps<{
list: CheckinList
}>()
defineEmits<{
selected: [list: CheckinList]
}>()
const subevent = computed(() => formatSubevent(props.list.subevent))
</script>
<template lang="pug">
a.list-group-item(href="#", @click.prevent="$emit('selected', list)")
.row
.col-md-6 {{ list.name }}
.col-md-6.text-muted {{ subevent }}
</template>

View File

@@ -1,101 +1,101 @@
<template>
<div class="panel panel-primary checkinlist-select">
<div class="panel-heading">
<h3 class="panel-title">
{{ $root.strings['checkinlist.select'] }}
</h3>
</div>
<ul class="list-group">
<checkinlist-item v-if="lists" v-for="l in lists" :list="l" :key="l.id" @selected="$emit('selected', l)"></checkinlist-item>
<li v-if="loading" class="list-group-item text-center">
<span class="fa fa-4x fa-cog fa-spin loading-icon"></span>
</li>
<li v-else-if="error" class="list-group-item text-center">
{{ error }}
</li>
<a v-else-if="next_url" class="list-group-item text-center" href="#" @click.prevent="loadNext">
{{ $root.strings['pagination.next'] }}
</a>
</ul>
</div>
</template>
<script>
export default {
components: {
CheckinlistItem: CheckinlistItem.default,
},
data() {
return {
loading: false,
error: null,
lists: null,
next_url: null,
}
},
// TODO: pagination
mounted() {
this.load()
},
methods: {
load() {
this.loading = true
const cutoff = moment().subtract(8, 'hours').toISOString()
if (location.hash) {
fetch(this.$root.api.lists + location.hash.substr(1) + '/' + '?expand=subevent')
.then(response => response.json())
.then(data => {
this.loading = false
if (data.id) {
this.$emit('selected', data)
} else {
location.hash = ''
this.load()
}
})
.catch(reason => {
location.hash = ''
this.load()
})
return
}
fetch(this.$root.api.lists + '?exclude=checkin_count&exclude=position_count&expand=subevent&ends_after=' + cutoff)
.then(response => response.json())
.then(data => {
this.loading = false
if (data.results) {
this.lists = data.results
this.next_url = data.next
} else if (data.results === 0) {
this.error = this.$root.strings['checkinlist.none']
} else {
this.error = data
}
})
.catch(reason => {
this.loading = false
this.error = reason
})
},
loadNext() {
this.loading = true
fetch(this.next_url)
.then(response => response.json())
.then(data => {
this.loading = false
if (data.results) {
this.lists.push(...data.results)
this.next_url = data.next
} else if (data.results === 0) {
this.error = this.$root.strings['checkinlist.none']
} else {
this.error = data
}
})
.catch(reason => {
this.loading = false
this.error = reason
})
},
},
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { api } from '../api'
import type { CheckinList } from '../api'
import { STRINGS } from '../i18n'
import CheckinlistItem from './checkinlist-item.vue'
const emit = defineEmits<{
selected: [list: CheckinList]
}>()
const loading = ref(false)
const error = ref<unknown>(null)
const lists = ref<CheckinList[] | null>(null)
const nextUrl = ref<string | null>(null)
async function load () {
loading.value = true
error.value = null
try {
if (location.hash) {
const listId = location.hash.substring(1)
try {
const data = await api.fetchCheckinList(listId)
loading.value = false
if (data.id) {
emit('selected', data)
} else {
location.hash = ''
load()
}
} catch {
location.hash = ''
load()
}
return
}
const data = await api.fetchCheckinLists()
loading.value = false
if (data.results) {
lists.value = data.results
nextUrl.value = data.next
} else if (data.results === 0) {
error.value = STRINGS['checkinlist.none']
} else {
error.value = data
}
} catch (e) {
loading.value = false
error.value = e
}
}
async function loadNext () {
if (!nextUrl.value) return
loading.value = true
error.value = null
try {
const data = await api.fetchNextPage<CheckinList>(nextUrl.value)
loading.value = false
if (data.results) {
lists.value.push(...data.results)
nextUrl.value = data.next
} else if (data.results === 0) {
error.value = STRINGS['checkinlist.none']
} else {
error.value = data
}
} catch (e) {
loading.value = false
error.value = e
}
}
onMounted(() => {
load()
})
</script>
<template lang="pug">
.panel.panel-primary.checkinlist-select
.panel-heading
h3.panel-title {{ STRINGS['checkinlist.select'] }}
ul.list-group
CheckinlistItem(
v-for="l in lists",
:key="l.id",
:list="l",
@selected="emit('selected', $event)"
)
li.list-group-item.text-center(v-if="loading")
span.fa.fa-4x.fa-cog.fa-spin.loading-icon
li.list-group-item.text-center(v-else-if="error") {{ error }}
a.list-group-item.text-center(v-else-if="nextUrl", href="#", @click.prevent="loadNext")
| {{ STRINGS['pagination.next'] }}
</template>

View File

@@ -1,54 +1,64 @@
<template>
<input class="form-control" :required="required">
</template>
<script>
export default {
props: ["required", "value"],
mounted: function () {
var vm = this;
var multiple = this.multiple;
$(this.$el)
.datetimepicker(this.opts())
.trigger("change")
.on("dp.change", function (e) {
vm.$emit("input", $(this).data('DateTimePicker').date().format("YYYY-MM-DD"));
});
if (!vm.value) {
$(this.$el).data("DateTimePicker").viewDate(moment().hour(0).minute(0).second(0).millisecond(0));
} else {
$(this.$el).data("DateTimePicker").date(moment(vm.value));
}
},
methods: {
opts: function () {
return {
format: $("body").attr("data-dateformat"),
locale: $("body").attr("data-datetimelocale"),
useCurrent: false,
showClear: this.required,
icons: {
time: 'fa fa-clock-o',
date: 'fa fa-calendar',
up: 'fa fa-chevron-up',
down: 'fa fa-chevron-down',
previous: 'fa fa-chevron-left',
next: 'fa fa-chevron-right',
today: 'fa fa-screenshot',
clear: 'fa fa-trash',
close: 'fa fa-remove'
}
};
}
},
watch: {
value: function (val) {
$(this.$el).data('DateTimePicker').date(moment(val));
},
},
destroyed: function () {
$(this.$el)
.off()
.datetimepicker("destroy");
}
<script setup lang="ts">
import { ref, watch, onMounted, onUnmounted } from 'vue'
import { dateFormat, datetimeLocale } from '../i18n'
const props = defineProps<{
required?: boolean
modelValue?: string
id?: string
}>()
const emit = defineEmits<{
'update:modelValue': [value: string]
}>()
const input = ref<HTMLInputElement>()
const opts = {
format: dateFormat,
locale: datetimeLocale,
useCurrent: false,
showClear: props.required,
icons: {
time: 'fa fa-clock-o',
date: 'fa fa-calendar',
up: 'fa fa-chevron-up',
down: 'fa fa-chevron-down',
previous: 'fa fa-chevron-left',
next: 'fa fa-chevron-right',
today: 'fa fa-screenshot',
clear: 'fa fa-trash',
close: 'fa fa-remove',
},
}
</script>
watch(() => props.modelValue, (val) => {
if (val) {
$(input.value!).data('DateTimePicker').date(moment(val))
}
})
onMounted(() => {
$(input.value!)
.datetimepicker(opts)
.trigger('change')
.on('dp.change', function (this: HTMLElement) {
emit('update:modelValue', $(this).data('DateTimePicker').date().format('YYYY-MM-DD'))
})
if (!props.modelValue) {
$(input.value!).data('DateTimePicker').viewDate(moment().hour(0).minute(0).second(0).millisecond(0))
} else {
$(input.value!).data('DateTimePicker').date(moment(props.modelValue))
}
})
onUnmounted(() => {
$(input.value!)
.off()
.datetimepicker('destroy')
})
</script>
<template lang="pug">
input.form-control(:id="id", ref="input", :required="required")
</template>

View File

@@ -1,55 +1,65 @@
<template>
<input class="form-control" :required="required">
</template>
<script>
export default {
props: ["required", "value"],
mounted: function () {
var vm = this;
var multiple = this.multiple;
$(this.$el)
.datetimepicker(this.opts())
.trigger("change")
.on("dp.change", function (e) {
vm.$emit("input", $(this).data('DateTimePicker').date().toISOString());
});
if (!vm.value) {
$(this.$el).data("DateTimePicker").viewDate(moment().hour(0).minute(0).second(0).millisecond(0));
} else {
$(this.$el).data("DateTimePicker").date(moment(vm.value));
}
},
methods: {
opts: function () {
return {
format: $("body").attr("data-datetimeformat"),
locale: $("body").attr("data-datetimelocale"),
timeZone: $("body").attr("data-timezone"),
useCurrent: false,
showClear: this.required,
icons: {
time: 'fa fa-clock-o',
date: 'fa fa-calendar',
up: 'fa fa-chevron-up',
down: 'fa fa-chevron-down',
previous: 'fa fa-chevron-left',
next: 'fa fa-chevron-right',
today: 'fa fa-screenshot',
clear: 'fa fa-trash',
close: 'fa fa-remove'
}
};
}
},
watch: {
value: function (val) {
$(this.$el).data('DateTimePicker').date(moment(val));
},
},
destroyed: function () {
$(this.$el)
.off()
.datetimepicker("destroy");
}
<script setup lang="ts">
import { ref, watch, onMounted, onUnmounted } from 'vue'
import { datetimeFormat, datetimeLocale, timezone } from '../i18n'
const props = defineProps<{
required?: boolean
modelValue?: string
id?: string
}>()
const emit = defineEmits<{
'update:modelValue': [value: string]
}>()
const input = ref<HTMLInputElement>()
const opts = {
format: datetimeFormat,
locale: datetimeLocale,
timeZone: timezone,
useCurrent: false,
showClear: props.required,
icons: {
time: 'fa fa-clock-o',
date: 'fa fa-calendar',
up: 'fa fa-chevron-up',
down: 'fa fa-chevron-down',
previous: 'fa fa-chevron-left',
next: 'fa fa-chevron-right',
today: 'fa fa-screenshot',
clear: 'fa fa-trash',
close: 'fa fa-remove',
},
}
</script>
watch(() => props.modelValue, (val) => {
if (val) {
$(input.value!).data('DateTimePicker').date(moment(val))
}
})
onMounted(() => {
$(input.value!)
.datetimepicker(opts)
.trigger('change')
.on('dp.change', function (this: HTMLElement) {
emit('update:modelValue', $(this).data('DateTimePicker').date().toISOString())
})
if (!props.modelValue) {
$(input.value!).data('DateTimePicker').viewDate(moment().hour(0).minute(0).second(0).millisecond(0))
} else {
$(input.value!).data('DateTimePicker').date(moment(props.modelValue))
}
})
onUnmounted(() => {
$(input.value!)
.off()
.datetimepicker('destroy')
})
</script>
<template lang="pug">
input.form-control(:id="id", ref="input", :required="required")
</template>

View File

@@ -1,48 +1,48 @@
<template>
<a class="list-group-item searchresult" href="#" @click.prevent="$emit('selected', position)" ref="a">
<div class="details">
<h4>{{ position.order }}-{{ position.positionid }} {{ position.attendee_name }}</h4>
<span>{{ itemvar }}<br></span>
<span v-if="subevent">{{ subevent }}<br></span>
<div class="secret">{{ position.secret }}</div>
</div>
<div :class="`status status-${status}`">
<span v-if="position.require_attention"><span class="fa fa-warning"></span><br></span>
{{ $root.strings[`status.${status}`] }}
</div>
</a>
</template>
<script>
export default {
components: {},
props: {
position: Object
},
computed: {
status() {
if (this.position.checkins.length) return 'redeemed';
if (this.position.order__status === 'n' && this.position.order__valid_if_pending) return 'pending_valid';
if (this.position.order__status === 'n' && this.position.order__require_approval) return 'require_approval';
return this.position.order__status
},
itemvar() {
if (this.position.variation) {
return `${i18nstring_localize(this.position.item.name)} ${i18nstring_localize(this.position.variation.value)}`
}
return i18nstring_localize(this.position.item.name)
},
subevent() {
if (!this.position.subevent) return ''
const name = i18nstring_localize(this.position.subevent.name)
const date = moment.utc(this.position.subevent.date_from).tz(this.$root.timezone).format(this.$root.datetime_format)
return `${name} · ${date}`
},
},
}
// secret
// status
// order code
// name
// seat
// require attention
<script setup lang="ts">
import { computed, ref } from 'vue'
import type { Position } from '../api'
import { STRINGS, i18nstringLocalize, formatSubevent } from '../i18n'
const props = defineProps<{
position: Position
}>()
defineEmits<{
selected: [position: Position]
}>()
const rootEl = ref<HTMLAnchorElement>()
const status = computed(() => {
if (props.position.checkins.length) return 'redeemed'
if (props.position.order__status === 'n' && props.position.order__valid_if_pending) return 'pending_valid'
if (props.position.order__status === 'n' && props.position.order__require_approval) return 'require_approval'
return props.position.order__status
})
const itemvar = computed(() => {
if (props.position.variation) {
return `${i18nstringLocalize(props.position.item.name)} ${i18nstringLocalize(props.position.variation.value)}`
}
return i18nstringLocalize(props.position.item.name)
})
const subevent = computed(() => formatSubevent(props.position.subevent))
defineExpose({ el: rootEl })
</script>
<template lang="pug">
a.list-group-item.searchresult(ref="rootEl", href="#", @click.prevent="$emit('selected', position)")
.details
h4 {{ position.order }}-{{ position.positionid }} {{ position.attendee_name }}
span {{ itemvar }}
br
span(v-if="subevent") {{ subevent }}
br
.secret {{ position.secret }}
.status(:class="`status-${status}`")
span(v-if="position.require_attention")
span.fa.fa-warning
br
| {{ STRINGS[`status.${status}`] }}
</template>

View File

@@ -1,54 +1,64 @@
<template>
<input class="form-control" :required="required">
</template>
<script>
export default {
props: ["required", "value"],
mounted: function () {
var vm = this;
var multiple = this.multiple;
$(this.$el)
.datetimepicker(this.opts())
.trigger("change")
.on("dp.change", function (e) {
vm.$emit("input", $(this).data('DateTimePicker').date().format("HH:mm:ss"));
});
if (!vm.value) {
$(this.$el).data("DateTimePicker").viewDate(moment().hour(0).minute(0).second(0).millisecond(0));
} else {
$(this.$el).data("DateTimePicker").date(moment(vm.value));
}
},
methods: {
opts: function () {
return {
format: $("body").attr("data-timeformat"),
locale: $("body").attr("data-datetimelocale"),
useCurrent: false,
showClear: this.required,
icons: {
time: 'fa fa-clock-o',
date: 'fa fa-calendar',
up: 'fa fa-chevron-up',
down: 'fa fa-chevron-down',
previous: 'fa fa-chevron-left',
next: 'fa fa-chevron-right',
today: 'fa fa-screenshot',
clear: 'fa fa-trash',
close: 'fa fa-remove'
}
};
}
},
watch: {
value: function (val) {
$(this.$el).data('DateTimePicker').date(moment(val));
},
},
destroyed: function () {
$(this.$el)
.off()
.datetimepicker("destroy");
}
<script setup lang="ts">
import { ref, watch, onMounted, onUnmounted } from 'vue'
import { timeFormat, datetimeLocale } from '../i18n'
const props = defineProps<{
required?: boolean
modelValue?: string
id?: string
}>()
const emit = defineEmits<{
'update:modelValue': [value: string]
}>()
const input = ref<HTMLInputElement>()
const opts = {
format: timeFormat,
locale: datetimeLocale,
useCurrent: false,
showClear: props.required,
icons: {
time: 'fa fa-clock-o',
date: 'fa fa-calendar',
up: 'fa fa-chevron-up',
down: 'fa fa-chevron-down',
previous: 'fa fa-chevron-left',
next: 'fa fa-chevron-right',
today: 'fa fa-screenshot',
clear: 'fa fa-trash',
close: 'fa fa-remove',
},
}
</script>
watch(() => props.modelValue, (val) => {
if (val) {
$(input.value!).data('DateTimePicker').date(moment(val))
}
})
onMounted(() => {
$(input.value!)
.datetimepicker(opts)
.trigger('change')
.on('dp.change', function (this: HTMLElement) {
emit('update:modelValue', $(this).data('DateTimePicker').date().format('HH:mm:ss'))
})
if (!props.modelValue) {
$(input.value!).data('DateTimePicker').viewDate(moment().hour(0).minute(0).second(0).millisecond(0))
} else {
$(input.value!).data('DateTimePicker').date(moment(props.modelValue))
}
})
onUnmounted(() => {
$(input.value!)
.off()
.datetimepicker('destroy')
})
</script>
<template lang="pug">
input.form-control(:id="id", ref="input", :required="required")
</template>

View File

@@ -0,0 +1,106 @@
const body = document.body
export const timezone = body.dataset.timezone ?? 'UTC'
export const datetimeFormat = body.dataset.datetimeformat ?? 'L LT'
export const dateFormat = body.dataset.dateformat ?? 'L'
export const timeFormat = body.dataset.timeformat ?? 'LT'
export const datetimeLocale = body.dataset.datetimelocale ?? 'en'
export const pretixLocale = body.dataset.pretixlocale ?? 'en'
moment.locale(datetimeLocale)
export function gettext (msgid: string): string {
if (typeof django !== 'undefined' && typeof django.gettext !== 'undefined') {
return django.gettext(msgid)
}
return msgid
}
export function ngettext (singular: string, plural: string, count: number): string {
if (typeof django !== 'undefined' && typeof django.ngettext !== 'undefined') {
return django.ngettext(singular, plural, count)
}
return plural
}
export type I18nString = string | Record<string, string> | null | undefined
export function i18nstringLocalize (obj: I18nString): string {
// external
return i18nstring_localize(obj)
}
export const STRINGS: Record<string, string> = {
'checkinlist.select': gettext('Select a check-in list'),
'checkinlist.none': gettext('No active check-in lists found.'),
'checkinlist.switch': gettext('Switch check-in list'),
'results.headline': gettext('Search results'),
'results.none': gettext('No tickets found'),
'check.headline': gettext('Result'),
'check.attention': gettext('This ticket requires special attention'),
'scantype.switch': gettext('Switch direction'),
'scantype.entry': gettext('Entry'),
'scantype.exit': gettext('Exit'),
'input.placeholder': gettext('Scan a ticket or search and press return…'),
'pagination.next': gettext('Load more'),
'status.p': gettext('Valid'),
'status.n': gettext('Unpaid'),
'status.c': gettext('Canceled'),
'status.e': gettext('Canceled'),
'status.pending_valid': gettext('Confirmed'),
'status.require_approval': gettext('Approval pending'),
'status.redeemed': gettext('Redeemed'),
'modal.cancel': gettext('Cancel'),
'modal.continue': gettext('Continue'),
'modal.unpaid.head': gettext('Ticket not paid'),
'modal.unpaid.text': gettext('This ticket is not yet paid. Do you want to continue anyways?'),
'modal.questions': gettext('Additional information required'),
'result.ok': gettext('Valid ticket'),
'result.exit': gettext('Exit recorded'),
'result.already_redeemed': gettext('Ticket already used'),
'result.questions': gettext('Information required'),
'result.invalid': gettext('Unknown ticket'),
'result.product': gettext('Ticket type not allowed here'),
'result.unpaid': gettext('Ticket not paid'),
'result.rules': gettext('Entry not allowed'),
'result.revoked': gettext('Ticket code revoked/changed'),
'result.blocked': gettext('Ticket blocked'),
'result.invalid_time': gettext('Ticket not valid at this time'),
'result.canceled': gettext('Order canceled'),
'result.ambiguous': gettext('Ticket code is ambiguous on list'),
'result.unapproved': gettext('Order not approved'),
'status.checkin': gettext('Checked-in Tickets'),
'status.position': gettext('Valid Tickets'),
'status.inside': gettext('Currently inside'),
yes: gettext('Yes'),
no: gettext('No'),
}
export interface SubEvent {
name: Record<string, string>
date_from: string
}
export function formatSubevent (subevent: SubEvent | null | undefined): string {
if (!subevent) return ''
const name = i18nstringLocalize(subevent.name)
const date = moment.utc(subevent.date_from).tz(timezone).format(datetimeFormat)
return `${name} · ${date}`
}
export interface Question {
type: string
}
export function formatAnswer (value: string, question: Question): string {
if (question.type === 'B' && value === 'True') {
return STRINGS['yes']
} else if (question.type === 'B' && value === 'False') {
return STRINGS['no']
} else if (question.type === 'W' && value) {
return moment(value).tz(timezone).format('L LT')
} else if (question.type === 'D' && value) {
return moment(value).format('L')
}
return value
}

View File

@@ -1,79 +0,0 @@
/*global gettext, Vue, App*/
function gettext(msgid) {
if (typeof django !== 'undefined' && typeof django.gettext !== 'undefined') {
return django.gettext(msgid);
}
return msgid;
}
function ngettext(singular, plural, count) {
if (typeof django !== 'undefined' && typeof django.ngettext !== 'undefined') {
return django.ngettext(singular, plural, count);
}
return plural;
}
moment.locale(document.body.attributes['data-datetimelocale'].value)
window.vapp = new Vue({
components: {
App: App.default
},
render: function (h) {
return h('App')
},
data: {
api: {
lists: document.querySelector('#app').attributes['data-api-lists'].value,
},
strings: {
'checkinlist.select': gettext('Select a check-in list'),
'checkinlist.none': gettext('No active check-in lists found.'),
'checkinlist.switch': gettext('Switch check-in list'),
'results.headline': gettext('Search results'),
'results.none': gettext('No tickets found'),
'check.headline': gettext('Result'),
'check.attention': gettext('This ticket requires special attention'),
'scantype.switch': gettext('Switch direction'),
'scantype.entry': gettext('Entry'),
'scantype.exit': gettext('Exit'),
'input.placeholder': gettext('Scan a ticket or search and press return…'),
'pagination.next': gettext('Load more'),
'status.p': gettext('Valid'),
'status.n': gettext('Unpaid'),
'status.c': gettext('Canceled'),
'status.e': gettext('Canceled'),
'status.pending_valid': gettext('Confirmed'),
'status.require_approval': gettext('Approval pending'),
'status.redeemed': gettext('Redeemed'),
'modal.cancel': gettext('Cancel'),
'modal.continue': gettext('Continue'),
'modal.unpaid.head': gettext('Ticket not paid'),
'modal.unpaid.text': gettext('This ticket is not yet paid. Do you want to continue anyways?'),
'modal.questions': gettext('Additional information required'),
'result.ok': gettext('Valid ticket'),
'result.exit': gettext('Exit recorded'),
'result.already_redeemed': gettext('Ticket already used'),
'result.questions': gettext('Information required'),
'result.invalid': gettext('Unknown ticket'),
'result.product': gettext('Ticket type not allowed here'),
'result.unpaid': gettext('Ticket not paid'),
'result.rules': gettext('Entry not allowed'),
'result.revoked': gettext('Ticket code revoked/changed'),
'result.blocked': gettext('Ticket blocked'),
'result.invalid_time': gettext('Ticket not valid at this time'),
'result.canceled': gettext('Order canceled'),
'result.ambiguous': gettext('Ticket code is ambiguous on list'),
'result.unapproved': gettext('Order not approved'),
'status.checkin': gettext('Checked-in Tickets'),
'status.position': gettext('Valid Tickets'),
'status.inside': gettext('Currently inside'),
'yes': gettext('Yes'),
'no': gettext('No'),
},
event_name: document.querySelector('#app').attributes['data-event-name'].value,
timezone: document.body.attributes['data-timezone'].value,
datetime_format: document.body.attributes['data-datetimeformat'].value,
},
el: '#app'
})

View File

@@ -0,0 +1,17 @@
import { createApp } from 'vue'
// import './scss/main.scss'
import App from './components/app.vue'
const mountEl = document.querySelector<HTMLElement>('#app')!
const app = createApp(App, mountEl.dataset)
app.mount('#app')
app.config.errorHandler = (error, _vm, info) => {
// vue fatals on errors by default, which is a weird choice
// https://github.com/vuejs/core/issues/3525
// https://github.com/vuejs/router/discussions/2435
console.error('[VUE]', info, error)
}

View File

@@ -4,6 +4,7 @@
{% load statici18n %}
{% load eventurl %}
{% load escapejson %}
{% load vite %}
<!DOCTYPE html>
<html>
<head>
@@ -23,11 +24,7 @@
<body data-datetimeformat="{{ js_datetime_format }}" data-timeformat="{{ js_time_format }}"
data-dateformat="{{ js_date_format }}" data-datetimelocale="{{ js_locale }}"
data-pretixlocale="{{ request.LANGUAGE_CODE }}" data-timezone="{{ request.event.settings.timezone }}">
<div
data-api-lists="{% url "api-v1:checkinlist-list" event=request.event.slug organizer=request.organizer.slug %}"
data-api-questions="{% url "api-v1:question-list" event=request.event.slug organizer=request.organizer.slug %}"
data-event-name="{{ request.event.name }}"
id="app"></div>
<div id="app" data-event-name="{{ request.event.name }}"></div>
{% compress js %}
<script type="text/javascript" src="{% static "pretixbase/js/i18nstring.js" %}"></script>
<script type="text/javascript" src="{% static "moment/moment-with-locales.js" %}"></script>
@@ -35,22 +32,17 @@
<script type="text/javascript" src="{% static "jquery/js/jquery-3.6.4.min.js" %}"></script>
<script type="text/javascript" src="{% static "datetimepicker/bootstrap-datetimepicker.js" %}"></script>
{% endcompress %}
{% if DEBUG %}
<script type="text/javascript" src="{% static "vuejs/vue.js" %}"></script>
{% else %}
<script type="text/javascript" src="{% static "vuejs/vue.min.js" %}"></script>
{% endif %}
{% compress js %}
<script type="text/vue" src="{% static 'pretixplugins/webcheckin/components/checkinlist-item.vue' %}"></script>
<script type="text/vue" src="{% static 'pretixplugins/webcheckin/components/checkinlist-select.vue' %}"></script>
<script type="text/vue" src="{% static 'pretixplugins/webcheckin/components/searchresult-item.vue' %}"></script>
<script type="text/vue" src="{% static 'pretixplugins/webcheckin/components/datetimefield.vue' %}"></script>
<script type="text/vue" src="{% static 'pretixplugins/webcheckin/components/datefield.vue' %}"></script>
<script type="text/vue" src="{% static 'pretixplugins/webcheckin/components/timefield.vue' %}"></script>
<script type="text/vue" src="{% static 'pretixplugins/webcheckin/components/app.vue' %}"></script>
<script type="text/javascript" src="{% static "pretixplugins/webcheckin/main.js" %}"></script>
{% endcompress %}
<script type="application/json" id="countries">{{ countries|escapejson_dumps }}</script>
<script type="application/json" id="api-settings">
{
"urls": {
"lists": "{% url "api-v1:checkinlist-list" event=request.event.slug organizer=request.organizer.slug %}",
"questions": "{% url "api-v1:question-list" event=request.event.slug organizer=request.organizer.slug %}"
}
}
</script>
{% vite_hmr %}
{% vite_asset "src/pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.ts" %}
{% csrf_token %}
</body>
</html>

View File

@@ -576,7 +576,7 @@ def filter_subevents_with_plugins(subevents, sales_channel=None):
if not app_cache:
_populate_app_cache()
for receiver in filter_subevents._live_receivers(None)[0]:
for receiver in filter_subevents._live_receivers(None):
app = get_defining_app(receiver)
event_state = {}

View File

@@ -49,7 +49,7 @@ from django.views.decorators.cache import cache_page
from django.views.decorators.gzip import gzip_page
from django.views.decorators.http import condition
from django.views.i18n import (
JavaScriptCatalog, builtin_template_path, get_formats,
JavaScriptCatalog, get_formats, js_catalog_template,
)
from lxml import html
@@ -121,9 +121,22 @@ def widget_css_etag(request, version, **kwargs):
return f'{_get_source_cache_key(version)}-{request.organizer.cache.get_or_set("css_version", default=lambda: int(time.time()))}'
def _use_vite(request):
if getattr(settings, 'PRETIX_WIDGET_VITE', False):
return True
origin = request.META.get('HTTP_ORIGIN', '')
gs = GlobalSettingsObject()
vite_origins = gs.settings.get('widget_vite_origins', as_type=str, default='')
if origin and vite_origins:
origins_list = [o.strip() for o in vite_origins.strip().splitlines() if o.strip()]
return origin in origins_list
return False
def widget_js_etag(request, version, lang, **kwargs):
gs = GlobalSettingsObject()
return gs.settings.get('widget_checksum_{}_{}'.format(version, lang))
variant = 'vite' if _use_vite(request) else 'legacy'
return gs.settings.get('widget_checksum_{}_{}_{}'.format(version, lang, variant))
@gzip_page
@@ -152,13 +165,16 @@ def widget_css(request, version, **kwargs):
return resp
def generate_widget_js(version, lang):
def generate_widget_js(version, lang, use_vite=False):
code = []
with language(lang):
# Provide isolation
code.append('(function (siteglobals) {\n')
code.append('var module = {}, exports = {};\n')
code.append('var lang = "%s";\n' % lang)
if use_vite:
code.append('const LANG = "%s";\n' % lang)
else:
code.append('var lang = "%s";\n' % lang)
c = JavaScriptCatalog()
c.translation = DjangoTranslation(lang, domain='djangojs')
@@ -170,8 +186,7 @@ def generate_widget_js(version, lang):
'September', 'October', 'November', 'December'
)
catalog = dict((k, v) for k, v in catalog.items() if k.startswith('widget\u0004') or k in str_wl)
with builtin_template_path("i18n_catalog.js").open(encoding="utf-8") as fh:
template = Engine().from_string(fh.read())
template = Engine().from_string(js_catalog_template)
context = Context({
'catalog_str': indent(json.dumps(
catalog, sort_keys=True, indent=2)) if catalog else None,
@@ -180,20 +195,25 @@ def generate_widget_js(version, lang):
'plural': plural,
})
i18n_js = template.render(context)
i18n_js = i18n_js.replace('for (const ', 'for (var ') # remove if we really want to break IE11 for good
i18n_js = i18n_js.replace(r"value.includes(", r"-1 != value.indexOf(") # remove if we really want to break IE11 for good
code.append(i18n_js)
files = [
'vuejs/vue.js' if settings.DEBUG else 'vuejs/vue.min.js',
'pretixpresale/js/widget/docready.js',
'pretixpresale/js/widget/floatformat.js',
'pretixpresale/js/widget/widget.js' if version == version_max else 'pretixpresale/js/widget/widget.v{}.js'.format(version),
]
for fname in files:
f = finders.find(fname)
with open(f, 'r', encoding='utf-8') as fp:
if use_vite:
vite_js = finders.find('vite/widget/widget.js')
if not vite_js:
raise FileNotFoundError('Vite widget build not found. Run: npm run build:widget')
with open(vite_js, 'r', encoding='utf-8') as fp:
code.append(fp.read())
else:
files = [
'vuejs/vue.js' if settings.DEBUG else 'vuejs/vue.min.js',
'pretixpresale/js/widget/docready.js',
'pretixpresale/js/widget/floatformat.js',
'pretixpresale/js/widget/widget.js' if version == version_max else 'pretixpresale/js/widget/widget.v{}.js'.format(version),
]
for fname in files:
f = finders.find(fname)
with open(f, 'r', encoding='utf-8') as fp:
code.append(fp.read())
if settings.DEBUG:
code.append('})(this);\n')
@@ -214,15 +234,22 @@ def widget_js(request, version, lang, **kwargs):
if version < version_min:
version = version_min
cached_js = cache.get('widget_js_data_v{}_{}'.format(version, lang))
use_vite = _use_vite(request)
variant = 'vite' if use_vite else 'legacy'
cache_prefix = 'widget_js_data_v{}_{}_{}'.format(version, lang, variant)
cached_js = cache.get(cache_prefix)
if cached_js and not settings.DEBUG:
resp = HttpResponse(cached_js, content_type='text/javascript')
resp._csp_ignore = True
resp['Access-Control-Allow-Origin'] = '*'
return resp
settings_key = 'widget_file_v{}_{}_{}'.format(version, lang, variant)
checksum_key = 'widget_checksum_v{}_{}_{}'.format(version, lang, variant)
gs = GlobalSettingsObject()
fname = gs.settings.get('widget_file_v{}_{}'.format(version, lang))
fname = gs.settings.get(settings_key)
resp = None
if fname and not settings.DEBUG:
if isinstance(fname, File):
@@ -230,21 +257,21 @@ def widget_js(request, version, lang, **kwargs):
try:
data = default_storage.open(fname).read()
resp = HttpResponse(data, content_type='text/javascript')
cache.set('widget_js_data_v{}_{}'.format(version, lang), data, 3600 * 4)
cache.set(cache_prefix, data, 3600 * 4)
except:
logger.exception('Failed to open widget.js')
if not resp:
data = generate_widget_js(version, lang).encode()
data = generate_widget_js(version, lang, use_vite=use_vite).encode()
checksum = hashlib.sha1(data).hexdigest()
if not settings.DEBUG:
newname = default_storage.save(
'widget/widget.{}.{}.{}.js'.format(version, lang, checksum),
'widget/widget.{}.{}.{}.{}.js'.format(version, lang, variant, checksum),
ContentFile(data)
)
gs.settings.set('widget_file_v{}_{}'.format(version, lang), 'file://' + newname)
gs.settings.set('widget_checksum_v{}_{}'.format(version, lang), checksum)
cache.set('widget_js_data_v{}_{}'.format(version, lang), data, 3600 * 4)
gs.settings.set(settings_key, 'file://' + newname)
gs.settings.set(checksum_key, checksum)
cache.set(cache_prefix, data, 3600 * 4)
resp = HttpResponse(data, content_type='text/javascript')
resp._csp_ignore = True
resp['Access-Control-Allow-Origin'] = '*'

View File

@@ -534,7 +534,6 @@ 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'
@@ -870,3 +869,10 @@ FILE_UPLOAD_MAX_SIZE_EMAIL_AUTO_ATTACHMENT = 1024 * 1024 * config.getint("pretix
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')

File diff suppressed because it is too large Load Diff

View File

@@ -1,16 +0,0 @@
{
"name": "pretix",
"version": "0.0.0",
"private": true,
"scripts": {},
"dependencies": {
"@babel/core": "^7.28.5",
"@babel/preset-env": "^7.29.0",
"@rollup/plugin-babel": "^6.1.0",
"@rollup/plugin-node-resolve": "^16.0.3",
"vue": "^2.7.16",
"rollup": "^2.79.1",
"rollup-plugin-vue": "^5.0.1",
"vue-template-compiler": "^2.7.16"
}
}

View File

@@ -1,23 +0,0 @@
import vue from 'rollup-plugin-vue'
import { getBabelOutputPlugin } from '@rollup/plugin-babel'
export default {
output: {
format: 'iife',
exports: 'named',
},
plugins: [
getBabelOutputPlugin({
presets: ['@babel/preset-env'],
// Running babel on iife output is apparently discouraged since it can lead to global
// variable leaks. Since we didn't get it to work on inputs, let's take that risk.
// (In our tests, it did not leak anything.)
allowAllFormats: true
}),
vue({
css: true,
compileTemplate: true,
needMap: false,
}),
],
};

View File

@@ -1,318 +0,0 @@
$(function () {
var TYPEOPS = {
// Every change to our supported JSON logic must be done
// * in pretix.base.services.checkin
// * in pretix.base.models.checkin
// * in pretix.helpers.jsonlogic_boolalg
// * in checkinrules.js
// * in libpretixsync
// * in pretixscan-ios
'product': {
'inList': {
'label': gettext('is one of'),
'cardinality': 2,
}
},
'variation': {
'inList': {
'label': gettext('is one of'),
'cardinality': 2,
}
},
'gate': {
'inList': {
'label': gettext('is one of'),
'cardinality': 2,
}
},
'datetime': {
'isBefore': {
'label': gettext('is before'),
'cardinality': 2,
},
'isAfter': {
'label': gettext('is after'),
'cardinality': 2,
},
},
'enum_entry_status': {
'==': {
'label': gettext('='),
'cardinality': 2,
},
},
'int_by_datetime': {
'<': {
'label': '<',
'cardinality': 2,
},
'<=': {
'label': '≤',
'cardinality': 2,
},
'>': {
'label': '>',
'cardinality': 2,
},
'>=': {
'label': '≥',
'cardinality': 2,
},
'==': {
'label': '=',
'cardinality': 2,
},
'!=': {
'label': '≠',
'cardinality': 2,
},
},
'int': {
'<': {
'label': '<',
'cardinality': 2,
},
'<=': {
'label': '≤',
'cardinality': 2,
},
'>': {
'label': '>',
'cardinality': 2,
},
'>=': {
'label': '≥',
'cardinality': 2,
},
'==': {
'label': '=',
'cardinality': 2,
},
'!=': {
'label': '≠',
'cardinality': 2,
},
},
};
var VARS = {
'product': {
'label': gettext('Product'),
'type': 'product',
},
'variation': {
'label': gettext('Product variation'),
'type': 'variation',
},
'gate': {
'label': gettext('Gate'),
'type': 'gate',
},
'now': {
'label': gettext('Current date and time'),
'type': 'datetime',
},
'now_isoweekday': {
'label': gettext('Current day of the week (1 = Monday, 7 = Sunday)'),
'type': 'int',
},
'entry_status': {
'label': gettext('Current entry status'),
'type': 'enum_entry_status',
},
'entries_number': {
'label': gettext('Number of previous entries'),
'type': 'int',
},
'entries_today': {
'label': gettext('Number of previous entries since midnight'),
'type': 'int',
},
'entries_since': {
'label': gettext('Number of previous entries since'),
'type': 'int_by_datetime',
},
'entries_before': {
'label': gettext('Number of previous entries before'),
'type': 'int_by_datetime',
},
'entries_days': {
'label': gettext('Number of days with a previous entry'),
'type': 'int',
},
'entries_days_since': {
'label': gettext('Number of days with a previous entry since'),
'type': 'int_by_datetime',
},
'entries_days_before': {
'label': gettext('Number of days with a previous entry before'),
'type': 'int_by_datetime',
},
'minutes_since_last_entry': {
'label': gettext('Minutes since last entry (-1 on first entry)'),
'type': 'int',
},
'minutes_since_first_entry': {
'label': gettext('Minutes since first entry (-1 on first entry)'),
'type': 'int',
},
};
var components = {
CheckinRulesVisualization: CheckinRulesVisualization.default,
}
if (typeof CheckinRule !== "undefined") {
Vue.component('checkin-rule', CheckinRule.default);
components = {
CheckinRulesEditor: CheckinRulesEditor.default,
CheckinRulesVisualization: CheckinRulesVisualization.default,
}
}
var app = new Vue({
el: '#rules-editor',
components: components,
data: function () {
return {
rules: {},
items: [],
all_products: false,
limit_products: [],
TYPEOPS: TYPEOPS,
VARS: VARS,
texts: {
and: gettext('All of the conditions below (AND)'),
or: gettext('At least one of the conditions below (OR)'),
date_from: gettext('Event start'),
date_to: gettext('Event end'),
date_admission: gettext('Event admission'),
date_custom: gettext('custom date and time'),
date_customtime: gettext('custom time'),
date_tolerance: gettext('Tolerance (minutes)'),
condition_add: gettext('Add condition'),
minutes: gettext('minutes'),
duplicate: gettext('Duplicate'),
status_present: pgettext('entry_status', 'present'),
status_absent: pgettext('entry_status', 'absent'),
},
hasRules: false,
};
},
computed: {
missingItems: function () {
// This computed property contains list of item or variation names that
// a) Are allowed on the checkin list according to all_products or include_products
// b) Are not matched by ANY logical branch of the rule.
// The list will be empty if there is a "catch-all" rule.
var products_seen = {};
var variations_seen = {};
var rules = convert_to_dnf(this.rules);
var branch_without_product_filter = false;
if (!rules["or"]) {
rules = {"or": [rules]}
}
for (var part of rules["or"]) {
if (!part["and"]) {
part = {"and": [part]}
}
var this_branch_without_product_filter = true;
for (var subpart of part["and"]) {
if (subpart["inList"]) {
if (subpart["inList"][0]["var"] === "product" && subpart["inList"][1]) {
this_branch_without_product_filter = false;
for (var listentry of subpart["inList"][1]["objectList"]) {
products_seen[parseInt(listentry["lookup"][1])] = true
}
} else if (subpart["inList"][0]["var"] === "variation" && subpart["inList"][1]) {
this_branch_without_product_filter = false;
for (var listentry_ of subpart["inList"][1]["objectList"]) {
variations_seen[parseInt(listentry_["lookup"][1])] = true
}
}
}
}
if (this_branch_without_product_filter) {
branch_without_product_filter = true;
break;
}
}
if (branch_without_product_filter || (!Object.keys(products_seen).length && !Object.keys(variations_seen).length)) {
// At least one branch with no product filters at all that's fine.
return [];
}
var missing = [];
for (var item of this.items) {
if (products_seen[item.id]) continue;
if (!this.all_products && !this.limit_products.includes(item.id)) continue;
if (item.variations.length > 0) {
for (var variation of item.variations) {
if (variations_seen[variation.id]) continue;
missing.push(item.name + " " + variation.name)
}
} else {
missing.push(item.name)
}
}
return missing;
}
},
created: function () {
this.rules = JSON.parse($("#id_rules").val());
if ($("#items").length) {
this.items = JSON.parse($("#items").html());
var root = this.$root
function _update() {
root.all_products = $("#id_all_products").prop("checked")
root.limit_products = $("input[name=limit_products]:checked").map(function () {
return parseInt($(this).val())
}).toArray()
}
$("#id_all_products, input[name=limit_products]").on("change", function () {
_update();
})
_update()
function check_for_invalid_ids(valid_products, valid_variations, rule) {
if (rule["and"]) {
for(const child of rule["and"])
check_for_invalid_ids(valid_products, valid_variations, child);
} else if (rule["or"]) {
for(const child of rule["or"])
check_for_invalid_ids(valid_products, valid_variations, child);
} else if (rule["inList"] && rule["inList"][0]["var"] === "product") {
for(const item of rule["inList"][1]["objectList"]) {
if (!valid_products[item["lookup"][1]])
item["lookup"][2] = "[" + gettext('Error: Product not found!') + "]";
else
item["lookup"][2] = valid_products[item["lookup"][1]];
}
} else if (rule["inList"] && rule["inList"][0]["var"] === "variation") {
for(const item of rule["inList"][1]["objectList"]) {
if (!valid_variations[item["lookup"][1]])
item["lookup"][2] = "[" + gettext('Error: Variation not found!') + "]";
else
item["lookup"][2] = valid_variations[item["lookup"][1]];
}
}
}
check_for_invalid_ids(
Object.fromEntries(this.items.map(p => [p.id, p.name])),
Object.fromEntries(this.items.flatMap(p => p.variations?.map(v => [v.id, p.name + ' ' + v.name]))),
this.rules
);
}
},
watch: {
rules: {
deep: true,
handler: function (newval) {
$("#id_rules").val(JSON.stringify(newval));
}
},
}
})
});

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