mirror of
https://github.com/pretix/pretix.git
synced 2026-05-26 18:43:59 +00:00
Compare commits
81 Commits
remove-inv
...
ssrf-cgnat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4d2bd83ed | ||
|
|
ed25a8b073 | ||
|
|
c9eb936d45 | ||
|
|
7237ece1ca | ||
|
|
18485f5d95 | ||
|
|
909ce5b27d | ||
|
|
c7b82fdc97 | ||
|
|
da380ed75e | ||
|
|
687c7e3ccf | ||
|
|
484b7141d9 | ||
|
|
f60031d67b | ||
|
|
dd29063a84 | ||
|
|
f37dfbd21a | ||
|
|
bb8ef00d49 | ||
|
|
d13c654596 | ||
|
|
2cc73baa99 | ||
|
|
f740d46d47 | ||
|
|
412a5adf8f | ||
|
|
e4da2e5e03 | ||
|
|
9d7038b127 | ||
|
|
ce5af572cc | ||
|
|
6d293e544e | ||
|
|
28a8032adf | ||
|
|
d765a89139 | ||
|
|
3df5b1d075 | ||
|
|
857791445f | ||
|
|
52b28997a2 | ||
|
|
f65a6aa11f | ||
|
|
9faca5ea24 | ||
|
|
867512eee5 | ||
|
|
1436b65347 | ||
|
|
cc06588991 | ||
|
|
32bd9fa265 | ||
|
|
bdc9b155f9 | ||
|
|
1af2941594 | ||
|
|
11dc1e6f70 | ||
|
|
e08243e3b2 | ||
|
|
3a4e30f2ec | ||
|
|
ea2fa741f5 | ||
|
|
20d1bb9d32 | ||
|
|
ad48d592e7 | ||
|
|
4861aca640 | ||
|
|
82450c8250 | ||
|
|
b21b69b2b8 | ||
|
|
80ed6e76cd | ||
|
|
bb211be436 | ||
|
|
3b70ef8c84 | ||
|
|
9d57380c9a | ||
|
|
8b468c31a5 | ||
|
|
9aec608601 | ||
|
|
e542bb606d | ||
|
|
fe1b4ec9d0 | ||
|
|
f04df7a6ee | ||
|
|
1640ddd497 | ||
|
|
27148324a6 | ||
|
|
71edfa8e1a | ||
|
|
8303ba7808 | ||
|
|
5bbbf0334d | ||
|
|
14708eef80 | ||
|
|
952f121008 | ||
|
|
074d26cff3 | ||
|
|
6a9815ea5f | ||
|
|
01bd81a3cd | ||
|
|
6ae8cfe6f0 | ||
|
|
b60c8165c2 | ||
|
|
e460bf8bae | ||
|
|
b4f3d5c435 | ||
|
|
4bc8caae73 | ||
|
|
9183034c15 | ||
|
|
33ccd4342f | ||
|
|
301c47b761 | ||
|
|
b0d1c93fd9 | ||
|
|
70d59a960c | ||
|
|
e87b030427 | ||
|
|
994e4b410a | ||
|
|
bd6abbc280 | ||
|
|
ca7c982abd | ||
|
|
6010d7f9e5 | ||
|
|
ac08359a0e | ||
|
|
0aee73a9bd | ||
|
|
27183a26ee |
@@ -1,5 +1,6 @@
|
||||
doc/
|
||||
env/
|
||||
node_modules/
|
||||
res/
|
||||
local/
|
||||
.git/
|
||||
|
||||
5
.editorconfig
Normal file
5
.editorconfig
Normal file
@@ -0,0 +1,5 @@
|
||||
[*.{js,jsx,ts,tsx,vue}]
|
||||
indent_style = tab
|
||||
indent_size = 2
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
5
.github/workflows/build.yml
vendored
5
.github/workflows/build.yml
vendored
@@ -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
|
||||
|
||||
43
.github/workflows/style-js.yml
vendored
Normal file
43
.github/workflows/style-js.yml
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
name: JS Code Style
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
paths:
|
||||
- 'src/pretix/static/pretixpresale/widget/**'
|
||||
- 'src/pretix/static/pretixcontrol/js/ui/checkinrules/**'
|
||||
- 'src/pretix/plugins/webcheckin/**'
|
||||
- 'eslint.config.mjs'
|
||||
- 'package.json'
|
||||
- 'package-lock.json'
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
paths:
|
||||
- 'src/pretix/static/pretixpresale/widget/**'
|
||||
- 'src/pretix/static/pretixcontrol/js/ui/checkinrules/**'
|
||||
- 'src/pretix/plugins/webcheckin/**'
|
||||
- 'eslint.config.mjs'
|
||||
- 'package.json'
|
||||
- 'package-lock.json'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
|
||||
jobs:
|
||||
eslint:
|
||||
name: eslint
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Node.js 24
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
cache: npm
|
||||
- name: Install Dependencies
|
||||
run: npm ci
|
||||
- name: Run ESLint
|
||||
run: npm run lint:eslint
|
||||
45
.github/workflows/tests.yml
vendored
45
.github/workflows/tests.yml
vendored
@@ -72,7 +72,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
|
||||
@@ -84,3 +84,46 @@ jobs:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
fail_ci_if_error: false
|
||||
if: matrix.database == 'postgres' && matrix.python-version == '3.13'
|
||||
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: playwright install
|
||||
- name: Run E2E tests
|
||||
working-directory: ./src
|
||||
run: PRETIX_CONFIG_FILE=tests/ci_postgres.cfg py.test tests/e2e/ -v --maxfail=10
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -24,5 +24,7 @@ local/
|
||||
.project
|
||||
.pydevproject
|
||||
.DS_Store
|
||||
node_modules/
|
||||
.vite/
|
||||
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
17
|
||||
24
|
||||
|
||||
1
.prettierignore
Normal file
1
.prettierignore
Normal file
@@ -0,0 +1 @@
|
||||
/*
|
||||
10
Dockerfile
10
Dockerfile
@@ -1,6 +1,7 @@
|
||||
FROM python:3.13-trixie
|
||||
|
||||
RUN apt-get update && \
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - && \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
gettext \
|
||||
@@ -21,8 +22,7 @@ RUN apt-get update && \
|
||||
libmaxminddb0 \
|
||||
libmaxminddb-dev \
|
||||
zlib1g-dev \
|
||||
nodejs \
|
||||
npm && \
|
||||
nodejs && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
dpkg-reconfigure locales && \
|
||||
@@ -50,6 +50,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 \
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -844,3 +844,187 @@ You can also fetch existing leads (if you are authorized to do so):
|
||||
:statuscode 200: No error
|
||||
:statuscode 401: Invalid authentication code
|
||||
:statuscode 403: Not permitted to access bulk data
|
||||
|
||||
Retrieving Vouchers
|
||||
"""""""""""""""""""
|
||||
|
||||
Vouchers returned by the App API use a different format than described in :ref:`rest-vouchers`.
|
||||
|
||||
.. rst-class:: rest-resource-table
|
||||
|
||||
===================================== ========================== =======================================================
|
||||
Field Type Description
|
||||
===================================== ========================== =======================================================
|
||||
id integer Internal ID of the voucher
|
||||
code string The voucher code that is required to redeem the voucher
|
||||
max_usages integer The maximum number of times this voucher can be
|
||||
redeemed (default: 1).
|
||||
redeemed integer The number of times this voucher already has been
|
||||
redeemed.
|
||||
valid_until datetime The voucher expiration date (or ``null``).
|
||||
subevent string Name of the date inside an event series this voucher belongs to (or ``null``).
|
||||
tag string A string that is used for grouping vouchers
|
||||
comment string An internal exhibitor comment on the voucher.
|
||||
items list of strings A list of items this voucher is restricted to (or ``null``).
|
||||
price_mode string Determines how this voucher affects product prices.
|
||||
Possible values:
|
||||
|
||||
* ``none`` – No effect on price
|
||||
* ``set`` – The product price is set to the given ``value``
|
||||
* ``subtract`` – The product price is determined by the original price *minus* the given ``value``
|
||||
* ``percent`` – The product price is determined by the original price reduced by the percentage given in ``value``
|
||||
value decimal (string) The value (see ``price_mode``)
|
||||
redemptions list of objects A list of objects, where each object represents an order position that has been purchased using the voucher.
|
||||
Each entry will contains the fields ``attendee_fields``, ``redemption_date`` and ``subevent``.
|
||||
|
||||
The attendee data in the ``attendee_fields`` that is shown is based on the event's configuration, and each entry
|
||||
contains the fields ``id``, ``label``, ``value``, and ``details``. ``details`` is usually empty
|
||||
except in a few cases where it contains an additional list of objects
|
||||
with ``value`` and ``label`` keys (e.g. splitting of names).
|
||||
===================================== ========================== =======================================================
|
||||
|
||||
|
||||
.. http:get:: /exhibitors/api/v1/vouchers/
|
||||
|
||||
Returns a list of all vouchers connected to the exhibitor.
|
||||
|
||||
Note that the ``attendee_fields`` array can contain any number of dynamic keys!
|
||||
Depending on the exhibitors permission and event configuration this might be empty, or contain lots of details.
|
||||
The app should dynamically show these values (read-only) with the labels sent by the server.
|
||||
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /exhibitors/api/v1/vouchers/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"count": 1,
|
||||
"next": null,
|
||||
"previous": null,
|
||||
"results": [
|
||||
{
|
||||
"id": 1,
|
||||
"code": "43K6LKM37FBVR2YG",
|
||||
"max_usages": 1,
|
||||
"redeemed": 0,
|
||||
"valid_until": null,
|
||||
"subevent": null,
|
||||
"tag": "testvoucher",
|
||||
"comment": "",
|
||||
"items": [
|
||||
"All"
|
||||
],
|
||||
"price_mode": "set",
|
||||
"value": "12.00",
|
||||
"redemptions": [
|
||||
{
|
||||
"attendee_fields": [
|
||||
{
|
||||
"id": "attendee_name",
|
||||
"label": "Name",
|
||||
"value": "Jon Doe",
|
||||
"details": [
|
||||
{"label": "Given name", "value": "John"},
|
||||
{"label": "Family name", "value": "Doe"},
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "attendee_email",
|
||||
"label": "Email",
|
||||
"value": "test@example.com",
|
||||
"details": []
|
||||
}
|
||||
],
|
||||
"redemption_date": "2026-05-06",
|
||||
"subevent": null
|
||||
},
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
:statuscode 200: No error
|
||||
:statuscode 401: Invalid authentication code
|
||||
:statuscode 403: Not permitted to access bulk data
|
||||
|
||||
.. http:get:: /exhibitors/api/v1/vouchers/(id)/
|
||||
|
||||
Returns the details of a single, specific voucher connected to the exhibitor.
|
||||
|
||||
Note that the ``attendee_fields`` array can contain any number of dynamic keys!
|
||||
Depending on the exhibitors permission and event configuration this might be empty, or contain lots of details.
|
||||
The app should dynamically show these values (read-only) with the labels sent by the server.
|
||||
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /exhibitors/api/v1/vouchers/1/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"id": 1,
|
||||
"code": "43K6LKM37FBVR2YG",
|
||||
"max_usages": 1,
|
||||
"redeemed": 0,
|
||||
"valid_until": null,
|
||||
"subevent": null,
|
||||
"tag": "testvoucher",
|
||||
"comment": "",
|
||||
"items": [
|
||||
"All"
|
||||
],
|
||||
"price_mode": "set",
|
||||
"value": "12.00",
|
||||
"redemptions": [
|
||||
{
|
||||
"attendee_fields": [
|
||||
{
|
||||
"id": "attendee_name",
|
||||
"label": "Name",
|
||||
"value": "Jon Doe",
|
||||
"details": [
|
||||
{"label": "Given name", "value": "John"},
|
||||
{"label": "Family name", "value": "Doe"},
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "attendee_email",
|
||||
"label": "Email",
|
||||
"value": "test@example.com",
|
||||
"details": []
|
||||
}
|
||||
],
|
||||
"redemption_date": "2026-05-06",
|
||||
"subevent": null
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
:param id: The ``id`` field of the voucher to fetch
|
||||
:statuscode 200: No error
|
||||
:statuscode 401: Invalid authentication code
|
||||
:statuscode 403: Not permitted to access bulk data
|
||||
:statuscode 404: Voucher not found in system
|
||||
@@ -70,6 +70,7 @@ The following values for ``action_types`` are valid with pretix core:
|
||||
* ``pretix.subevent.changed``
|
||||
* ``pretix.subevent.deleted``
|
||||
* ``pretix.event.item.*``
|
||||
* ``pretix.event.quota.*``
|
||||
* ``pretix.event.live.activated``
|
||||
* ``pretix.event.live.deactivated``
|
||||
* ``pretix.event.testmode.activated``
|
||||
|
||||
@@ -86,7 +86,7 @@ individual commits, we use "Rebase and merge" instead. Merge commits should be a
|
||||
.. _PEP 8: https://legacy.python.org/dev/peps/pep-0008/
|
||||
.. _flake8: https://pypi.python.org/pypi/flake8
|
||||
.. _Django Coding Style: https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/coding-style/
|
||||
.. _translation: https://docs.djangoproject.com/en/1.11/topics/i18n/translation/
|
||||
.. _class-based views: https://docs.djangoproject.com/en/1.11/topics/class-based-views/
|
||||
.. _translation: https://docs.djangoproject.com/en/6.0/topics/i18n/translation/
|
||||
.. _class-based views: https://docs.djangoproject.com/en/6.0/topics/class-based-views/
|
||||
.. _pytest-style: https://docs.pytest.org/en/latest/assert.html
|
||||
.. _fixtures: https://docs.pytest.org/en/latest/fixture.html
|
||||
|
||||
@@ -110,6 +110,56 @@ process::
|
||||
|
||||
However, beware that code changes will not auto-reload within Celery.
|
||||
|
||||
Running the local development server will also automatically start a vite dev server for all control vue components.
|
||||
|
||||
Run the widget development server
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
To locally develop the presale widget you need to start a separate vite dev server using::
|
||||
|
||||
npm run dev:widget
|
||||
|
||||
You can control the org, event and much more via query parameters like this::
|
||||
|
||||
http://localhost:5180/?org=testorg&event=testevent
|
||||
|
||||
The following query parameters are supported:
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
:widths: 20 20 60
|
||||
|
||||
* - Parameter
|
||||
- Default
|
||||
- Description
|
||||
* - ``org``
|
||||
- ``testorg``
|
||||
- Organization slug
|
||||
* - ``event``
|
||||
- ``testevent``
|
||||
- Event slug
|
||||
* - ``host``
|
||||
- ``http://localhost:8000``
|
||||
- Backend host URL
|
||||
* - ``type``
|
||||
- ``widget``
|
||||
- Element type: ``widget`` or ``button``
|
||||
* - ``mode``
|
||||
- ``dev``
|
||||
- ``dev`` loads the Vite dev source, ``prod`` loads the built ``v2.{lang}.js``
|
||||
* - ``lang``
|
||||
- ``de``
|
||||
- Language code for the prod script
|
||||
* - ``button-text``
|
||||
- ``Buy tickets!``
|
||||
- Text content for the button (only used when ``type=button``)
|
||||
|
||||
Any other query parameter is passed through as an attribute on the widget/button element.
|
||||
For example, ``?skip-ssl-check&list-type=calendar&items=123`` adds those attributes directly.
|
||||
|
||||
|
||||
|
||||
|
||||
.. _`checksandtests`:
|
||||
|
||||
Code checks and unit tests
|
||||
|
||||
108
eslint.config.mjs
Normal file
108
eslint.config.mjs
Normal 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',
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
4781
package-lock.json
generated
Normal file
4781
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
52
package.json
Normal file
52
package.json
Normal 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"
|
||||
},
|
||||
"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",
|
||||
"smol-toml": "^1.6.1",
|
||||
"stylus": "^0.64.0",
|
||||
"typescript-eslint": "^8.57.0",
|
||||
"vite": "^8.0.0"
|
||||
}
|
||||
}
|
||||
@@ -27,13 +27,13 @@ classifiers = [
|
||||
]
|
||||
|
||||
dependencies = [
|
||||
"arabic-reshaper==3.0.0", # Support for Arabic in reportlab
|
||||
"arabic-reshaper==3.0.1", # Support for Arabic in reportlab
|
||||
"babel",
|
||||
"BeautifulSoup4==4.14.*",
|
||||
"bleach==6.3.*",
|
||||
"celery==5.6.*",
|
||||
"chardet==5.2.*",
|
||||
"cryptography>=47.0.0",
|
||||
"cryptography>=48.0.0",
|
||||
"css-inline==0.20.*",
|
||||
"defusedcsv>=3.0.0",
|
||||
"dnspython==2.*",
|
||||
@@ -43,7 +43,7 @@ dependencies = [
|
||||
"django-countries==8.2.*",
|
||||
"django-filter==25.1",
|
||||
"django-formset-js-improved==0.5.0.5",
|
||||
"django-formtools==2.5.1",
|
||||
"django-formtools==2.6.1",
|
||||
"django-hierarkey==2.0.*,>=2.0.1",
|
||||
"django-hijack==3.7.*",
|
||||
"django-i18nfield==1.11.*",
|
||||
@@ -56,7 +56,7 @@ dependencies = [
|
||||
"django-redis==6.0.*",
|
||||
"django-scopes==2.0.*",
|
||||
"django-statici18n==2.7.*",
|
||||
"djangorestframework==3.16.*",
|
||||
"djangorestframework==3.17.*",
|
||||
"dnspython==2.8.*",
|
||||
"drf_ujson2==1.7.*",
|
||||
"geoip2==5.*",
|
||||
@@ -74,11 +74,11 @@ dependencies = [
|
||||
"packaging",
|
||||
"paypalrestsdk==1.13.*",
|
||||
"paypal-checkout-serversdk==1.0.*",
|
||||
"PyJWT==2.12.*",
|
||||
"PyJWT==2.13.*",
|
||||
"phonenumberslite==9.0.*",
|
||||
"Pillow==12.2.*",
|
||||
"pretix-plugin-build",
|
||||
"protobuf==7.34.*",
|
||||
"protobuf==7.35.*",
|
||||
"psycopg2-binary",
|
||||
"pycountry",
|
||||
"pycparser==3.0",
|
||||
@@ -91,9 +91,9 @@ dependencies = [
|
||||
"pyuca",
|
||||
"qrcode==8.2",
|
||||
"redis==7.4.*",
|
||||
"reportlab==4.4.*",
|
||||
"reportlab==4.5.*",
|
||||
"requests==2.32.*",
|
||||
"sentry-sdk==2.58.*",
|
||||
"sentry-sdk==2.60.*",
|
||||
"sepaxml==2.7.*",
|
||||
"stripe==7.9.*",
|
||||
"text-unidecode==1.*",
|
||||
@@ -111,7 +111,7 @@ dev = [
|
||||
"aiohttp==3.13.*",
|
||||
"coverage",
|
||||
"coveralls",
|
||||
"fakeredis==2.34.*",
|
||||
"fakeredis==2.35.*",
|
||||
"flake8==7.3.*",
|
||||
"freezegun",
|
||||
"isort==8.0.*",
|
||||
@@ -124,7 +124,9 @@ dev = [
|
||||
"pytest-mock==3.15.*",
|
||||
"pytest-sugar",
|
||||
"pytest-xdist==3.8.*",
|
||||
"pytest-playwright",
|
||||
"pytest==9.0.*",
|
||||
"playwright",
|
||||
"responses",
|
||||
]
|
||||
|
||||
|
||||
@@ -37,4 +37,9 @@ ignore =
|
||||
CONTRIBUTING.md
|
||||
Dockerfile
|
||||
SECURITY.md
|
||||
eslint.config.mjs
|
||||
package-lock.json
|
||||
package.json
|
||||
tsconfig.json
|
||||
vite.config.js
|
||||
|
||||
|
||||
12
src/Makefile
12
src/Makefile
@@ -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
|
||||
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -47,3 +47,5 @@ HAS_MEMCACHED = False
|
||||
HAS_CELERY = False
|
||||
HAS_GEOIP = False
|
||||
SENTRY_ENABLED = False
|
||||
VITE_DEV_MODE = False
|
||||
VITE_IGNORE = False
|
||||
|
||||
@@ -133,37 +133,43 @@ class JobRunSerializer(serializers.Serializer):
|
||||
return not bool(self._errors)
|
||||
|
||||
|
||||
class ExportFormDataField(serializers.Field):
|
||||
def get_attribute(self, instance):
|
||||
return (instance.export_identifier, instance.export_form_data)
|
||||
|
||||
def to_representation(self, value):
|
||||
export_identifier, export_form_data = value
|
||||
exporter = self.context['exporters'].get(export_identifier)
|
||||
if exporter:
|
||||
return JobRunSerializer(exporter=exporter).to_representation(export_form_data)
|
||||
else:
|
||||
return export_form_data
|
||||
|
||||
def get_value(self, dictionary):
|
||||
return dictionary
|
||||
|
||||
def to_internal_value(self, data):
|
||||
if "export_form_data" in data:
|
||||
identifier = data.get('export_identifier', self.parent.instance.export_identifier if self.parent.instance else None)
|
||||
exporter = self.context['exporters'].get(identifier)
|
||||
if exporter:
|
||||
return JobRunSerializer(exporter=exporter).to_internal_value(data["export_form_data"])
|
||||
else:
|
||||
return data['export_form_data']
|
||||
|
||||
|
||||
class ScheduledExportSerializer(serializers.ModelSerializer):
|
||||
schedule_next_run = serializers.DateTimeField(read_only=True)
|
||||
export_identifier = serializers.ChoiceField(choices=[])
|
||||
locale = serializers.ChoiceField(choices=settings.LANGUAGES, default='en')
|
||||
owner = serializers.SlugRelatedField(slug_field='email', read_only=True)
|
||||
error_counter = serializers.IntegerField(read_only=True)
|
||||
export_form_data = ExportFormDataField()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['export_identifier'].choices = [(e, e) for e in self.context['exporters']]
|
||||
|
||||
def validate(self, attrs):
|
||||
if attrs.get("export_form_data"):
|
||||
identifier = attrs.get('export_identifier', self.instance.export_identifier if self.instance else None)
|
||||
exporter = self.context['exporters'].get(identifier)
|
||||
if exporter:
|
||||
try:
|
||||
attrs["export_form_data"] = JobRunSerializer(exporter=exporter).to_internal_value(attrs["export_form_data"])
|
||||
except ValidationError as e:
|
||||
raise ValidationError({"export_form_data": e.detail})
|
||||
else:
|
||||
raise ValidationError({"export_identifier": ["Unknown exporter."]})
|
||||
return attrs
|
||||
|
||||
def to_representation(self, instance):
|
||||
repr = super().to_representation(instance)
|
||||
exporter = self.context['exporters'].get(instance.export_identifier)
|
||||
if exporter:
|
||||
repr["export_form_data"] = JobRunSerializer(exporter=exporter).to_representation(repr["export_form_data"])
|
||||
return repr
|
||||
|
||||
def validate_mail_additional_recipients(self, value):
|
||||
d = value.replace(' ', '')
|
||||
if len(d.split(',')) > 25:
|
||||
|
||||
@@ -115,10 +115,10 @@ class PluginsField(serializers.Field):
|
||||
|
||||
def to_representation(self, obj):
|
||||
from pretix.base.plugins import get_all_plugins
|
||||
|
||||
active_plugins = set(obj.get_plugins())
|
||||
return sorted([
|
||||
p.module for p in get_all_plugins()
|
||||
if not p.name.startswith('.') and getattr(p, 'visible', True) and p.module in obj.get_plugins()
|
||||
if not p.name.startswith('.') and getattr(p, 'visible', True) and p.module in active_plugins
|
||||
])
|
||||
|
||||
def to_internal_value(self, data):
|
||||
|
||||
@@ -45,6 +45,12 @@ class PrimaryKeyRelatedField(serializers.PrimaryKeyRelatedField):
|
||||
return value
|
||||
return super().to_representation(value)
|
||||
|
||||
def to_internal_value(self, data):
|
||||
value = super().to_internal_value(data)
|
||||
if value is not None:
|
||||
return value.pk
|
||||
return value
|
||||
|
||||
|
||||
class FormFieldWrapperField(serializers.Field):
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
@@ -408,6 +408,12 @@ def register_default_webhook_events(sender, **kwargs):
|
||||
_('This includes product added or deleted and changes to nested objects like '
|
||||
'variations or bundles.'),
|
||||
),
|
||||
ParametrizedItemWebhookEvent(
|
||||
'pretix.event.quota.*',
|
||||
_('Quota changed'),
|
||||
_('This includes related events like creation, deletion, opening or closing of quotas. '
|
||||
'No webhook is sent for changes to the resulting availability.'),
|
||||
),
|
||||
ParametrizedEventWebhookEvent(
|
||||
'pretix.event.live.activated',
|
||||
_('Shop taken live'),
|
||||
|
||||
@@ -160,7 +160,7 @@ class OrderListExporter(MultiSheetListExporter):
|
||||
|
||||
def _get_all_payment_methods(self, qs):
|
||||
pps = dict(get_all_payment_providers())
|
||||
return sorted([(pp, pps[pp]) for pp in set(
|
||||
return sorted([(pp, pps.get(pp, pp)) for pp in set(
|
||||
OrderPayment.objects.exclude(provider='free').filter(order__event__in=self.events).values_list(
|
||||
'provider', flat=True
|
||||
).distinct()
|
||||
@@ -330,6 +330,7 @@ class OrderListExporter(MultiSheetListExporter):
|
||||
taxsum=Sum('tax_value'), grosssum=Sum('value')
|
||||
)
|
||||
}
|
||||
payment_methods = None
|
||||
if form_data.get('include_payment_amounts'):
|
||||
payment_sum_cache = {
|
||||
(o['order__id'], o['provider']): o['grosssum'] for o in
|
||||
@@ -347,6 +348,7 @@ class OrderListExporter(MultiSheetListExporter):
|
||||
grosssum=Sum('amount')
|
||||
)
|
||||
}
|
||||
payment_methods = self._get_all_payment_methods(qs)
|
||||
sum_cache = {
|
||||
(o['order__id'], o['tax_rate']): o for o in
|
||||
OrderPosition.objects.values('tax_rate', 'order__id').order_by().annotate(
|
||||
@@ -434,7 +436,6 @@ class OrderListExporter(MultiSheetListExporter):
|
||||
)
|
||||
|
||||
if form_data.get('include_payment_amounts'):
|
||||
payment_methods = self._get_all_payment_methods(qs)
|
||||
for id, vn in payment_methods:
|
||||
row.append(
|
||||
payment_sum_cache.get((order.id, id), Decimal('0.00')) -
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
import copy
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
from datetime import timedelta
|
||||
from decimal import Decimal
|
||||
from io import BytesIO
|
||||
@@ -47,9 +48,7 @@ from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from django.core.validators import (
|
||||
MaxValueValidator, MinValueValidator, RegexValidator,
|
||||
)
|
||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||
from django.db.models import QuerySet
|
||||
from django.forms import Select, widgets
|
||||
from django.forms.widgets import FILE_INPUT_CONTRADICTION
|
||||
@@ -220,20 +219,6 @@ class NamePartsFormField(forms.MultiValueField):
|
||||
defaults = {
|
||||
'widget': self.widget,
|
||||
'max_length': kwargs.pop('max_length', None),
|
||||
'validators': [
|
||||
RegexValidator(
|
||||
# The following characters should never appear in a name anywhere of
|
||||
# the world. However, they commonly appear in inputs generated by spam
|
||||
# bots.
|
||||
r'^[^$€/%§{}<>~]*$',
|
||||
message=_('Please do not use special characters in names.')
|
||||
),
|
||||
RegexValidator(
|
||||
URL_RE,
|
||||
inverse_match=True,
|
||||
message=_('Please do not use special characters in names.')
|
||||
)
|
||||
]
|
||||
}
|
||||
self.max_length = defaults['max_length']
|
||||
self.scheme_name = kwargs.pop('scheme')
|
||||
@@ -255,7 +240,6 @@ class NamePartsFormField(forms.MultiValueField):
|
||||
if fname == 'title' and self.scheme_titles:
|
||||
d = dict(defaults)
|
||||
d.pop('max_length', None)
|
||||
d.pop('validators', None)
|
||||
field = forms.ChoiceField(
|
||||
**d,
|
||||
choices=[('', '')] + [(d, d) for d in self.scheme_titles[1]]
|
||||
@@ -264,7 +248,6 @@ class NamePartsFormField(forms.MultiValueField):
|
||||
elif fname == 'salutation':
|
||||
d = dict(defaults)
|
||||
d.pop('max_length', None)
|
||||
d.pop('validators', None)
|
||||
field = forms.ChoiceField(
|
||||
**d,
|
||||
choices=[
|
||||
@@ -296,6 +279,37 @@ class NamePartsFormField(forms.MultiValueField):
|
||||
if sum(len(v) for v in value.values() if v) > (self.max_length or 250):
|
||||
raise forms.ValidationError(_('Please enter a shorter name.'), code='max_length')
|
||||
|
||||
for fname, label, size in self.scheme['fields']:
|
||||
if fname == 'salutation' or (fname == 'title' and self.scheme_titles):
|
||||
continue
|
||||
v = value.get(fname)
|
||||
if not v:
|
||||
continue
|
||||
special_chars = re.findall('[$€/%§{}<>~]', v)
|
||||
if special_chars:
|
||||
raise forms.ValidationError(
|
||||
_('The field "%(label)s" may not contain special characters such as "%(chars)s".'),
|
||||
code='name_special_chars',
|
||||
params={
|
||||
"label": label,
|
||||
"chars": "".join(special_chars),
|
||||
},
|
||||
)
|
||||
# URL_RE checks for valid domain names, including one special TLD med, which can be part of a title
|
||||
if ".med" in v:
|
||||
v = v.replace(".med", ". med")
|
||||
value[fname] = v
|
||||
url_matched = URL_RE.search(v)
|
||||
if url_matched:
|
||||
raise forms.ValidationError(
|
||||
_('The field "%(label)s" may not contain an URL (%(url)s).'),
|
||||
code='url_in_title',
|
||||
params={
|
||||
"label": label,
|
||||
"url": url_matched.group(0),
|
||||
}
|
||||
)
|
||||
|
||||
if value.get("salutation") == "empty":
|
||||
value["salutation"] = ""
|
||||
|
||||
|
||||
@@ -1160,7 +1160,7 @@ class Modern1Renderer(ClassicInvoiceRenderer):
|
||||
return stylesheet
|
||||
|
||||
def _draw_invoice_from(self, canvas):
|
||||
if not self.invoice.invoice_from:
|
||||
if not self.invoice.address_invoice_from:
|
||||
return
|
||||
c = [
|
||||
self._clean_text(l)
|
||||
|
||||
59
src/pretix/base/management/commands/runserver.py
Normal file
59
src/pretix/base/management/commands/runserver.py
Normal 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)
|
||||
@@ -281,7 +281,7 @@ class SecurityMiddleware(MiddlewareMixin):
|
||||
|
||||
h = {
|
||||
'default-src': ["{static}"],
|
||||
'script-src': ['{static}'],
|
||||
'script-src': ["{static}"],
|
||||
'object-src': ["'none'"],
|
||||
'frame-src': ['{static}'],
|
||||
'style-src': ["{static}", "{media}"],
|
||||
@@ -295,6 +295,18 @@ class SecurityMiddleware(MiddlewareMixin):
|
||||
# this. However, we'll restrict it to HTTPS.
|
||||
'form-action': ["{dynamic}", "https:"] + (['http:'] if settings.SITE_URL.startswith('http://') else []),
|
||||
}
|
||||
|
||||
if settings.VITE_DEV_MODE:
|
||||
h['script-src'] += ["http://localhost:5173", "ws://localhost:5173"]
|
||||
h['style-src'] += ["'unsafe-inline'"]
|
||||
h['connect-src'] += ["http://localhost:5173", "ws://localhost:5173"]
|
||||
|
||||
if hasattr(request, 'csp_nonce'):
|
||||
nonce = f"'nonce-{request.csp_nonce}'"
|
||||
h['script-src'].append(nonce)
|
||||
if not settings.VITE_DEV_MODE:
|
||||
# can't have 'unsafe-inline' and nonce at the same time
|
||||
h['style-src'].append(nonce)
|
||||
# Only include pay.google.com for wallet detection purposes on the Payment selection page
|
||||
if (
|
||||
url.url_name == "event.order.pay.change" or
|
||||
|
||||
@@ -442,7 +442,7 @@ class AttendeeState(ImportColumn):
|
||||
|
||||
@property
|
||||
def verbose_name(self):
|
||||
return _('Attendee address') + ': ' + _('State')
|
||||
return _('Attendee address') + ': ' + pgettext('address', 'State')
|
||||
|
||||
def clean(self, value, previous_values):
|
||||
if value:
|
||||
|
||||
@@ -125,7 +125,7 @@ class LoggingMixin:
|
||||
elif isinstance(self, Event):
|
||||
event = self
|
||||
organizer_id = self.organizer_id
|
||||
elif hasattr(self, 'event'):
|
||||
elif hasattr(self, 'event') and self.event:
|
||||
event = self.event
|
||||
organizer_id = self.event.organizer_id
|
||||
elif hasattr(self, 'organizer_id'):
|
||||
|
||||
@@ -49,14 +49,39 @@ class PluginType(Enum):
|
||||
EXPORT = 4
|
||||
|
||||
|
||||
def plugin_is_available(meta, event=None, organizer=None):
|
||||
if not hasattr(meta.app, 'is_available'):
|
||||
return True
|
||||
|
||||
level = getattr(meta, "level", PLUGIN_LEVEL_EVENT)
|
||||
if level == PLUGIN_LEVEL_EVENT:
|
||||
if event:
|
||||
return meta.app.is_available(event)
|
||||
elif organizer:
|
||||
if not hasattr(organizer, '_plugin_availability_fallback_event'):
|
||||
with scope(organizer=organizer):
|
||||
setattr(organizer, '_plugin_availability_fallback_event', organizer.events.first())
|
||||
return (
|
||||
organizer._plugin_availability_fallback_event
|
||||
and meta.app.is_available(organizer._plugin_availability_fallback_event)
|
||||
)
|
||||
elif level == PLUGIN_LEVEL_ORGANIZER:
|
||||
if organizer:
|
||||
return meta.app.is_available(organizer)
|
||||
elif event:
|
||||
return meta.app.is_available(event.organizer)
|
||||
elif level == PLUGIN_LEVEL_EVENT_ORGANIZER_HYBRID and (event or organizer):
|
||||
return meta.app.is_available(event or organizer)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def get_all_plugins(*, event=None, organizer=None) -> List[type]:
|
||||
"""
|
||||
Returns the PretixPluginMeta classes of all plugins found in the installed Django apps.
|
||||
"""
|
||||
assert not event or not organizer
|
||||
plugins = []
|
||||
event_fallback = None
|
||||
event_fallback_used = False
|
||||
for app in apps.get_app_configs():
|
||||
if hasattr(app, 'PretixPluginMeta'):
|
||||
meta = app.PretixPluginMeta
|
||||
@@ -65,28 +90,8 @@ def get_all_plugins(*, event=None, organizer=None) -> List[type]:
|
||||
if app.name in settings.PRETIX_PLUGINS_EXCLUDE:
|
||||
continue
|
||||
|
||||
level = getattr(meta, "level", PLUGIN_LEVEL_EVENT)
|
||||
if level == PLUGIN_LEVEL_EVENT:
|
||||
if event and hasattr(app, 'is_available'):
|
||||
if not app.is_available(event):
|
||||
continue
|
||||
elif organizer and hasattr(app, 'is_available'):
|
||||
if not event_fallback_used:
|
||||
with scope(organizer=organizer):
|
||||
event_fallback = organizer.events.first()
|
||||
event_fallback_used = True
|
||||
if not event_fallback or not app.is_available(event_fallback):
|
||||
continue
|
||||
elif level == PLUGIN_LEVEL_ORGANIZER:
|
||||
if organizer and hasattr(app, 'is_available'):
|
||||
if not app.is_available(organizer):
|
||||
continue
|
||||
elif event and hasattr(app, 'is_available'):
|
||||
if not app.is_available(event.organizer):
|
||||
continue
|
||||
elif level == PLUGIN_LEVEL_EVENT_ORGANIZER_HYBRID and (event or organizer) and hasattr(app, 'is_available'):
|
||||
if not app.is_available(event or organizer):
|
||||
continue
|
||||
if not plugin_is_available(meta, event, organizer):
|
||||
continue
|
||||
|
||||
plugins.append(meta)
|
||||
return sorted(
|
||||
|
||||
@@ -162,12 +162,12 @@ error_messages = {
|
||||
'price_too_high': gettext_lazy('The entered price is to high.'),
|
||||
'voucher_invalid': gettext_lazy('This voucher code is not known in our database.'),
|
||||
'voucher_min_usages': ngettext_lazy(
|
||||
'The voucher code "%(voucher)s" can only be used if you select at least %(number)s matching products.',
|
||||
'The voucher code "%(voucher)s" can only be used if you select at least %(number)s matching product.',
|
||||
'The voucher code "%(voucher)s" can only be used if you select at least %(number)s matching products.',
|
||||
'number'
|
||||
),
|
||||
'voucher_min_usages_removed': ngettext_lazy(
|
||||
'The voucher code "%(voucher)s" can only be used if you select at least %(number)s matching products. '
|
||||
'The voucher code "%(voucher)s" can only be used if you select at least %(number)s matching product. '
|
||||
'We have therefore removed some positions from your cart that can no longer be purchased like this.',
|
||||
'The voucher code "%(voucher)s" can only be used if you select at least %(number)s matching products. '
|
||||
'We have therefore removed some positions from your cart that can no longer be purchased like this.',
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -55,10 +55,12 @@
|
||||
{% trans "You receive these emails based on your notification settings." %}<br>
|
||||
<a href="{{ settings_url }}">
|
||||
{% trans "Click here to view and change your notification settings" %}
|
||||
</a><br>
|
||||
<a href="{{ disable_url }}">
|
||||
{% trans "Click here disable all notifications immediately." %}
|
||||
</a>
|
||||
{% if disable_url %}<br>
|
||||
<a href="{{ disable_url }}">
|
||||
{% trans "Click here disable all notifications immediately." %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<!--[if gte mso 9]>
|
||||
</td></tr></table>
|
||||
|
||||
@@ -14,5 +14,6 @@
|
||||
{% trans "You receive these emails based on your notification settings." %}
|
||||
{% trans "Click here to view and change your notification settings:" %}
|
||||
{{ settings_url }}
|
||||
{% trans "Click here disable all notifications immediately:" %}
|
||||
{% if disable_url %}{% trans "Click here disable all notifications immediately:" %}
|
||||
{{ disable_url }}
|
||||
{% endif %}
|
||||
|
||||
32
src/pretix/base/templatetags/locale.py
Normal file
32
src/pretix/base/templatetags/locale.py
Normal file
@@ -0,0 +1,32 @@
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
from django import template
|
||||
from django.conf import settings
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.filter
|
||||
def human_readable_locale(value):
|
||||
if not value:
|
||||
return ''
|
||||
return dict(settings.LANGUAGES).get(value, '')
|
||||
243
src/pretix/base/templatetags/vite.py
Normal file
243
src/pretix/base/templatetags/vite.py
Normal file
@@ -0,0 +1,243 @@
|
||||
#
|
||||
# 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
|
||||
import pathlib
|
||||
import re
|
||||
import secrets
|
||||
from urllib.parse import urljoin
|
||||
from urllib.request import urlopen
|
||||
|
||||
import importlib_metadata as metadata
|
||||
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/"
|
||||
|
||||
# entry_name -> {"manifest_entry": {...}, "url_base": "..."}
|
||||
_PLUGIN_REGISTRY = {}
|
||||
|
||||
|
||||
def _discover_plugin_manifests():
|
||||
"""Discover plugin vite manifests at startup.
|
||||
|
||||
Scans installed pretix plugins for a .vite/manifest.json inside a static.dist
|
||||
directory. Only non-editable (wheel) plugins are expected to ship pre-built
|
||||
assets; editable plugins are served through the Vite dev server.
|
||||
"""
|
||||
for ep in metadata.entry_points(group='pretix.plugin'):
|
||||
dist = ep.dist
|
||||
if not dist or not dist.files:
|
||||
continue
|
||||
|
||||
try:
|
||||
url_info = json.loads(dist.read_text('direct_url.json') or '{}')
|
||||
if url_info.get('dir_info', {}).get('editable', False):
|
||||
continue # editable plugins are served via vite dev server
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Find .vite/manifest.json inside a /static/ directory
|
||||
try:
|
||||
manifest_rel = None
|
||||
for f in dist.files:
|
||||
if f.name == 'manifest.json' and '/static/' in str(f) and '/.vite/' in str(f):
|
||||
manifest_rel = f
|
||||
break
|
||||
|
||||
if not manifest_rel:
|
||||
continue
|
||||
|
||||
manifest_path = pathlib.Path(str(dist.locate_file(manifest_rel)))
|
||||
if not manifest_path.exists():
|
||||
continue
|
||||
|
||||
plugin_manifest = json.loads(manifest_path.read_text())
|
||||
|
||||
url_base = re.search(r'/static/(.+?)/\.vite/', str(manifest_rel)).group(1) + '/'
|
||||
|
||||
for _key, entry in plugin_manifest.items():
|
||||
if entry.get('isEntry') and 'name' in entry:
|
||||
_PLUGIN_REGISTRY[entry['name']] = {
|
||||
'manifest_entry': entry,
|
||||
'url_base': url_base,
|
||||
}
|
||||
except Exception:
|
||||
LOGGER.warning(f"Failed to discover vite manifest for plugin {ep.name}", exc_info=True)
|
||||
|
||||
|
||||
# Load core manifest
|
||||
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)}")
|
||||
|
||||
# Discover plugin manifests
|
||||
if not settings.VITE_IGNORE:
|
||||
_discover_plugin_manifests()
|
||||
|
||||
|
||||
def _generate_script_tag(path, attrs, src=None):
|
||||
all_attrs = " ".join(f'{key}="{value}"' for key, value in attrs.items())
|
||||
if src is None:
|
||||
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 from the core manifest."""
|
||||
tags = []
|
||||
manifest_entry = _MANIFEST[asset]
|
||||
if already_processed is None:
|
||||
already_processed = []
|
||||
|
||||
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)
|
||||
|
||||
if "imports" in manifest_entry:
|
||||
for import_path in manifest_entry["imports"]:
|
||||
tags += _generate_css_tags(import_path, already_processed)
|
||||
|
||||
return tags
|
||||
|
||||
|
||||
def _generate_plugin_css_tags(manifest_entry, url_base):
|
||||
"""Build CSS tags for a plugin manifest entry."""
|
||||
tags = []
|
||||
if "css" in manifest_entry:
|
||||
for css_path in manifest_entry["css"]:
|
||||
full_path = urljoin(settings.STATIC_URL, url_base + css_path)
|
||||
tags.append(f'<link rel="stylesheet" href="{full_path}" />')
|
||||
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 ""
|
||||
|
||||
# Check plugin registry (non-editable plugins with pre-built assets)
|
||||
if path in _PLUGIN_REGISTRY:
|
||||
info = _PLUGIN_REGISTRY[path]
|
||||
entry = info['manifest_entry']
|
||||
url_base = info['url_base']
|
||||
tags = _generate_plugin_css_tags(entry, url_base)
|
||||
# Always use STATIC_URL for pre-built plugin assets, even in dev mode
|
||||
src = urljoin(settings.STATIC_URL, url_base + entry["file"])
|
||||
tags.append(_generate_script_tag(path, {"type": "module", "crossorigin": ""}, src=src))
|
||||
return "".join(tags)
|
||||
|
||||
# Dev mode: editable plugins and core entries go through the vite dev server
|
||||
if settings.VITE_DEV_MODE:
|
||||
return _generate_script_tag(path, {"type": "module"})
|
||||
|
||||
# Prod mode
|
||||
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"})
|
||||
|
||||
|
||||
_dev_importmap_cache = None
|
||||
|
||||
|
||||
def _get_dev_importmap():
|
||||
"""Fetch the shared-dep import map from the Vite dev server. Cached after first call."""
|
||||
global _dev_importmap_cache
|
||||
if _dev_importmap_cache is not None:
|
||||
return _dev_importmap_cache
|
||||
try:
|
||||
url = urljoin(settings.VITE_DEV_SERVER, "/__pretix_importmap")
|
||||
raw = json.loads(urlopen(url, timeout=2).read())
|
||||
_dev_importmap_cache = {
|
||||
dep: urljoin(settings.VITE_DEV_SERVER, dep_path)
|
||||
for dep, dep_path in raw.items()
|
||||
}
|
||||
except Exception:
|
||||
LOGGER.warning("Failed to fetch import map from Vite dev server")
|
||||
_dev_importmap_cache = {}
|
||||
return _dev_importmap_cache
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
@mark_safe
|
||||
def vite_importmap(context):
|
||||
"""Emit an import map so pre-built plugin assets can resolve shared dependencies like vue."""
|
||||
imports = {}
|
||||
|
||||
if settings.VITE_DEV_MODE:
|
||||
# Fetch the import map from the Vite dev server (served by sharedDepsPlugin)
|
||||
imports.update(_get_dev_importmap())
|
||||
else:
|
||||
# Discover all _vendor/* entries from the core manifest
|
||||
for _key, entry in _MANIFEST.items():
|
||||
name = entry.get("name", "")
|
||||
if name.startswith("_vendor/"):
|
||||
bare_specifier = name[len("_vendor/"):]
|
||||
imports[bare_specifier] = urljoin(settings.STATIC_URL, MANIFEST_BASE + entry["file"])
|
||||
|
||||
if not imports:
|
||||
return ""
|
||||
|
||||
# Generate a nonce and store it on the request so the CSP middleware can allow it
|
||||
nonce = secrets.token_urlsafe(16)
|
||||
request = context.get('request')
|
||||
if request:
|
||||
request.csp_nonce = nonce
|
||||
|
||||
return f'<script type="importmap" nonce="{nonce}">{json.dumps({"imports": imports})}</script>'
|
||||
@@ -24,10 +24,12 @@ import calendar
|
||||
from dateutil.rrule import DAILY, MONTHLY, WEEKLY, YEARLY, rrule, rrulestr
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import validate_email
|
||||
from django.core.validators import RegexValidator, validate_email
|
||||
from django.utils.deconstruct import deconstructible
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from pretix.base.templatetags.rich_text import URL_RE
|
||||
|
||||
# This file is based on an earlier version of pretix which was released under the Apache License 2.0. The full text of
|
||||
# the Apache License 2.0 can be obtained at <http://www.apache.org/licenses/LICENSE-2.0>.
|
||||
#
|
||||
@@ -113,6 +115,33 @@ def multimail_validate(val):
|
||||
return s
|
||||
|
||||
|
||||
class RegexValidatorInverseMatchAndParam(RegexValidator):
|
||||
inverse_match = True
|
||||
|
||||
def __call__(self, value):
|
||||
regex_matches = self.regex.search(str(value))
|
||||
if regex_matches:
|
||||
raise ValidationError(
|
||||
self.message,
|
||||
code=self.code,
|
||||
params={
|
||||
"value": value,
|
||||
"match": regex_matches.group(0) if regex_matches else "",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class NoUrlValidator(RegexValidatorInverseMatchAndParam):
|
||||
regex = URL_RE
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
if not kwargs.get("message"):
|
||||
kwargs["message"] = _('You entered an URL, which is not allowed. Please remove %(match)s from your input.')
|
||||
if not kwargs.get("code"):
|
||||
kwargs["code"] = "contains_url"
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
||||
class RRuleValidator:
|
||||
def __init__(self, enforce_simple=False):
|
||||
self.enforce_simple = enforce_simple
|
||||
|
||||
@@ -1528,6 +1528,133 @@ class SubEventFilterForm(FilterForm):
|
||||
return self.event.organizer.meta_properties.filter(filter_allowed=True)
|
||||
|
||||
|
||||
class QuotaFilterForm(FilterForm):
|
||||
orders = {
|
||||
'-date': ('-subevent__date_from', 'name', 'pk'),
|
||||
'date': ('subevent__date_from', '-name', '-pk'),
|
||||
'size': ('size', 'name', 'pk'),
|
||||
'-size': ('-size', '-name', '-pk'),
|
||||
'name': ('name', 'pk'),
|
||||
'-name': ('-name', '-pk'),
|
||||
}
|
||||
subevent = forms.ModelChoiceField(
|
||||
label=pgettext_lazy('subevent', 'Date'),
|
||||
queryset=SubEvent.objects.none(),
|
||||
required=False,
|
||||
empty_label=pgettext_lazy('subevent', 'All dates')
|
||||
)
|
||||
date_from = forms.DateField(
|
||||
label=_('Date from'),
|
||||
required=False,
|
||||
widget=DatePickerWidget({
|
||||
'placeholder': _('Date from'),
|
||||
}),
|
||||
)
|
||||
date_until = forms.DateField(
|
||||
label=_('Date until'),
|
||||
required=False,
|
||||
widget=DatePickerWidget({
|
||||
'placeholder': _('Date until'),
|
||||
}),
|
||||
)
|
||||
time_from = forms.TimeField(
|
||||
label=_('Start time from'),
|
||||
required=False,
|
||||
widget=TimePickerWidget({}),
|
||||
)
|
||||
time_until = forms.TimeField(
|
||||
label=_('Start time until'),
|
||||
required=False,
|
||||
widget=TimePickerWidget({}),
|
||||
)
|
||||
weekday = forms.MultipleChoiceField(
|
||||
label=_('Weekday'),
|
||||
choices=(
|
||||
('2', _('Monday')),
|
||||
('3', _('Tuesday')),
|
||||
('4', _('Wednesday')),
|
||||
('5', _('Thursday')),
|
||||
('6', _('Friday')),
|
||||
('7', _('Saturday')),
|
||||
('1', _('Sunday')),
|
||||
),
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
required=False
|
||||
)
|
||||
query = forms.CharField(
|
||||
label=_('Quota name'),
|
||||
widget=forms.TextInput(),
|
||||
required=False
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.event = kwargs.pop('event')
|
||||
super().__init__(*args, **kwargs)
|
||||
if self.event.has_subevents:
|
||||
self.fields['date_from'].widget = DatePickerWidget()
|
||||
self.fields['date_until'].widget = DatePickerWidget()
|
||||
self.fields['subevent'].queryset = self.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,
|
||||
}),
|
||||
'data-placeholder': pgettext_lazy('subevent', 'All dates')
|
||||
}
|
||||
)
|
||||
self.fields['subevent'].widget.choices = self.fields['subevent'].choices
|
||||
else:
|
||||
del self.fields['subevent']
|
||||
del self.fields['date_from']
|
||||
del self.fields['date_until']
|
||||
del self.fields['time_from']
|
||||
del self.fields['time_until']
|
||||
del self.fields['weekday']
|
||||
|
||||
def filter_qs(self, qs):
|
||||
fdata = self.cleaned_data
|
||||
|
||||
if fdata.get('weekday'):
|
||||
qs = qs.annotate(wday=ExtractWeekDay('subevent__date_from')).filter(wday__in=fdata.get('weekday'))
|
||||
|
||||
if fdata.get('subevent'):
|
||||
qs = qs.filter(subevent=fdata["subevent"])
|
||||
|
||||
if fdata.get('query'):
|
||||
query = fdata.get('query')
|
||||
qs = qs.filter(name__icontains=query)
|
||||
|
||||
if fdata.get('date_until'):
|
||||
date_end = make_aware(datetime.combine(
|
||||
fdata.get('date_until') + timedelta(days=1),
|
||||
time(hour=0, minute=0, second=0, microsecond=0)
|
||||
), get_current_timezone())
|
||||
qs = qs.filter(
|
||||
Q(subevent__date_to__isnull=True, subevent__date_from__lt=date_end) |
|
||||
Q(subevent__date_to__isnull=False, subevent__date_to__lt=date_end)
|
||||
)
|
||||
if fdata.get('date_from'):
|
||||
date_start = make_aware(datetime.combine(
|
||||
fdata.get('date_from'),
|
||||
time(hour=0, minute=0, second=0, microsecond=0)
|
||||
), get_current_timezone())
|
||||
qs = qs.filter(subevent__date_from__gte=date_start)
|
||||
|
||||
if fdata.get('time_until'):
|
||||
qs = qs.filter(subevent__date_from__time__lte=fdata.get('time_until'))
|
||||
if fdata.get('time_from'):
|
||||
qs = qs.filter(subevent__date_from__time__gte=fdata.get('time_from'))
|
||||
|
||||
if fdata.get('ordering'):
|
||||
qs = qs.order_by(*get_deterministic_ordering(Quota, self.get_order_by()))
|
||||
else:
|
||||
qs = qs.order_by('-subevent__date_from', 'name', 'pk')
|
||||
|
||||
return qs
|
||||
|
||||
|
||||
class OrganizerFilterForm(FilterForm):
|
||||
orders = {
|
||||
'slug': 'slug',
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -43,6 +43,7 @@ from django.core.exceptions import ValidationError
|
||||
from django.db.models import Max, Q
|
||||
from django.forms import ChoiceField, RadioSelect
|
||||
from django.forms.formsets import DELETION_FIELD_NAME
|
||||
from django.forms.utils import ErrorDict
|
||||
from django.urls import reverse
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.html import escape, format_html
|
||||
@@ -375,6 +376,60 @@ class QuotaForm(I18nModelForm):
|
||||
return inst
|
||||
|
||||
|
||||
class QuotaBulkEditForm(QuotaForm):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.mixed_values = kwargs.pop('mixed_values')
|
||||
self.queryset = kwargs.pop('queryset')
|
||||
super().__init__(**kwargs)
|
||||
self.fields.pop("subevent", None) # Would add extra complexity and it's hard to imagine a use case for that
|
||||
self.fields["name"].required = False
|
||||
self.fields["itemvars"].required = False
|
||||
|
||||
def clean(self):
|
||||
d = super().clean()
|
||||
if self.prefix + "name" in self.data.getlist('_bulk') and not d.get("name"):
|
||||
raise ValidationError({"name": _("This field is required.")})
|
||||
if self.prefix + "itemvars" in self.data.getlist('_bulk') and not d.get("itemvars"):
|
||||
raise ValidationError({"itemvars": _("This field is required.")})
|
||||
return d
|
||||
|
||||
def save(self, commit=True):
|
||||
objs = list(self.queryset)
|
||||
fields = set()
|
||||
|
||||
for k in self.fields:
|
||||
cb_val = self.prefix + k
|
||||
if cb_val not in self.data.getlist('_bulk'):
|
||||
continue
|
||||
|
||||
fields.add(k)
|
||||
if k == 'itemvars':
|
||||
selected_items = set(list(self.event.items.filter(id__in=[
|
||||
i.split('-')[0] for i in self.cleaned_data['itemvars']
|
||||
])))
|
||||
selected_variations = list(ItemVariation.objects.filter(item__event=self.event, id__in=[
|
||||
i.split('-')[1] for i in self.cleaned_data['itemvars'] if '-' in i
|
||||
]))
|
||||
for obj in objs:
|
||||
obj.items.set(selected_items)
|
||||
obj.variations.set(selected_variations)
|
||||
else:
|
||||
for obj in objs:
|
||||
setattr(obj, k, self.cleaned_data[k])
|
||||
|
||||
fields = [f for f in fields if f != 'itemvars']
|
||||
if fields:
|
||||
Quota.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()
|
||||
|
||||
|
||||
class ItemCreateForm(I18nModelForm):
|
||||
NONE = 'none'
|
||||
EXISTING = 'existing'
|
||||
|
||||
@@ -34,11 +34,11 @@
|
||||
# License for the specific language governing permissions and limitations under the License.
|
||||
|
||||
from collections import defaultdict
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
from typing import Optional
|
||||
|
||||
import bleach
|
||||
import dateutil.parser
|
||||
from django.dispatch import receiver
|
||||
from django.urls import reverse
|
||||
from django.utils.formats import date_format
|
||||
@@ -248,7 +248,7 @@ class OrderValidFromChanged(OrderChangeLogEntryType):
|
||||
def display_prefixed(self, event: Event, logentry: LogEntry, data):
|
||||
return _('The validity start date for position #{posid} has been changed to {value}.').format(
|
||||
posid=data.get('positionid', '?'),
|
||||
value=date_format(dateutil.parser.parse(data.get('new_value')), 'SHORT_DATETIME_FORMAT') if data.get(
|
||||
value=date_format(datetime.fromisoformat(data.get('new_value')), 'SHORT_DATETIME_FORMAT') if data.get(
|
||||
'new_value') else '–'
|
||||
)
|
||||
|
||||
@@ -260,7 +260,7 @@ class OrderValidUntilChanged(OrderChangeLogEntryType):
|
||||
def display_prefixed(self, event: Event, logentry: LogEntry, data):
|
||||
return _('The validity end date for position #{posid} has been changed to {value}.').format(
|
||||
posid=data.get('positionid', '?'),
|
||||
value=date_format(dateutil.parser.parse(data.get('new_value')), 'SHORT_DATETIME_FORMAT') if data.get('new_value') else '–'
|
||||
value=date_format(datetime.fromisoformat(data.get('new_value')), 'SHORT_DATETIME_FORMAT') if data.get('new_value') else '–'
|
||||
)
|
||||
|
||||
|
||||
@@ -364,7 +364,7 @@ class CheckinErrorLogEntryType(OrderLogEntryType):
|
||||
data['posid'] = logentry.parsed_data.get('positionid', '?')
|
||||
|
||||
if 'datetime' in data:
|
||||
dt = dateutil.parser.parse(data.get('datetime'))
|
||||
dt = datetime.fromisoformat(data.get('datetime'))
|
||||
if abs((logentry.datetime - dt).total_seconds()) > 5 or data.get('forced'):
|
||||
if event:
|
||||
data['datetime'] = date_format(dt.astimezone(event.timezone), "SHORT_DATETIME_FORMAT")
|
||||
@@ -430,7 +430,7 @@ class OrderPrintLogEntryType(OrderLogEntryType):
|
||||
return _('Position #{posid} has been printed at {datetime} with type "{type}".').format(
|
||||
posid=data.get('positionid'),
|
||||
datetime=date_format(
|
||||
dateutil.parser.parse(data["datetime"]).astimezone(logentry.event.timezone),
|
||||
datetime.fromisoformat(data["datetime"]).astimezone(logentry.event.timezone),
|
||||
"SHORT_DATETIME_FORMAT"
|
||||
) if logentry.event else data["datetime"],
|
||||
type=dict(PrintLog.PRINT_TYPES)[data["type"]],
|
||||
@@ -985,7 +985,7 @@ class LegacyCheckinLogEntryType(OrderLogEntryType):
|
||||
|
||||
def display(self, logentry, data):
|
||||
# deprecated
|
||||
dt = dateutil.parser.parse(data.get('datetime'))
|
||||
dt = datetime.fromisoformat(data.get('datetime'))
|
||||
tz = logentry.event.timezone
|
||||
dt_formatted = date_format(dt.astimezone(tz), "SHORT_DATETIME_FORMAT")
|
||||
if 'list' in data:
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% load statici18n %}
|
||||
{% load vite %}
|
||||
{% load eventsignal %}
|
||||
{% load eventurl %}
|
||||
{% load dialog %}
|
||||
@@ -84,6 +85,7 @@
|
||||
<meta name="theme-color" content="#3b1c4a">
|
||||
<meta name="referrer" content="origin">
|
||||
|
||||
{% vite_importmap %}
|
||||
{% block custom_header %}{% endblock %}
|
||||
</head>
|
||||
<body data-datetimeformat="{{ js_datetime_format }}" data-timeformat="{{ js_time_format }}"
|
||||
|
||||
@@ -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 }}
|
||||
@@ -125,13 +89,10 @@
|
||||
</button>
|
||||
</div>
|
||||
</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>
|
||||
{% if items %}
|
||||
{{ items|json_script:"items" }}
|
||||
{% 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 +105,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 %}
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
{% extends "pretixcontrol/event/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% block content %}
|
||||
<h1>
|
||||
{% trans "Change multiple quotas" %}
|
||||
<small>
|
||||
{% blocktrans trimmed with number=quotas.count %}
|
||||
{{ number }} selected
|
||||
{% endblocktrans %}
|
||||
</small>
|
||||
</h1>
|
||||
<form class="form-horizontal" action="" method="post">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form_errors form %}
|
||||
<div class="hidden">
|
||||
{% for d in quotas %}
|
||||
<input type="hidden" name="quota" value="{{ d.pk }}">
|
||||
{% endfor %}
|
||||
</div>
|
||||
<fieldset>
|
||||
<legend>{% trans "General information" %}</legend>
|
||||
{% bootstrap_field form.name layout="bulkedit" %}
|
||||
{% bootstrap_field form.size layout="bulkedit" %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Items" %}</legend>
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
Please select the products or product variations this quota should be applied to. If you apply two
|
||||
quotas to the same product, it will only be available if <strong>both</strong> quotas have capacity
|
||||
left.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
{% bootstrap_field form.itemvars layout="bulkedit" %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Advanced options" %}</legend>
|
||||
{% bootstrap_field form.close_when_sold_out layout="bulkedit" %}
|
||||
{% bootstrap_field form.release_after_exit layout="bulkedit" %}
|
||||
{% bootstrap_field form.ignore_for_event_availability layout="bulkedit" %}
|
||||
</fieldset>
|
||||
<div class="form-group submit-group">
|
||||
<button type="submit" class="btn btn-primary btn-save">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,34 @@
|
||||
{% extends "pretixcontrol/event/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% block title %}{% trans "Delete quotas" %}{% endblock %}
|
||||
{% block content %}
|
||||
<h1>{% trans "Delete quotas" %}</h1>
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
{% csrf_token %}
|
||||
{% if allowed %}
|
||||
<p>{% blocktrans trimmed count num=allowed|length %}
|
||||
Are you sure you want to delete the following quota?
|
||||
{% plural %}
|
||||
Are you sure you want to delete the following {{ num }} quotas?
|
||||
{% endblocktrans %}</p>
|
||||
<ul>
|
||||
{% for q in allowed %}
|
||||
<li>
|
||||
{{ q }} {% if q.subevent %}({{ q.subevent }}){% endif %}
|
||||
<input type="hidden" name="quota" value="{{ q.pk }}">
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
<div class="form-group submit-group">
|
||||
<a href="{% url "control:event.items.quotas" organizer=request.event.organizer.slug event=request.event.slug %}"
|
||||
class="btn btn-default btn-cancel">
|
||||
{% trans "Cancel" %}
|
||||
</a>
|
||||
<button type="submit" class="btn btn-danger btn-save" value="delete_confirm" name="action">
|
||||
{% trans "Delete" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
@@ -1,6 +1,7 @@
|
||||
{% extends "pretixcontrol/items/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load urlreplace %}
|
||||
{% load bootstrap3 %}
|
||||
{% block title %}{% trans "Quotas" %}{% endblock %}
|
||||
{% block inside %}
|
||||
<h1>{% trans "Quotas" %}</h1>
|
||||
@@ -13,21 +14,12 @@
|
||||
number of a specific ticket type at the same time.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
{% if request.event.has_subevents %}
|
||||
<form class="form-inline helper-display-inline" action="" method="get">
|
||||
{% include "pretixcontrol/event/fragment_subevent_choice_simple.html" %}
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if quotas|length == 0 %}
|
||||
{% if quotas|length == 0 and not filter_form.filtered %}
|
||||
<div class="empty-collection">
|
||||
<p>
|
||||
{% if request.GET.subevent %}
|
||||
{% trans "Your search did not match any quotas." %}
|
||||
{% else %}
|
||||
{% blocktrans trimmed %}
|
||||
You haven't created any quotas yet.
|
||||
{% endblocktrans %}
|
||||
{% endif %}
|
||||
{% blocktrans trimmed %}
|
||||
You haven't created any quotas yet.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
|
||||
{% if 'event.items:write' in request.eventpermset %}
|
||||
@@ -36,79 +28,160 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
{% trans "Filter" %}
|
||||
</h3>
|
||||
</div>
|
||||
<form class="panel-body filter-form" action="" method="get">
|
||||
<div class="row">
|
||||
<div class="{% if not filter_form.subevent %}col-lg-6{% else %}col-lg-2{% endif %} col-md-6 col-sm-6 col-xs-12">
|
||||
{% bootstrap_field filter_form.query %}
|
||||
</div>
|
||||
{% if filter_form.subevent %}
|
||||
<div class="col-lg-2 col-md-6 col-md-2 col-sm-6 col-xs-12">
|
||||
{% bootstrap_field filter_form.subevent %}
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-3 col-sm-6 col-xs-12">
|
||||
{% bootstrap_field filter_form.date_from %}
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-3 col-sm-6 col-xs-12">
|
||||
{% bootstrap_field filter_form.date_until %}
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-3 col-sm-6 col-xs-12">
|
||||
{% bootstrap_field filter_form.time_from %}
|
||||
</div>
|
||||
<div class="col-lg-2 col-md-3 col-sm-6 col-xs-12">
|
||||
{% bootstrap_field filter_form.time_until %}
|
||||
</div>
|
||||
<div class="col-xs-12 one-line-checkboxes">
|
||||
{% bootstrap_field filter_form.weekday %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="text-right flip">
|
||||
<button class="btn btn-primary btn-lg" type="submit">
|
||||
<span class="fa fa-filter"></span>
|
||||
{% trans "Filter" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% if 'event.items:write' in request.eventpermset %}
|
||||
<p>
|
||||
<a href="{% url "control:event.items.quotas.add" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new quota" %}
|
||||
</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover table-quotas">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Quota name" %}
|
||||
<a href="?{% url_replace request 'ordering' '-name' %}"><i class="fa fa-caret-down"></i></a>
|
||||
<a href="?{% url_replace request 'ordering' 'name' %}"><i class="fa fa-caret-up"></i></a>
|
||||
</th>
|
||||
<th>{% trans "Products" %}</th>
|
||||
{% if request.event.has_subevents %}
|
||||
<th>{% trans "Date" context "subevent" %}
|
||||
<a href="?{% url_replace request 'ordering' '-date' %}"><i class="fa fa-caret-down"></i></a>
|
||||
<a href="?{% url_replace request 'ordering' 'date' %}"><i class="fa fa-caret-up"></i></a>
|
||||
</th>
|
||||
{% endif %}
|
||||
<th>{% trans "Total capacity" %}
|
||||
<a href="?{% url_replace request 'ordering' '-size' %}"><i class="fa fa-caret-down"></i></a>
|
||||
<a href="?{% url_replace request 'ordering' 'size' %}"><i class="fa fa-caret-up"></i></a>
|
||||
</th>
|
||||
<th>{% trans "Capacity left" %}</th>
|
||||
<th class="action-col-2"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for q in quotas %}
|
||||
<form action="{% url "control:event.items.quotas.bulkaction" organizer=request.event.organizer.slug event=request.event.slug %}" method="post">
|
||||
{% csrf_token %}
|
||||
{% for field in filter_form %}
|
||||
{{ field.as_hidden }}
|
||||
{% endfor %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover table-quotas">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>
|
||||
<strong><a href="{% url "control:event.items.quotas.show" organizer=request.event.organizer.slug event=request.event.slug quota=q.id %}">{{ q.name }}</a></strong>
|
||||
{% if q.ignore_for_event_availability %}
|
||||
<span class="fa fa-eye-slash text-muted" data-toggle="tooltip" title="{% trans "Ignore this quota when determining event availability" %}"></span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<ul>
|
||||
{% for item in q.cached_items %}
|
||||
{% if not item.has_variations %}
|
||||
<li><a href="{% url "control:event.item" organizer=request.event.organizer.slug event=request.event.slug item=item.id %}">{{ item }}</a></li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% for v in q.variations.all %}
|
||||
<li><a href="{% url "control:event.item" organizer=request.event.organizer.slug event=request.event.slug item=v.item.id %}#tab-0-3-open">
|
||||
{{ v.item }} – {{ v }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</td>
|
||||
{% if request.event.has_subevents %}
|
||||
<td>
|
||||
{{ q.subevent.name }} – {{ q.subevent.get_date_range_display_with_times }}
|
||||
</td>
|
||||
{% if "event.items:write" in request.eventpermset %}
|
||||
<th>
|
||||
<label aria-label="{% trans "select all rows for batch-operation" %}" class="batch-select-label"><input type="checkbox" data-toggle-table/></label>
|
||||
</th>
|
||||
{% endif %}
|
||||
<td>{% if q.size == None %}Unlimited{% else %}{{ q.size }}{% endif %}</td>
|
||||
<td>{% include "pretixcontrol/items/fragment_quota_availability.html" with availability=q.cached_avail closed=q.closed %}</td>
|
||||
<td class="text-right flip">
|
||||
{% if 'event.items:write' in request.eventpermset %}
|
||||
<a href="{% url "control:event.items.quotas.edit" organizer=request.event.organizer.slug event=request.event.slug quota=q.id %}" class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
|
||||
<a href="{% url "control:event.items.quotas.add" organizer=request.event.organizer.slug event=request.event.slug %}?copy_from={{ q.id }}"
|
||||
class="btn btn-sm btn-default" title="{% trans "Clone" %}" data-toggle="tooltip">
|
||||
<span class="fa fa-copy"></span>
|
||||
</a>
|
||||
<a href="{% url "control:event.items.quotas.delete" organizer=request.event.organizer.slug event=request.event.slug quota=q.id %}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<th>{% trans "Quota name" %}
|
||||
<a href="?{% url_replace request 'filter-ordering' '-name' %}"><i class="fa fa-caret-down"></i></a>
|
||||
<a href="?{% url_replace request 'filter-ordering' 'name' %}"><i class="fa fa-caret-up"></i></a>
|
||||
</th>
|
||||
<th>{% trans "Products" %}</th>
|
||||
{% if request.event.has_subevents %}
|
||||
<th>{% trans "Date" context "subevent" %}
|
||||
<a href="?{% url_replace request 'filter-ordering' '-date' %}"><i class="fa fa-caret-down"></i></a>
|
||||
<a href="?{% url_replace request 'filter-ordering' 'date' %}"><i class="fa fa-caret-up"></i></a>
|
||||
</th>
|
||||
{% endif %}
|
||||
<th>{% trans "Total capacity" %}
|
||||
<a href="?{% url_replace request 'filter-ordering' '-size' %}"><i class="fa fa-caret-down"></i></a>
|
||||
<a href="?{% url_replace request 'filter-ordering' 'size' %}"><i class="fa fa-caret-up"></i></a>
|
||||
</th>
|
||||
<th>{% trans "Capacity left" %}</th>
|
||||
<th class="action-col-2"></th>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% if "event.items: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="6">
|
||||
<label for="__all">
|
||||
{% trans "Select all results on other pages as well" %}
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for q in quotas %}
|
||||
<tr>
|
||||
{% if "event.items:write" in request.eventpermset %}
|
||||
<td>
|
||||
<label aria-label="{% trans "select row for batch-operation" %}" class="batch-select-label"><input type="checkbox" name="quota" class="batch-select-checkbox" value="{{ q.pk }}"/></label>
|
||||
</td>
|
||||
{% endif %}
|
||||
<td>
|
||||
<strong><a href="{% url "control:event.items.quotas.show" organizer=request.event.organizer.slug event=request.event.slug quota=q.id %}">{{ q.name }}</a></strong>
|
||||
{% if q.ignore_for_event_availability %}
|
||||
<span class="fa fa-eye-slash text-muted" data-toggle="tooltip" title="{% trans "Ignore this quota when determining event availability" %}"></span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<ul>
|
||||
{% for item in q.cached_items %}
|
||||
{% if not item.has_variations %}
|
||||
<li><a href="{% url "control:event.item" organizer=request.event.organizer.slug event=request.event.slug item=item.id %}">{{ item }}</a></li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% for v in q.variations.all %}
|
||||
<li><a href="{% url "control:event.item" organizer=request.event.organizer.slug event=request.event.slug item=v.item.id %}#tab-0-3-open">
|
||||
{{ v.item }} – {{ v }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</td>
|
||||
{% if request.event.has_subevents %}
|
||||
<td>
|
||||
{{ q.subevent.name }} – {{ q.subevent.get_date_range_display_with_times }}
|
||||
</td>
|
||||
{% endif %}
|
||||
<td>{% if q.size == None %}Unlimited{% else %}{{ q.size }}{% endif %}</td>
|
||||
<td>{% include "pretixcontrol/items/fragment_quota_availability.html" with availability=q.cached_avail closed=q.closed %}</td>
|
||||
<td class="text-right flip">
|
||||
{% if 'event.items:write' in request.eventpermset %}
|
||||
<a href="{% url "control:event.items.quotas.edit" organizer=request.event.organizer.slug event=request.event.slug quota=q.id %}" class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
|
||||
<a href="{% url "control:event.items.quotas.add" organizer=request.event.organizer.slug event=request.event.slug %}?copy_from={{ q.id }}"
|
||||
class="btn btn-sm btn-default" title="{% trans "Clone" %}" data-toggle="tooltip">
|
||||
<span class="fa fa-copy"></span>
|
||||
</a>
|
||||
<a href="{% url "control:event.items.quotas.delete" organizer=request.event.organizer.slug event=request.event.slug quota=q.id %}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% if "event.items:write" in request.eventpermset %}
|
||||
<div class="batch-select-actions">
|
||||
<button type="submit" class="btn btn-danger btn-save" name="action" value="delete">
|
||||
<i class="fa fa-trash"></i>{% trans "Delete selected" %}
|
||||
</button>
|
||||
<button type="submit" class="btn btn-primary btn-save" name="action" value="edit"
|
||||
formaction="{% url "control:event.items.quotas.bulkedit" organizer=request.event.organizer.slug event=request.event.slug %}">
|
||||
<i class="fa fa-edit"></i>{% trans "Edit selected" %}
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</form>
|
||||
{% endif %}
|
||||
{% include "pretixcontrol/pagination.html" %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -134,6 +134,39 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if invoice_qualified and order.invoice_dirty %}
|
||||
<div class="alert alert-warning">
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
This order was changed after the last invoice was generated. A new invoice was not generated yet, because invoices are configured to be generated on payment or if required by the payment method.
|
||||
A new invoice will be generated once the customer pays the invoice or selects a payment method that requires an invoice.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
{% if "event.orders:write" in request.eventpermset %}
|
||||
<p>
|
||||
{% if uncancelled_invoice %}
|
||||
<form action="{% url "control:event.order.reissueinvoice" event=request.event.slug organizer=request.event.organizer.slug code=order.code id=uncancelled_invoice.pk %}"
|
||||
method="post">
|
||||
{% csrf_token %}
|
||||
<button class="btn btn-default" type="submit">
|
||||
{% blocktrans trimmed %}
|
||||
Reissue invoice
|
||||
{% endblocktrans %}
|
||||
</button>
|
||||
</form>
|
||||
{% elif can_generate_invoice %}
|
||||
<form method="post" action="{% url "control:event.order.geninvoice" event=request.event.slug organizer=request.event.organizer.slug code=order.code %}">
|
||||
{% csrf_token %}
|
||||
<button class="btn btn-default">
|
||||
{% trans "Generate invoice" %}
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-lg-10">
|
||||
{% for cr in order.cancellation_requests.all %}
|
||||
@@ -471,7 +504,9 @@
|
||||
{% endif %}
|
||||
{% if line.subevent %}
|
||||
<br/>
|
||||
<span class="fa fa-calendar fa-fw"></span> {{ line.subevent.name }} · {{ line.subevent.get_date_range_display_with_times }}
|
||||
<span class="fa fa-calendar fa-fw"></span>
|
||||
<a href="{% url "control:event.subevent" organizer=request.event.organizer.slug event=request.event.slug subevent=line.subevent_id %}">{{ line.subevent.name }}</a>
|
||||
· {{ line.subevent.get_date_range_display_with_times }}
|
||||
{% endif %}
|
||||
{% if line.used_membership %}
|
||||
<br /><span class="fa fa-id-card fa-fw" aria-hidden="true"></span>
|
||||
@@ -551,7 +586,7 @@
|
||||
<span class="fa fa-print"></span>
|
||||
{{ pl.datetime|date:"SHORT_DATETIME_FORMAT" }}
|
||||
{{ pl.get_type_display }}
|
||||
({{ pl.source }}{% if pl.device %}, #{{ pl.device.device_id }}{% endif %})
|
||||
({{ pl.source }}{% if pl.device %}, {{ pl.device.name }} - #{{ pl.device.device_id }}{% endif %})
|
||||
{% if not pl.successful %}<span class="fa fa-warning fa-fw"></span>{% endif %}
|
||||
<br>
|
||||
{% endfor %}
|
||||
@@ -1043,7 +1078,7 @@
|
||||
<dt>{% trans "VAT ID" %}</dt>
|
||||
<dd>
|
||||
{{ order.invoice_address.vat_id }}
|
||||
{% if order.invoice_address.vat_id_validated %}
|
||||
{% if order.invoice_address.vat_id and order.invoice_address.vat_id_validated %}
|
||||
<span class="fa fa-check" data-toggle="tooltip" title="{% blocktrans trimmed %}Valid EU VAT ID{% endblocktrans %}"></span>
|
||||
{% elif order.invoice_address.vat_id %}
|
||||
<form class="form-inline helper-display-inline" method="post"
|
||||
|
||||
@@ -23,9 +23,9 @@
|
||||
<legend>{% trans "How should the refund be sent?" %}</legend>
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
Any payments that you selected for automatical refunds will be immediately communicate the refund
|
||||
request to the respective payment provider. Manual refunds will be created as pending refunds, you
|
||||
can then later mark them as done once you actually transferred the money back to the customer.
|
||||
Any payments you selected for automatic refunds will have the refund request sent immediately to the
|
||||
respective payment provider. Manual refunds will be created as pending refunds, which you can later
|
||||
mark as done once you have actually transferred the money back to the customer.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@
|
||||
</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
<form action="{% url "control:organizer.device.bulk_edit" organizer=request.organizer.slug %}" method="post">
|
||||
<form action="#will-be-overridden" method="post">
|
||||
{% csrf_token %}
|
||||
{% for field in filter_form %}
|
||||
{{ field.as_hidden }}
|
||||
|
||||
@@ -349,6 +349,8 @@ urlpatterns = [
|
||||
name='event.items.questions.edit'),
|
||||
re_path(r'^questions/add$', item.QuestionCreate.as_view(), name='event.items.questions.add'),
|
||||
re_path(r'^quotas/$', item.QuotaList.as_view(), name='event.items.quotas'),
|
||||
re_path(r'^quotas/bulk_action$', item.QuotaBulkAction.as_view(), name='event.items.quotas.bulkaction'),
|
||||
re_path(r'^quotas/bulk_edit$', item.QuotaBulkUpdateView.as_view(), name='event.items.quotas.bulkedit'),
|
||||
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'),
|
||||
re_path(r'^quotas/(?P<quota>\d+)/change$', item.QuotaUpdate.as_view(), name='event.items.quotas.edit'),
|
||||
|
||||
@@ -41,21 +41,22 @@ from json.decoder import JSONDecodeError
|
||||
from django.contrib import messages
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.core.files import File
|
||||
from django.db import transaction
|
||||
from django.db import models, transaction
|
||||
from django.db.models import (
|
||||
Count, Exists, F, OuterRef, Prefetch, ProtectedError, Q,
|
||||
Count, Exists, F, OuterRef, Prefetch, ProtectedError, Q, Subquery, Value,
|
||||
)
|
||||
from django.db.models.functions import Cast, Concat
|
||||
from django.forms.models import inlineformset_factory
|
||||
from django.http import (
|
||||
Http404, HttpResponse, HttpResponseBadRequest, HttpResponseRedirect,
|
||||
)
|
||||
from django.shortcuts import redirect
|
||||
from django.shortcuts import redirect, render
|
||||
from django.urls import resolve, reverse
|
||||
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 FormView, ListView, View
|
||||
from django.views.generic.detail import DetailView, SingleObjectMixin
|
||||
from django_countries.fields import Country
|
||||
|
||||
@@ -65,7 +66,7 @@ from pretix.api.serializers.item import (
|
||||
)
|
||||
from pretix.base.forms import I18nFormSet
|
||||
from pretix.base.models import (
|
||||
CartPosition, Item, ItemCategory, ItemProgramTime, ItemVariation,
|
||||
CartPosition, Item, ItemCategory, ItemProgramTime, ItemVariation, LogEntry,
|
||||
OrderPosition, Question, QuestionAnswer, QuestionOption, Quota,
|
||||
SeatCategoryMapping, Voucher,
|
||||
)
|
||||
@@ -74,12 +75,15 @@ from pretix.base.models.items import ItemAddOn, ItemBundle, ItemMetaValue
|
||||
from pretix.base.services.quotas import QuotaAvailability
|
||||
from pretix.base.services.tickets import invalidate_cache
|
||||
from pretix.base.signals import quota_availability
|
||||
from pretix.control.forms.filter import QuestionAnswerFilterForm
|
||||
from pretix.control.forms.filter import (
|
||||
QuestionAnswerFilterForm, QuotaFilterForm,
|
||||
)
|
||||
from pretix.control.forms.item import (
|
||||
CategoryForm, ItemAddOnForm, ItemAddOnsFormSet, ItemBundleForm,
|
||||
ItemBundleFormSet, ItemCreateForm, ItemMetaValueForm, ItemProgramTimeForm,
|
||||
ItemProgramTimeFormSet, ItemUpdateForm, ItemVariationForm,
|
||||
ItemVariationsFormSet, QuestionForm, QuestionOptionForm, QuotaForm,
|
||||
ItemVariationsFormSet, QuestionForm, QuestionOptionForm, QuotaBulkEditForm,
|
||||
QuotaForm,
|
||||
)
|
||||
from pretix.control.permissions import (
|
||||
EventPermissionRequiredMixin, event_permission_required,
|
||||
@@ -87,6 +91,7 @@ from pretix.control.permissions import (
|
||||
from pretix.control.signals import item_forms, item_formsets
|
||||
from pretix.helpers.models import modelcopy
|
||||
|
||||
from ...helpers import GroupConcat
|
||||
from ...helpers.compat import CompatDeleteView
|
||||
from . import ChartContainingView, CreateView, PaginationMixin, UpdateView
|
||||
|
||||
@@ -831,13 +836,38 @@ class QuestionCreate(EventPermissionRequiredMixin, QuestionMixin, CreateView):
|
||||
return ret
|
||||
|
||||
|
||||
class QuotaList(PaginationMixin, ListView):
|
||||
class QuotaQueryMixin:
|
||||
|
||||
@cached_property
|
||||
def request_data(self):
|
||||
if self.request.method == "POST":
|
||||
return self.request.POST
|
||||
return self.request.GET
|
||||
|
||||
def get_queryset(self):
|
||||
qs = self.request.event.quotas
|
||||
if self.filter_form.is_valid():
|
||||
qs = self.filter_form.filter_qs(qs)
|
||||
|
||||
if 'quota' in self.request_data and '__ALL' not in self.request_data:
|
||||
qs = qs.filter(
|
||||
id__in=self.request_data.getlist('quota')
|
||||
)
|
||||
|
||||
return qs
|
||||
|
||||
@cached_property
|
||||
def filter_form(self):
|
||||
return QuotaFilterForm(data=self.request_data, prefix='filter', event=self.request.event)
|
||||
|
||||
|
||||
class QuotaList(PaginationMixin, QuotaQueryMixin, ListView):
|
||||
model = Quota
|
||||
context_object_name = 'quotas'
|
||||
template_name = 'pretixcontrol/items/quotas.html'
|
||||
|
||||
def get_queryset(self):
|
||||
qs = self.request.event.quotas.prefetch_related(
|
||||
return super().get_queryset().prefetch_related(
|
||||
Prefetch(
|
||||
"items",
|
||||
queryset=Item.objects.annotate(
|
||||
@@ -852,28 +882,10 @@ class QuotaList(PaginationMixin, ListView):
|
||||
queryset=self.request.event.subevents.all()
|
||||
)
|
||||
)
|
||||
if self.request.GET.get("subevent", "") != "":
|
||||
s = self.request.GET.get("subevent", "")
|
||||
qs = qs.filter(subevent_id=s)
|
||||
|
||||
valid_orders = {
|
||||
'-date': ('-subevent__date_from', 'name', 'pk'),
|
||||
'date': ('subevent__date_from', '-name', '-pk'),
|
||||
'size': ('size', 'name', 'pk'),
|
||||
'-size': ('-size', '-name', '-pk'),
|
||||
'name': ('name', 'pk'),
|
||||
'-name': ('-name', '-pk'),
|
||||
}
|
||||
|
||||
if self.request.GET.get("ordering", "-date") in valid_orders:
|
||||
qs = qs.order_by(*valid_orders[self.request.GET.get("ordering", "-date")])
|
||||
else:
|
||||
qs = qs.order_by('name', 'subevent__date_from', 'pk')
|
||||
|
||||
return qs
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data()
|
||||
ctx['filter_form'] = self.filter_form
|
||||
|
||||
qa = QuotaAvailability()
|
||||
qa.queue(*ctx['quotas'])
|
||||
@@ -884,6 +896,165 @@ class QuotaList(PaginationMixin, ListView):
|
||||
return ctx
|
||||
|
||||
|
||||
class QuotaBulkAction(QuotaQueryMixin, EventPermissionRequiredMixin, View):
|
||||
permission = 'event.items:write'
|
||||
|
||||
@transaction.atomic
|
||||
def post(self, request, *args, **kwargs):
|
||||
if request.POST.get('action') == 'delete':
|
||||
return render(request, 'pretixcontrol/items/quota_delete_bulk.html', {
|
||||
'allowed': self.get_queryset().select_related("subevent"),
|
||||
})
|
||||
elif request.POST.get('action') == 'delete_confirm':
|
||||
log_entries = []
|
||||
to_delete = []
|
||||
for obj in self.get_queryset():
|
||||
log_entries.append(obj.log_action('pretix.event.quota.deleted', user=self.request.user, save=False))
|
||||
to_delete.append(obj.pk)
|
||||
|
||||
if to_delete:
|
||||
LogEntry.bulk_create_and_postprocess(log_entries)
|
||||
Quota.objects.filter(pk__in=to_delete).delete()
|
||||
messages.success(request, _('The selected quotas have been deleted or disabled.'))
|
||||
return redirect(self.get_success_url())
|
||||
|
||||
def get_success_url(self) -> str:
|
||||
return reverse('control:event.items.quotas', kwargs={
|
||||
'organizer': self.request.event.organizer.slug,
|
||||
'event': self.request.event.slug,
|
||||
})
|
||||
|
||||
|
||||
class QuotaBulkUpdateView(QuotaQueryMixin, EventPermissionRequiredMixin, FormView):
|
||||
template_name = 'pretixcontrol/items/quota_bulk_edit.html'
|
||||
permission = 'event.items:write'
|
||||
context_object_name = 'quota'
|
||||
form_class = QuotaBulkEditForm
|
||||
|
||||
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 behaviour
|
||||
return '_bulk' in self.request.POST
|
||||
|
||||
def get_form_kwargs(self):
|
||||
initial = {}
|
||||
mixed_values = set()
|
||||
qs = self.get_queryset().annotate(
|
||||
items_list=Subquery(
|
||||
Quota.items.through.objects.filter(
|
||||
quota_id=OuterRef('pk'),
|
||||
item__variations__isnull=True,
|
||||
).order_by().values('quota_id').annotate(
|
||||
g=GroupConcat('item_id', separator=',', ordered=True)
|
||||
).values('g')
|
||||
),
|
||||
vars_list=Subquery(
|
||||
Quota.variations.through.objects.filter(
|
||||
quota_id=OuterRef('pk')
|
||||
).order_by().values('quota_id').annotate(
|
||||
g=GroupConcat(
|
||||
Concat(
|
||||
Cast(F('itemvariation__item_id'), output_field=models.TextField()),
|
||||
Value('-', output_field=models.TextField()),
|
||||
Cast(F('itemvariation_id'), output_field=models.TextField()),
|
||||
),
|
||||
separator=',',
|
||||
ordered=True
|
||||
)
|
||||
).values('g')
|
||||
),
|
||||
)
|
||||
|
||||
fields = {
|
||||
'name': 'name',
|
||||
'size': 'size',
|
||||
'subevent': 'subevent',
|
||||
'close_when_sold_out': 'close_when_sold_out',
|
||||
'release_after_exit': 'release_after_exit',
|
||||
'ignore_for_event_availability': 'ignore_for_event_availability',
|
||||
}
|
||||
for k, f in fields.items():
|
||||
existing_values = list(qs.order_by(f).values(f).annotate(c=Count('*')))
|
||||
if len(existing_values) == 1:
|
||||
initial[k] = existing_values[0][f]
|
||||
elif len(existing_values) > 1:
|
||||
mixed_values.add(k)
|
||||
initial[k] = None
|
||||
|
||||
item_values = list(qs.order_by("items_list").values("items_list").annotate(c=Count('*')))
|
||||
var_values = list(qs.order_by("vars_list").values("vars_list").annotate(c=Count('*')))
|
||||
if len(item_values) > 1 or len(var_values) > 1:
|
||||
mixed_values.add("itemvars")
|
||||
else:
|
||||
initial["itemvars"] = [iv for iv in (item_values[0]["items_list"] or "").split(",") + (var_values[0]["vars_list"] or "").split(",") if iv]
|
||||
|
||||
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.items.quotas', kwargs={
|
||||
'organizer': self.request.event.organizer.slug,
|
||||
'event': self.request.event.slug,
|
||||
})
|
||||
|
||||
@transaction.atomic()
|
||||
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.event.quota.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['quotas'] = self.get_queryset()
|
||||
ctx['bulk_selected'] = self.request.POST.getlist("_bulk")
|
||||
return ctx
|
||||
|
||||
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)
|
||||
|
||||
|
||||
class QuotaCreate(EventPermissionRequiredMixin, CreateView):
|
||||
model = Quota
|
||||
form_class = QuotaForm
|
||||
|
||||
@@ -554,6 +554,9 @@ class OrderDetail(OrderView):
|
||||
ctx['download_buttons'] = self.download_buttons
|
||||
ctx['payment_refund_sum'] = self.order.payment_refund_sum
|
||||
ctx['pending_sum'] = self.order.pending_sum
|
||||
ctx['uncancelled_invoice'] = self.order.invoices.exclude(
|
||||
Exists(self.order.invoices.filter(refers=OuterRef('pk'), is_cancellation=True))
|
||||
).exclude(is_cancellation=True).first()
|
||||
|
||||
return ctx
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ from pretix.base.models.organizer import (
|
||||
from pretix.base.payment import PaymentException
|
||||
from pretix.base.plugins import (
|
||||
PLUGIN_LEVEL_EVENT, PLUGIN_LEVEL_EVENT_ORGANIZER_HYBRID,
|
||||
PLUGIN_LEVEL_ORGANIZER,
|
||||
PLUGIN_LEVEL_ORGANIZER, plugin_is_available,
|
||||
)
|
||||
from pretix.base.services.export import (
|
||||
init_organizer_exporters, multiexport, scheduled_organizer_export,
|
||||
@@ -597,6 +597,13 @@ class OrganizerCreate(CreateView):
|
||||
})
|
||||
|
||||
|
||||
def available_plugins(organizer):
|
||||
from pretix.base.plugins import get_all_plugins
|
||||
|
||||
return (p for p in get_all_plugins(organizer=organizer) if not p.name.startswith('.')
|
||||
and getattr(p, 'visible', True))
|
||||
|
||||
|
||||
class OrganizerPlugins(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, TemplateView, SingleObjectMixin):
|
||||
model = Organizer
|
||||
context_object_name = 'organizer'
|
||||
@@ -606,12 +613,6 @@ class OrganizerPlugins(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixi
|
||||
def get_object(self, queryset=None) -> Organizer:
|
||||
return self.request.organizer
|
||||
|
||||
def available_plugins(self, organizer):
|
||||
from pretix.base.plugins import get_all_plugins
|
||||
|
||||
return (p for p in get_all_plugins(organizer=organizer) if not p.name.startswith('.')
|
||||
and getattr(p, 'visible', True))
|
||||
|
||||
def prepare_links(self, pluginmeta, key):
|
||||
links = getattr(pluginmeta, key, [])
|
||||
try:
|
||||
@@ -637,7 +638,7 @@ class OrganizerPlugins(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixi
|
||||
from pretix.base.plugins import CATEGORY_LABELS, CATEGORY_ORDER
|
||||
|
||||
context = super().get_context_data(*args, **kwargs)
|
||||
plugins = list(self.available_plugins(self.object))
|
||||
plugins = list(available_plugins(self.object))
|
||||
|
||||
active_counter = Counter()
|
||||
events_total = 0
|
||||
@@ -685,7 +686,7 @@ class OrganizerPlugins(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixi
|
||||
self.object = self.get_object()
|
||||
|
||||
plugins_available = {
|
||||
p.module: p for p in self.available_plugins(self.object)
|
||||
p.module: p for p in available_plugins(self.object)
|
||||
}
|
||||
choose_events_next = False
|
||||
with transaction.atomic():
|
||||
@@ -786,12 +787,6 @@ class OrganizerPluginEvents(OrganizerDetailViewMixin, OrganizerPermissionRequire
|
||||
}
|
||||
return kwargs
|
||||
|
||||
def available_plugins(self, organizer):
|
||||
from pretix.base.plugins import get_all_plugins
|
||||
|
||||
return (p for p in get_all_plugins(organizer=organizer) if not p.name.startswith('.')
|
||||
and getattr(p, 'visible', True))
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
return super().get_context_data(
|
||||
plugin=self.plugin,
|
||||
@@ -799,12 +794,10 @@ class OrganizerPluginEvents(OrganizerDetailViewMixin, OrganizerPermissionRequire
|
||||
)
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
plugins_available = {
|
||||
p.module: p for p in self.available_plugins(self.request.organizer)
|
||||
}
|
||||
if kwargs["plugin"] not in plugins_available:
|
||||
try:
|
||||
self.plugin = next(p for p in available_plugins(self.request.organizer) if p.module == kwargs["plugin"])
|
||||
except StopIteration:
|
||||
raise Http404(_("Unknown plugin."))
|
||||
self.plugin = plugins_available[kwargs["plugin"]]
|
||||
level = getattr(self.plugin, "level", PLUGIN_LEVEL_EVENT)
|
||||
if level == PLUGIN_LEVEL_ORGANIZER:
|
||||
raise Http404(_("This plugin can only be enabled for the entire organizer account."))
|
||||
@@ -835,6 +828,9 @@ class OrganizerPluginEvents(OrganizerDetailViewMixin, OrganizerPermissionRequire
|
||||
logentries_to_save = []
|
||||
|
||||
for e in self.request.organizer.events.filter(pk__in=events_to_enable):
|
||||
if not plugin_is_available(self.plugin, organizer=self.request.organizer, event=e):
|
||||
messages.warning(self.request, _("This plugin cannot be activated for event {}.").format(e.name))
|
||||
continue
|
||||
logentries_to_save.append(
|
||||
e.log_action('pretix.event.plugins.enabled', user=self.request.user, data={'plugin': self.plugin.module}, save=False)
|
||||
)
|
||||
|
||||
@@ -531,6 +531,7 @@ class SubEventUpdate(EventPermissionRequiredMixin, SubEventEditorMixin, UpdateVi
|
||||
|
||||
@transaction.atomic
|
||||
def form_valid(self, form):
|
||||
self.object = form.save()
|
||||
self.save_formset(self.object)
|
||||
self.save_cl_formset(self.object)
|
||||
self.save_meta()
|
||||
@@ -569,7 +570,7 @@ class SubEventUpdate(EventPermissionRequiredMixin, SubEventEditorMixin, UpdateVi
|
||||
f.subevent = self.object
|
||||
f.save()
|
||||
tickets.invalidate_cache.apply_async(kwargs={'event': self.request.event.pk})
|
||||
return super().form_valid(form)
|
||||
return HttpResponseRedirect(self.get_success_url())
|
||||
|
||||
def get_success_url(self) -> str:
|
||||
return reverse('control:event.subevents', kwargs={
|
||||
|
||||
@@ -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)
|
||||
@@ -117,12 +117,17 @@ class GroupConcat(Aggregate):
|
||||
template = "%(function)s(%(distinct)s%(field)s::text, '%(separator)s' ORDER BY %(field)s::text ASC)"
|
||||
else:
|
||||
template = "%(function)s(%(distinct)s%(field)s::text, '%(separator)s')"
|
||||
return super().as_sql(
|
||||
|
||||
template, params = super().as_sql(
|
||||
compiler, connection,
|
||||
function='string_agg',
|
||||
template=template,
|
||||
**extra_context,
|
||||
)
|
||||
if self.ordered:
|
||||
# ordered statement requires field parameters twice
|
||||
params = params + params
|
||||
return template, params
|
||||
|
||||
|
||||
class ReplicaRouter:
|
||||
|
||||
@@ -40,6 +40,8 @@ from urllib3.util.connection import (
|
||||
)
|
||||
from urllib3.util.timeout import _DEFAULT_TIMEOUT
|
||||
|
||||
_cgnat_net = ipaddress.ip_network('100.64.0.0/10')
|
||||
|
||||
|
||||
def monkeypatch_vobject_performance():
|
||||
"""
|
||||
@@ -152,6 +154,8 @@ def monkeypatch_urllib3_ssrf_protection():
|
||||
raise HTTPError(f"Request to local address {sa[0]} blocked")
|
||||
if ip_addr.is_private:
|
||||
raise HTTPError(f"Request to private address {sa[0]} blocked")
|
||||
if ip_addr in _cgnat_net:
|
||||
raise HTTPError(f"Request to RFC 6598 address {sa[0]} blocked")
|
||||
|
||||
sock = None
|
||||
try:
|
||||
|
||||
@@ -8,18 +8,17 @@ msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-04-28 09:03+0000\n"
|
||||
"PO-Revision-Date: 2025-04-08 18:00+0000\n"
|
||||
"Last-Translator: Menaouer Chaabi "
|
||||
"<98581961+DerJimno@users.noreply.github.com>\n"
|
||||
"Language-Team: Arabic <https://translate.pretix.eu/projects/pretix/pretix/ar/"
|
||||
">\n"
|
||||
"PO-Revision-Date: 2026-05-19 04:16+0000\n"
|
||||
"Last-Translator: Khalid Shaheen <khalid.shaheen@gmail.com>\n"
|
||||
"Language-Team: Arabic <https://translate.pretix.eu/projects/pretix/pretix/"
|
||||
"ar/>\n"
|
||||
"Language: ar\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 "
|
||||
"&& n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n"
|
||||
"X-Generator: Weblate 5.10.4\n"
|
||||
"X-Generator: Weblate 2026.5\n"
|
||||
|
||||
#: pretix/_base_settings.py:87
|
||||
msgid "English"
|
||||
@@ -39,7 +38,7 @@ msgstr "العربية"
|
||||
|
||||
#: pretix/_base_settings.py:91
|
||||
msgid "Basque"
|
||||
msgstr ""
|
||||
msgstr "الباسكية"
|
||||
|
||||
#: pretix/_base_settings.py:92
|
||||
msgid "Catalan"
|
||||
@@ -59,7 +58,7 @@ msgstr "التشيكية"
|
||||
|
||||
#: pretix/_base_settings.py:96
|
||||
msgid "Croatian"
|
||||
msgstr ""
|
||||
msgstr "الكرواتية"
|
||||
|
||||
#: pretix/_base_settings.py:97
|
||||
msgid "Danish"
|
||||
@@ -91,7 +90,7 @@ msgstr "اليونانية"
|
||||
|
||||
#: pretix/_base_settings.py:104
|
||||
msgid "Hebrew"
|
||||
msgstr ""
|
||||
msgstr "العبرية"
|
||||
|
||||
#: pretix/_base_settings.py:105
|
||||
msgid "Indonesian"
|
||||
@@ -103,7 +102,7 @@ msgstr "الإيطالية"
|
||||
|
||||
#: pretix/_base_settings.py:107
|
||||
msgid "Japanese"
|
||||
msgstr ""
|
||||
msgstr "اليابانية"
|
||||
|
||||
#: pretix/_base_settings.py:108
|
||||
msgid "Latvian"
|
||||
@@ -147,7 +146,7 @@ msgstr "الأسبانية"
|
||||
|
||||
#: pretix/_base_settings.py:118
|
||||
msgid "Spanish (Latin America)"
|
||||
msgstr ""
|
||||
msgstr "الإسبانية (أميركا اللاتينية)"
|
||||
|
||||
#: pretix/_base_settings.py:119
|
||||
msgid "Turkish"
|
||||
@@ -293,41 +292,28 @@ msgid "The bundled item must not have bundles on its own."
|
||||
msgstr "يجب ألا يحتوي العنصر المجمع على حزم بمفرده."
|
||||
|
||||
#: pretix/api/serializers/item.py:235
|
||||
#, fuzzy
|
||||
#| msgid "The payment is too late to be accepted."
|
||||
msgid "The program start must not be empty."
|
||||
msgstr "فات الأوان لقبول الدفع."
|
||||
msgstr "يجب ألا يكون موعد بداية البرنامج فارغ."
|
||||
|
||||
#: pretix/api/serializers/item.py:239
|
||||
#, fuzzy
|
||||
#| msgid "The payment is too late to be accepted."
|
||||
msgid "The program end must not be empty."
|
||||
msgstr "فات الأوان لقبول الدفع."
|
||||
msgstr "يجب ألا يكون موعد نهاية البرنامج فارغ."
|
||||
|
||||
#: pretix/api/serializers/item.py:242 pretix/base/models/items.py:2322
|
||||
#, fuzzy
|
||||
#| msgid "The maximum count needs to be greater than the minimum count."
|
||||
msgid "The program end must not be before the program start."
|
||||
msgstr "يجب أن يكون الحد الأقصى للعدد أكبر من الحد الأدنى للعد."
|
||||
msgstr "يجب ألا يكون موعد نهاية البرنامج سابق لموعد بدايته."
|
||||
|
||||
#: pretix/api/serializers/item.py:247 pretix/base/models/items.py:2316
|
||||
#, fuzzy
|
||||
#| msgid "You can not select a subevent if your event is not an event series."
|
||||
msgid "You cannot use program times on an event series."
|
||||
msgstr ""
|
||||
"لا يمكنك تحديد فعالية فرعية إذا لم تكن الفعالية الخاصة بك سلسلة فعاليات."
|
||||
msgstr "لا يمكنك استخدام أوقات البرنامج لسلسلة من الفعاليات."
|
||||
|
||||
#: pretix/api/serializers/item.py:337
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "Updating add-ons, bundles, or variations via PATCH/PUT is not supported. "
|
||||
#| "Please use the dedicated nested endpoint."
|
||||
msgid ""
|
||||
"Updating add-ons, bundles, program times or variations via PATCH/PUT is not "
|
||||
"supported. Please use the dedicated nested endpoint."
|
||||
msgstr ""
|
||||
"تحديث الإضافات، أو الحزم، أو المتغيرات عن طريق PATCH/PUT غير مدعوم. الرجاء "
|
||||
"استخدام نقطة نهاية المتداخلة المخصصة."
|
||||
"لا يوجد دعم لتحديث الإضافات، أو الحزم، أو مواعيد البرامج، أو المتغيرات عبر "
|
||||
"طريقتي PATCH/PUT. يُرجى استخدام نقطة النهاية المتداخلة المخصصة لذلك."
|
||||
|
||||
#: pretix/api/serializers/item.py:345
|
||||
msgid "Only admission products can currently be personalized."
|
||||
@@ -411,10 +397,8 @@ msgstr "توجد مسبقا بطاقة هدايا بنفس السر في حسا
|
||||
|
||||
#: pretix/api/serializers/organizer.py:495
|
||||
#: pretix/control/views/organizer.py:1039
|
||||
#, fuzzy
|
||||
#| msgid "Account information"
|
||||
msgid "Account invitation"
|
||||
msgstr "معلومات الحساب"
|
||||
msgstr "دعوة إلى فتح حساب"
|
||||
|
||||
#: pretix/api/serializers/organizer.py:516
|
||||
#: pretix/control/views/organizer.py:1138
|
||||
@@ -499,47 +483,35 @@ msgstr "تم تغيير عنوان اتصال الطلب"
|
||||
#: pretix/api/webhooks.py:331 pretix/base/notifications.py:281
|
||||
#: pretix/control/templates/pretixcontrol/event/mail.html:102
|
||||
msgid "Order changed"
|
||||
msgstr "تم تغيير الطلب."
|
||||
msgstr "تم تغيير الطلب"
|
||||
|
||||
#: pretix/api/webhooks.py:335
|
||||
#, fuzzy
|
||||
#| msgid "Enable payment method"
|
||||
msgid "Refund of payment created"
|
||||
msgstr "تمكين طريقة الدفع"
|
||||
msgstr "تم إنشاء طلب استرداد الدفع"
|
||||
|
||||
#: pretix/api/webhooks.py:339 pretix/base/notifications.py:293
|
||||
msgid "External refund of payment"
|
||||
msgstr "استرداد الدفع الخارجي"
|
||||
msgstr "استرداد خارجي للمدفوعات"
|
||||
|
||||
#: pretix/api/webhooks.py:343
|
||||
#, fuzzy
|
||||
#| msgid "Text (requested by user)"
|
||||
msgid "Refund of payment requested by customer"
|
||||
msgstr "النص (عن طريق المستخدم المطلوب)"
|
||||
msgstr "استرداد المبلغ بناءً على طلب العميل"
|
||||
|
||||
#: pretix/api/webhooks.py:347
|
||||
#, fuzzy
|
||||
#| msgid "Payment completed."
|
||||
msgid "Refund of payment completed"
|
||||
msgstr "تم السداد."
|
||||
msgstr "تم إتمام استرداد الدفع"
|
||||
|
||||
#: pretix/api/webhooks.py:351
|
||||
#, fuzzy
|
||||
#| msgid "Refund {local_id} has been canceled."
|
||||
msgid "Refund of payment canceled"
|
||||
msgstr "تم إلغاء استرداد {local_id}."
|
||||
msgstr "إلغاء استرداد الدفع"
|
||||
|
||||
#: pretix/api/webhooks.py:355
|
||||
#, fuzzy
|
||||
#| msgid "Refund order"
|
||||
msgid "Refund of payment failed"
|
||||
msgstr "أجل استرداد"
|
||||
msgstr "فشل استرداد الدفع"
|
||||
|
||||
#: pretix/api/webhooks.py:359
|
||||
#, fuzzy
|
||||
#| msgid "Payment confirmation date"
|
||||
msgid "Payment confirmed"
|
||||
msgstr "تاريخ الدفع تأكيدا"
|
||||
msgstr "تم تأكيد الدفع"
|
||||
|
||||
#: pretix/api/webhooks.py:363
|
||||
msgid "Order approved"
|
||||
@@ -550,14 +522,12 @@ msgid "Order denied"
|
||||
msgstr "تم رفض الطلب"
|
||||
|
||||
#: pretix/api/webhooks.py:371
|
||||
#, fuzzy
|
||||
#| msgid "Order denied"
|
||||
msgid "Order deleted"
|
||||
msgstr "تم رفض الطلب"
|
||||
msgstr "تم حذف الطلب"
|
||||
|
||||
#: pretix/api/webhooks.py:375
|
||||
msgid "Ticket checked in"
|
||||
msgstr "تم تسجيل التذكرة"
|
||||
msgstr "تم تسجيل دخول التذكرة"
|
||||
|
||||
#: pretix/api/webhooks.py:379
|
||||
msgid "Ticket check-in reverted"
|
||||
@@ -572,10 +542,8 @@ msgid "Event details changed"
|
||||
msgstr "تم تغيير تفاصيل الفعالية"
|
||||
|
||||
#: pretix/api/webhooks.py:391
|
||||
#, fuzzy
|
||||
#| msgid "Event date"
|
||||
msgid "Event deleted"
|
||||
msgstr "تاريخ الفعالية"
|
||||
msgstr "تم حذف الفعالية"
|
||||
|
||||
#: pretix/api/webhooks.py:395
|
||||
msgctxt "subevent"
|
||||
@@ -593,58 +561,44 @@ msgid "Event series date deleted"
|
||||
msgstr "تم حذف تاريخ سلسلة الفعاليات"
|
||||
|
||||
#: pretix/api/webhooks.py:407
|
||||
#, fuzzy
|
||||
#| msgid "Product name"
|
||||
msgid "Product changed"
|
||||
msgstr "اسم المنتج"
|
||||
msgstr "تم تغيير المنتج"
|
||||
|
||||
#: pretix/api/webhooks.py:408
|
||||
msgid ""
|
||||
"This includes product added or deleted and changes to nested objects like "
|
||||
"variations or bundles."
|
||||
msgstr ""
|
||||
"يشمل ذلك المنتجات التي تمت إضافتها أو حذفها، والتغييرات التي طرأت على "
|
||||
"الكائنات المتداخلة، مثل المتغيرات أو الحزم."
|
||||
|
||||
#: pretix/api/webhooks.py:413
|
||||
#, fuzzy
|
||||
#| msgid "Shop not live"
|
||||
msgid "Shop taken live"
|
||||
msgstr "تسوق لا يعيش"
|
||||
msgstr "تم إطلاق المتجر"
|
||||
|
||||
#: pretix/api/webhooks.py:417
|
||||
#, fuzzy
|
||||
#| msgid "The shop has been taken offline."
|
||||
msgid "Shop taken offline"
|
||||
msgstr "وقد اتخذت المحل حاليا."
|
||||
msgstr "تم إيقاف المتجر مؤقتاً"
|
||||
|
||||
#: pretix/api/webhooks.py:421
|
||||
#, fuzzy
|
||||
#| msgid "The order has been reactivated."
|
||||
msgid "Test-Mode of shop has been activated"
|
||||
msgstr "تم إعادة تنشيط الطلب."
|
||||
msgstr "تم تفعيل وضع الاختبار للمتجر"
|
||||
|
||||
#: pretix/api/webhooks.py:425
|
||||
#, fuzzy
|
||||
#| msgid "The order has been reactivated."
|
||||
msgid "Test-Mode of shop has been deactivated"
|
||||
msgstr "تم إعادة تنشيط الطلب."
|
||||
msgstr "تم إلغاء تفعيل وضع الاختبار للمتجر"
|
||||
|
||||
#: pretix/api/webhooks.py:429
|
||||
#, fuzzy
|
||||
#| msgid "Waiting list entry"
|
||||
msgid "Waiting list entry added"
|
||||
msgstr "دخول قائمة الانتظار"
|
||||
msgstr "تم إضافة قيد إلى قائمة الانتظار"
|
||||
|
||||
#: pretix/api/webhooks.py:433
|
||||
#, fuzzy
|
||||
#| msgid "Waiting list entry"
|
||||
msgid "Waiting list entry changed"
|
||||
msgstr "دخول قائمة الانتظار"
|
||||
msgstr "تم تغيير قيد قائمة الانتظار"
|
||||
|
||||
#: pretix/api/webhooks.py:437
|
||||
#, fuzzy
|
||||
#| msgid "Waiting list entry"
|
||||
msgid "Waiting list entry deleted"
|
||||
msgstr "دخول قائمة الانتظار"
|
||||
msgstr "تم حذف قيد قائمة الانتظار"
|
||||
|
||||
#: pretix/api/webhooks.py:441
|
||||
#, fuzzy
|
||||
|
||||
@@ -8,8 +8,8 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-04-28 09:03+0000\n"
|
||||
"PO-Revision-Date: 2025-09-29 07:39+0000\n"
|
||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||
"PO-Revision-Date: 2026-04-30 18:00+0000\n"
|
||||
"Last-Translator: Paul Berschick <paul@plainschwarz.com>\n"
|
||||
"Language-Team: Catalan <https://translate.pretix.eu/projects/pretix/pretix/"
|
||||
"ca/>\n"
|
||||
"Language: ca\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.13.3\n"
|
||||
"X-Generator: Weblate 5.17\n"
|
||||
|
||||
#: pretix/_base_settings.py:87
|
||||
msgid "English"
|
||||
@@ -14132,13 +14132,13 @@ msgid "Contact:"
|
||||
msgstr "Adreça de contacte"
|
||||
|
||||
#: pretix/base/templates/pretixbase/email/order_details.html:54
|
||||
#, fuzzy, python-format
|
||||
#| msgid ""
|
||||
#| "You are receiving this email because you placed an order for {event}."
|
||||
#, python-format
|
||||
msgid ""
|
||||
"You are receiving this email because you placed an order for "
|
||||
"<strong>%(event)s</strong>."
|
||||
msgstr "Heu rebut aquest correu perquè heu fet una comanda per {event}."
|
||||
msgstr ""
|
||||
"Has rebut aquest correu perquè has fet una inscripció a <strong>%(event)s</"
|
||||
"strong>."
|
||||
|
||||
#: pretix/base/templates/pretixbase/email/order_details.html:93
|
||||
#: pretix/control/templates/pretixcontrol/organizers/customer.html:23
|
||||
@@ -24790,7 +24790,7 @@ msgstr "Data d'inici de l'esdeveniment"
|
||||
#: pretix/control/templates/pretixcontrol/order/index.html:465
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:55
|
||||
msgid "Voucher code used:"
|
||||
msgstr ""
|
||||
msgstr "Codi de descompte utilitzat:"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/order/index.html:467
|
||||
#, fuzzy, python-format
|
||||
@@ -35206,11 +35206,9 @@ msgstr ""
|
||||
|
||||
#: pretix/presale/forms/renderers.py:66
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_voucher_form.html:14
|
||||
#, fuzzy
|
||||
#| msgid "expired"
|
||||
msgctxt "form"
|
||||
msgid "required"
|
||||
msgstr "expirat"
|
||||
msgstr "obligatori"
|
||||
|
||||
#: pretix/presale/ical.py:87 pretix/presale/ical.py:146
|
||||
#: pretix/presale/ical.py:182
|
||||
@@ -35399,10 +35397,8 @@ msgid "We're now trying to book these add-ons for you!"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/checkout_addons.html:28
|
||||
#, fuzzy
|
||||
#| msgid "Meta information"
|
||||
msgid "Additional options for"
|
||||
msgstr "Informació meta"
|
||||
msgstr "Opcions addicionals per a"
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/checkout_addons.html:60
|
||||
#, fuzzy
|
||||
@@ -35754,12 +35750,11 @@ msgstr[0] "Heu de triar només una opció d'aquesta categoria."
|
||||
msgstr[1] "Heu de triar %(min_count)s d'aquesta categoria."
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html:26
|
||||
#, fuzzy, python-format
|
||||
#| msgid "You can choose up to %(max_count)s options from this category."
|
||||
#, python-format
|
||||
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] "Podeu triar fins a %(max_count)s opcions d'aquesta categoria."
|
||||
msgstr[1] "Podeu triar fins a %(max_count)s opcions d'aquesta categoria."
|
||||
msgstr[0] "Pots triar una opció d'aquesta categoria."
|
||||
msgstr[1] "Pots escollir fins a %(max_count)s opcions d’aquesta categoria."
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html:34
|
||||
#, python-format
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,8 +5,8 @@ msgstr ""
|
||||
"Project-Id-Version: 1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-04-28 09:03+0000\n"
|
||||
"PO-Revision-Date: 2026-04-28 09:22+0000\n"
|
||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||
"PO-Revision-Date: 2026-05-06 00:00+0000\n"
|
||||
"Last-Translator: Daniel Musketa <daniel@musketa.de>\n"
|
||||
"Language-Team: German <https://translate.pretix.eu/projects/pretix/pretix/"
|
||||
"de/>\n"
|
||||
"Language: de\n"
|
||||
@@ -14,7 +14,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.17\n"
|
||||
"X-Generator: Weblate 5.17.1\n"
|
||||
"X-Poedit-Bookmarks: -1,-1,904,-1,-1,-1,-1,-1,-1,-1\n"
|
||||
|
||||
#: pretix/_base_settings.py:87
|
||||
@@ -3072,7 +3072,7 @@ msgstr "Wertgutscheine"
|
||||
#: pretix/base/exporters/orderlist.py:1367
|
||||
msgid "Download a spreadsheet of all gift cards including their current value."
|
||||
msgstr ""
|
||||
"Tabelle (Excel oder CSV) mit allen Wertgutscheinen und deren aktuellen Wert."
|
||||
"Tabelle (Excel oder CSV) mit allen Wertgutscheinen und deren aktuellem Wert."
|
||||
|
||||
#: pretix/base/exporters/orderlist.py:1378
|
||||
msgid "Show value at"
|
||||
@@ -4971,7 +4971,7 @@ msgstr ""
|
||||
"Sollte kurz sein und darf nur Kleinbuchstaben, Zahlen, Bindestriche und "
|
||||
"Punkte enthalten. Muss unter Ihren Veranstaltungen einmalig sein. Wir "
|
||||
"empfehlen eine Abkürzung oder ein Datum mit unter 10 Zeichen, das man sich "
|
||||
"gut merken kann. Sie können jedoch auch einen zufälligen Wert verwendet. "
|
||||
"gut merken kann. Sie können jedoch auch einen zufälligen Wert verwenden. "
|
||||
"Dies wird z.B. in Links, Bestellnummern, Rechnungsnummern und "
|
||||
"Verwendungszwecken für Banküberweisungen benutzt."
|
||||
|
||||
@@ -7515,7 +7515,7 @@ msgid ""
|
||||
"You are already on this waiting list! We will notify you as soon as we have "
|
||||
"a ticket available for you."
|
||||
msgstr ""
|
||||
"Sie sind bereits auf der Warteliste! Wir benachrichtigen Sie sobald wir ein "
|
||||
"Sie sind bereits auf der Warteliste! Wir benachrichtigen Sie, sobald wir ein "
|
||||
"verfügbares Ticket für Sie haben."
|
||||
|
||||
#: pretix/base/notifications.py:192 pretix/control/navigation.py:205
|
||||
@@ -8671,8 +8671,8 @@ msgid ""
|
||||
"offline scanning – please refer to documentation or support for details)"
|
||||
msgstr ""
|
||||
"pretix Signaturverfahren 1 (für sehr große Veranstaltungen, ändert die "
|
||||
"Funktionsweise des Offline-Modus, bitte informiere dich in der Dokumentation "
|
||||
"oder beim Support)"
|
||||
"Funktionsweise des Offline-Modus, bitte informieren Sie sich in der "
|
||||
"Dokumentation oder beim Support)"
|
||||
|
||||
#: pretix/base/services/cancelevent.py:265
|
||||
#: pretix/base/services/cancelevent.py:351
|
||||
@@ -9579,8 +9579,8 @@ msgid ""
|
||||
"changes are still accurate and try again."
|
||||
msgstr ""
|
||||
"Diese Bestellung wurde zeitgleich von einem anderen Benutzer bearbeitet. "
|
||||
"Bitte prüfe, ob deine Änderungen immer noch zutreffend sind und probiere es "
|
||||
"erneut."
|
||||
"Bitte prüfen Sie, ob Ihre Änderungen immer noch zutreffend sind und "
|
||||
"probieren Sie es erneut."
|
||||
|
||||
#: pretix/base/services/orders.py:150
|
||||
msgid "Your cart is empty."
|
||||
@@ -11363,7 +11363,7 @@ msgid ""
|
||||
"If your event series has more than 50 dates in the future, only the month or "
|
||||
"week calendar can be used."
|
||||
msgstr ""
|
||||
"Wenn deine Veranstaltungsreihe mehr als 50 zukünftige Termine hat, kann nur "
|
||||
"Wenn Ihre Veranstaltungsreihe mehr als 50 zukünftige Termine hat, kann nur "
|
||||
"der Monats- oder Wochenkalender verwendet werden."
|
||||
|
||||
#: pretix/base/settings.py:1892
|
||||
@@ -11636,8 +11636,9 @@ msgid ""
|
||||
"set this to e.g. 10, they will only be able to choose values in increments "
|
||||
"of 10."
|
||||
msgstr ""
|
||||
"Standardmäßig können Kunden auf einen beliebigen Betrag verzichten. Wenn du "
|
||||
"diesen Wert z.B. auf 10 setzt, sind nur noch Werte im Abstand von 10 erlaubt."
|
||||
"Standardmäßig können Kunden auf einen beliebigen Betrag verzichten. Wenn Sie "
|
||||
"diesen Wert z.B. auf 10 setzen, sind nur noch Werte im Abstand von 10 "
|
||||
"erlaubt."
|
||||
|
||||
#: pretix/base/settings.py:2193
|
||||
msgid ""
|
||||
@@ -17304,7 +17305,7 @@ msgstr ""
|
||||
|
||||
#: pretix/control/forms/vouchers.py:448
|
||||
msgid "You need to specify as many seats as voucher codes."
|
||||
msgstr "Sie müssen genau so viele Sitze angeben, wie du Gutscheine erzeugst."
|
||||
msgstr "Sie müssen genau so viele Sitze angeben, wie Sie Gutscheine erzeugen."
|
||||
|
||||
#: pretix/control/forms/waitinglist.py:39
|
||||
msgid "Select a valid choice."
|
||||
@@ -20819,7 +20820,7 @@ msgid ""
|
||||
"event and only retain the financial information such as the number and type "
|
||||
"of tickets sold."
|
||||
msgstr ""
|
||||
"Sie können personenbezogene Daten wie Namen und E-Mail-Adressen aus deiner "
|
||||
"Sie können personenbezogene Daten wie Namen und E-Mail-Adressen aus Ihrer "
|
||||
"Veranstaltung entfernen und nur finanzielle Infos wie die Anzahl und Art der "
|
||||
"verkauften Tickets aufbewahren."
|
||||
|
||||
@@ -23019,7 +23020,7 @@ msgstr ""
|
||||
"Wenn Sie eine Gültigkeit in Tagen, Monaten oder Jahren angeben, endet die "
|
||||
"Gültigkeit immer am Ende eines vollen Tages (Mitternacht), plus der "
|
||||
"ausgewählten Anzahl an Minuten und Stunden. Der Starttermin wird in die "
|
||||
"Berechnung eingeschlossen, d.h. wenn Sie \"1 Tag\" auswählen ist das Ticket "
|
||||
"Berechnung eingeschlossen, d.h. wenn Sie \"1 Tag\" auswählen, ist das Ticket "
|
||||
"gültig bis zum Ende des Tages, an dem die Gültigkeit beginnt."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/item/index.html:254
|
||||
@@ -23452,7 +23453,7 @@ msgid ""
|
||||
"statistical data on customers who previously selected this option, and when "
|
||||
"such customers edit their answers, they need to select a different option."
|
||||
msgstr ""
|
||||
"Wenn du eine Antwortoption löschst, kannst du anschließend keine "
|
||||
"Wenn Sie eine Antwortoption löschen, können Sie anschließend keine "
|
||||
"statistischen Daten mehr zur Verwendung dieser Option abrufen und wenn "
|
||||
"Kunden, die diese Option gewählt haben, ihre Daten bearbeiten möchten, muss "
|
||||
"eine andere Option gewählt werden."
|
||||
@@ -25961,8 +25962,8 @@ msgid ""
|
||||
"Instead of an URL, you can also configure a text that will be shown within "
|
||||
"pretix. This will be ignored if a URL is configured."
|
||||
msgstr ""
|
||||
"Statt einer URL kannst du auch einen Text eingeben, der von pretix angezeigt "
|
||||
"wird. Dies wird ignoriert, wenn eine URL konfiguriert wurde."
|
||||
"Statt einer URL können Sie auch einen Text eingeben, der von pretix "
|
||||
"angezeigt wird. Dies wird ignoriert, wenn eine URL konfiguriert wurde."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/organizers/edit.html:229
|
||||
msgid "Barcode media"
|
||||
@@ -26128,7 +26129,7 @@ msgid ""
|
||||
"This feature allows you to configure acceptance of gift cards across "
|
||||
"multiple organizer accounts."
|
||||
msgstr ""
|
||||
"Diese Funktion erlaubt dir, die Akzeptanz von Wertgutscheinen über mehrere "
|
||||
"Diese Funktion erlaubt Ihnen, die Akzeptanz von Wertgutscheinen über mehrere "
|
||||
"Veranstalterkonten hinweg zu konfigurieren."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/organizers/giftcard_acceptance_list.html:18
|
||||
@@ -26168,7 +26169,7 @@ msgstr "Ablehnen"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/organizers/giftcard_acceptance_list.html:84
|
||||
msgid "Other organizers accepting gift cards from you"
|
||||
msgstr "Andere Veranstalter, die deine Wertgutscheine akzeptieren"
|
||||
msgstr "Andere Veranstalter, die Ihre Wertgutscheine akzeptieren"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/organizers/giftcard_acceptance_list.html:87
|
||||
msgid ""
|
||||
@@ -27156,9 +27157,9 @@ msgid ""
|
||||
"use pretixPRINT version %(print_version)s (or newer) or pretixSCAN Desktop "
|
||||
"version %(scan_version)s (or newer)."
|
||||
msgstr ""
|
||||
"Dieses Layout verwendet neue Funktionen. Wenn du mit einem Gerät druckst, "
|
||||
"stelle sicher, dass pretixPRINT-Version %(print_version)s (oder neuer) oder "
|
||||
"pretixSCAN Desktop Version %(scan_version)s (oder neuer) im Einsatz ist."
|
||||
"Dieses Layout verwendet neue Funktionen. Wenn Sie mit einem Gerät drucken, "
|
||||
"stellen Sie sicher, dass pretixPRINT-Version %(print_version)s (oder neuer) "
|
||||
"oder pretixSCAN Desktop Version %(scan_version)s (oder neuer) im Einsatz ist."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/pdf/placeholders.html:16
|
||||
msgid "Available placeholders"
|
||||
@@ -27234,8 +27235,8 @@ msgid ""
|
||||
"might be required to keep some of this data on file. You can therefore "
|
||||
"download the following file and store it in a safe place:"
|
||||
msgstr ""
|
||||
"Sie sind dabei, Daten unwiderruflich vom Server zu löschen, auch wenn du "
|
||||
"manche dieser Daten ggf. aus gesetzlichen Gründen noch aufheben musst. Wir "
|
||||
"Sie sind dabei, Daten unwiderruflich vom Server zu löschen, auch wenn Sie "
|
||||
"manche dieser Daten ggf. aus gesetzlichen Gründen noch aufheben müssen. Wir "
|
||||
"empfehlen daher, die folgende Datei herunterzuladen und sie sicher "
|
||||
"aufzubewahren:"
|
||||
|
||||
@@ -27284,7 +27285,7 @@ msgid ""
|
||||
"while to complete. We will inform you via email once it has been completed."
|
||||
msgstr ""
|
||||
"Abhängig von der Datenmenge in der Veranstaltung kann der folgende Schritt "
|
||||
"eine Weile dauern. Wir informieren dich per E-Mail, sobald er abgeschlossen "
|
||||
"eine Weile dauern. Wir informieren Sie per E-Mail, sobald er abgeschlossen "
|
||||
"ist."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/shredder/index.html:11
|
||||
@@ -32188,8 +32189,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Während die meisten Zahlungsmethoden keinen grundlosen Widerruf vorsehen, "
|
||||
"können SEPA-Lastschriften per Mausklick zurückgerufen werden. Aus diesem "
|
||||
"Grund - und abhängig von der Art Ihrer Veranstaltung - kann es notwendig "
|
||||
"sein SEPA-Lastschriften nicht anzubieten und damit das Risiko von "
|
||||
"Grund – und abhängig von der Art Ihrer Veranstaltung – kann es notwendig "
|
||||
"sein, SEPA-Lastschriften nicht anzubieten und damit das Risiko von "
|
||||
"kostspieligen Rücklastschriften zu vermeiden."
|
||||
|
||||
#: pretix/plugins/paypal2/payment.py:182
|
||||
@@ -33624,8 +33625,8 @@ msgid ""
|
||||
"Some payment methods might need to be enabled in the settings of your Stripe "
|
||||
"account before they work properly."
|
||||
msgstr ""
|
||||
"Manche Zahlungsmethoden müssen in den Einstellungen deines Stripe-Kontos "
|
||||
"aktiviert werden bevor sie funktionieren."
|
||||
"Manche Zahlungsmethoden müssen in den Einstellungen Ihres Stripe-Kontos "
|
||||
"aktiviert werden, bevor sie funktionieren."
|
||||
|
||||
#: pretix/plugins/stripe/payment.py:369 pretix/plugins/stripe/payment.py:1597
|
||||
msgid "Alipay"
|
||||
@@ -33644,8 +33645,8 @@ msgid ""
|
||||
"Some payment methods might need to be enabled in the settings of your Stripe "
|
||||
"account before work properly."
|
||||
msgstr ""
|
||||
"Manche Zahlungsmethoden müssen in den Einstellungen deines Stripe-Kontos "
|
||||
"aktiviert werden bevor sie funktionieren."
|
||||
"Manche Zahlungsmethoden müssen in den Einstellungen Ihres Stripe-Kontos "
|
||||
"aktiviert werden, bevor sie funktionieren."
|
||||
|
||||
#: pretix/plugins/stripe/payment.py:391
|
||||
msgid ""
|
||||
@@ -35338,7 +35339,7 @@ msgstr "Diese Zahlungsmethode unterstützt den Testmodus nicht."
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/checkout_payment.html:113
|
||||
msgid "If you continue, actual money might be transferred."
|
||||
msgstr "Wenn du fortfährst, wird möglicherweise echtes Geld transferiert."
|
||||
msgstr "Wenn Sie fortfahren, wird möglicherweise echtes Geld transferiert."
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/checkout_payment.html:124
|
||||
msgid "There are no payment providers enabled."
|
||||
@@ -35790,7 +35791,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Da Sie eine Firmenadresse eingegeben haben, wurde Ihr Preis aus dem Preis "
|
||||
"ohne Umsatzsteuer neu berechnet. Durch geänderte Rundung hat sich der "
|
||||
"Endpreis deiner Buchung minimal verändert."
|
||||
"Endpreis Ihrer Buchung minimal verändert."
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:516
|
||||
#, python-format
|
||||
@@ -36025,7 +36026,7 @@ msgid ""
|
||||
"the order clicked the link in the email they received to confirm the email "
|
||||
"address is valid."
|
||||
msgstr ""
|
||||
"Sie können Ihre Tickets hier herunterladen sobald die Person, die die "
|
||||
"Sie können Ihre Tickets hier herunterladen, sobald die Person, die die "
|
||||
"Bestellung aufgegeben hat, einen Link in der an sie geschickten E-Mail "
|
||||
"geklickt hat."
|
||||
|
||||
@@ -37011,10 +37012,10 @@ msgid ""
|
||||
"need the ticket any more, please be so kind and remove your ticket from the "
|
||||
"list so we can pass it on to the next person waiting as quickly as possible!"
|
||||
msgstr ""
|
||||
"Sie wurden von unserer Warteliste ausgewählt um ein Ticket zu erhalten. Wenn "
|
||||
"Sie das Ticket nicht mehr brauchen, helfen Sie uns indem Sie sich von der "
|
||||
"Warteliste entfernen, sodass wir das Ticket schnellstmöglich an die nächste "
|
||||
"wartende Person weitergeben können."
|
||||
"Sie wurden von unserer Warteliste ausgewählt, um ein Ticket zu erhalten. "
|
||||
"Wenn Sie das Ticket nicht mehr brauchen, helfen Sie uns, indem Sie sich von "
|
||||
"der Warteliste entfernen, sodass wir das Ticket schnellstmöglich an die "
|
||||
"nächste wartende Person weitergeben können."
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/waitinglist_remove.html:16
|
||||
msgctxt "waitinglist"
|
||||
@@ -37754,8 +37755,8 @@ msgstr ""
|
||||
#: pretix/presale/views/order.py:1198
|
||||
msgid "Please click the link we sent you via email to download your tickets."
|
||||
msgstr ""
|
||||
"Bitte klicke den Link, den wir dir per E-Mail geschickt haben, um deine "
|
||||
"Tickets herunterzuladen."
|
||||
"Bitte klicken Sie den Link, den wir Ihnen per E-Mail geschickt haben, um "
|
||||
"Ihre Tickets herunterzuladen."
|
||||
|
||||
#: pretix/presale/views/order.py:1689
|
||||
#, python-brace-format
|
||||
|
||||
@@ -8,8 +8,8 @@ msgstr ""
|
||||
"Project-Id-Version: 1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-04-28 09:03+0000\n"
|
||||
"PO-Revision-Date: 2026-04-28 09:22+0000\n"
|
||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||
"PO-Revision-Date: 2026-05-04 10:44+0000\n"
|
||||
"Last-Translator: Daniel Musketa <daniel@musketa.de>\n"
|
||||
"Language-Team: German (informal) <https://translate.pretix.eu/projects/"
|
||||
"pretix/pretix/de_Informal/>\n"
|
||||
"Language: de_Informal\n"
|
||||
@@ -829,7 +829,7 @@ msgid ""
|
||||
"Field \"{field_name}\" requires {required_input}, but only got "
|
||||
"{available_inputs}. Please check your {provider_name} settings."
|
||||
msgstr ""
|
||||
"Feld \"{field_name}\" erfordert {required_input}, aber hat nur "
|
||||
"Feld \"{field_name}\" erfordert {required_input}, aber nur "
|
||||
"{available_inputs} sind verfügbar. Bitte prüfe die Einstellungen für "
|
||||
"{provider_name}."
|
||||
|
||||
@@ -1529,8 +1529,8 @@ msgid ""
|
||||
"Only include invoices issued in this time frame. Note that the invoice date "
|
||||
"does not always correspond to the order or payment date."
|
||||
msgstr ""
|
||||
"Nur Rechnungen, die in diesem Zeitraum wurden. Achtung: Das Rechnungsdatum "
|
||||
"korrespondiert nicht zwingend zum Bestell- oder Zahlungsdatum."
|
||||
"Nur Rechnungen, die in diesem Zeitraum ausgestellt wurden. Achtung: Das "
|
||||
"Rechnungsdatum korrespondiert nicht zwingend zum Bestell- oder Zahlungsdatum."
|
||||
|
||||
#: pretix/base/exporters/events.py:47
|
||||
msgid "Event data"
|
||||
@@ -3540,7 +3540,7 @@ msgstr "Das eingegebene aktuelle Passwort war nicht korrekt."
|
||||
|
||||
#: pretix/base/forms/user.py:95
|
||||
msgid "Please choose a password different to your current one."
|
||||
msgstr "Bitte wählen ein anderes Passwort als das derzeitige."
|
||||
msgstr "Bitte wähle ein anderes Passwort als das derzeitige."
|
||||
|
||||
#: pretix/base/forms/user.py:105 pretix/presale/forms/customer.py:399
|
||||
#: pretix/presale/forms/customer.py:475
|
||||
@@ -3579,7 +3579,7 @@ msgid ""
|
||||
"up. Please note: to use literal \"{\" or \"}\", you need to double them as "
|
||||
"\"{{\" and \"}}\"."
|
||||
msgstr ""
|
||||
"Es ist ein Fehler in deine Platzhalter-Syntax. Bitte prüfe, dass die "
|
||||
"Es ist ein Fehler in deiner Platzhalter-Syntax. Bitte prüfe, dass die "
|
||||
"öffnenden \"{\" und schließenden \"}\" geschweiften Klammern zusammenpassen. "
|
||||
"Um die geschweiften Klammern \"{\" und \"}\" im erzeugten Text zu verwenden, "
|
||||
"müssen sie doppelt gesetzt werden als \"{{\" und \"}}\"."
|
||||
@@ -4217,7 +4217,7 @@ msgstr "Automatisch generieren"
|
||||
|
||||
#: pretix/base/modelimport_orders.py:496
|
||||
msgid "You cannot assign a position secret that already exists."
|
||||
msgstr "Sie können keinen Ticketcode verwenden, der bereits existiert."
|
||||
msgstr "Du kannst keinen Ticketcode verwenden, der bereits existiert."
|
||||
|
||||
#: pretix/base/modelimport_orders.py:528
|
||||
msgid "Please enter a valid language code."
|
||||
@@ -4971,7 +4971,7 @@ msgstr ""
|
||||
"Sollte kurz sein und darf nur Kleinbuchstaben, Zahlen, Bindestriche und "
|
||||
"Punkte enthalten. Muss unter deinen Veranstaltungen einmalig sein. Wir "
|
||||
"empfehlen eine Abkürzung oder ein Datum mit unter 10 Zeichen, das man sich "
|
||||
"gut merken kann. Du kannst jedoch auch einen zufälligen Wert verwendet. Dies "
|
||||
"gut merken kann. Du kannst jedoch auch einen zufälligen Wert verwenden. Dies "
|
||||
"wird z.B. in Links, Bestellnummern, Rechnungsnummern und Verwendungszwecken "
|
||||
"für Banküberweisungen benutzt."
|
||||
|
||||
@@ -6186,7 +6186,7 @@ msgstr "Minimaler Wert"
|
||||
#: pretix/base/models/items.py:1747 pretix/base/models/items.py:1750
|
||||
#: pretix/base/models/items.py:1754
|
||||
msgid "Currently not supported in our apps and during check-in"
|
||||
msgstr "Derzeit nicht von unseren Apps und während dem Check-In unterstützt"
|
||||
msgstr "Derzeit nicht von unseren Apps und beim Check-In unterstützt"
|
||||
|
||||
#: pretix/base/models/items.py:1737 pretix/base/models/items.py:1743
|
||||
#: pretix/base/models/items.py:1749
|
||||
@@ -6231,7 +6231,7 @@ msgstr "Ungültige Nummerneingabe."
|
||||
|
||||
#: pretix/base/models/items.py:1870 pretix/base/models/items.py:1894
|
||||
msgid "Please choose a later date."
|
||||
msgstr "Bitte wählen Sie ein späteres Datum."
|
||||
msgstr "Bitte wähle ein späteres Datum."
|
||||
|
||||
#: pretix/base/models/items.py:1872 pretix/base/models/items.py:1896
|
||||
msgid "Please choose an earlier date."
|
||||
@@ -6880,7 +6880,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Wenn du dies anschaltest, müssen alle Mitglieder entweder Zwei-Faktor-"
|
||||
"Authentifizierung einrichten oder das Team verlassen. Die Einstellung kann "
|
||||
"ein paar Minuten benötigen um für alle Benutzer aktiv zu werden."
|
||||
"ein paar Minuten benötigen, um für alle Benutzer aktiv zu werden."
|
||||
|
||||
#: pretix/base/models/organizer.py:384
|
||||
msgid "All event permissions"
|
||||
@@ -9015,7 +9015,7 @@ msgstr ""
|
||||
msgid "One of the products you selected can only be bought part of a bundle."
|
||||
msgstr ""
|
||||
"Eins der ausgewählten Produkte wird nicht einzeln verkauft, sondern nur als "
|
||||
"Teil fester Produktpaketen."
|
||||
"Teil fester Produktpakete."
|
||||
|
||||
#: pretix/base/services/cart.py:218
|
||||
msgid "Please select a valid seat."
|
||||
@@ -9912,7 +9912,7 @@ msgid ""
|
||||
"country is currently not available. We will therefore need to charge you the "
|
||||
"same tax rate as if you did not enter a VAT ID."
|
||||
msgstr ""
|
||||
"Die USt-ID-Nr. konnte nicht geprüft werden, da der Prüfdienst Ihres Landes "
|
||||
"Die USt-ID-Nr. konnte nicht geprüft werden, da der Prüfdienst deines Landes "
|
||||
"im Moment nicht verfügbar ist. Wir müssen daher den selben Steuersatz "
|
||||
"berechnen, wie wenn keine USt-ID-Nr. eingegeben worden wäre."
|
||||
|
||||
@@ -11535,8 +11535,8 @@ msgid ""
|
||||
"cancellation fee from the user."
|
||||
msgstr ""
|
||||
"Betrifft nur ausstehende Zahlungen, für kostenlose Bestellungen wird nie "
|
||||
"eine Stornogebühr erhoben. Bitte beachten Sie, dass Sie für das Eintreiben "
|
||||
"der Stornogebühr selbst verantwortlich sind."
|
||||
"eine Stornogebühr erhoben. Bitte beachte, dass du für das Eintreiben der "
|
||||
"Stornogebühr selbst verantwortlich bist."
|
||||
|
||||
#: pretix/base/settings.py:2073
|
||||
msgid "Charge payment, shipping and service fees"
|
||||
@@ -19340,8 +19340,8 @@ msgstr ""
|
||||
msgid ""
|
||||
"Please leave a short comment on what you did in the following admin sessions:"
|
||||
msgstr ""
|
||||
"Bitte hinterlassen Sie einen kurzen Kommentar, was Sie in diesen Admin-"
|
||||
"Sitzungen gemacht haben:"
|
||||
"Bitte hinterlasse einen kurzen Kommentar, was du in diesen Admin-Sitzungen "
|
||||
"gemacht hast:"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/base.html:376
|
||||
msgid "Read more"
|
||||
@@ -19427,8 +19427,7 @@ msgstr "im Entwicklermodus"
|
||||
#: 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 ""
|
||||
"Wenn dies länger als einige Minuten dauert, kontaktieren Sie uns bitte."
|
||||
msgstr "Wenn dies länger als einige Minuten dauert, kontaktiere uns bitte."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/boxoffice/payment.html:4
|
||||
#: pretix/control/templates/pretixcontrol/organizers/devices.html:75
|
||||
@@ -22988,7 +22987,7 @@ msgstr ""
|
||||
"Wenn du eine Gültigkeit in Tagen, Monaten oder Jahren angibst, endet die "
|
||||
"Gültigkeit immer am Ende eines vollen Tages (Mitternacht), plus der "
|
||||
"ausgewählten Anzahl an Minuten und Stunden. Der Starttermin wird in die "
|
||||
"Berechnung eingeschlossen, d.h. wenn Sie \"1 Tag\" auswählen ist das Ticket "
|
||||
"Berechnung eingeschlossen, d.h. wenn du \"1 Tag\" auswählst, ist das Ticket "
|
||||
"gültig bis zum Ende des Tages, an dem die Gültigkeit beginnt."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/item/index.html:254
|
||||
@@ -23662,8 +23661,8 @@ msgid ""
|
||||
"Are you sure you want to generate a new client secret for the application "
|
||||
"<strong>%(application)s</strong>?"
|
||||
msgstr ""
|
||||
"Möchten Sie wirklich einen neuen Schlüssel für die App "
|
||||
"<strong>%(application)s</strong> generieren?"
|
||||
"Möchtest du wirklich einen neuen Schlüssel für die App <strong>%(application)"
|
||||
"s</strong> generieren?"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/oauth/app_rollkeys.html:15
|
||||
msgid "Roll secret"
|
||||
@@ -23720,7 +23719,7 @@ msgstr "Bestellung freigeben"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/order/approve.html:10
|
||||
msgid "Do you really want to approve this order?"
|
||||
msgstr "Möchten Sie diese Bestellung wirklich freigeben?"
|
||||
msgstr "Möchtest du diese Bestellung wirklich freigeben?"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/order/approve.html:20
|
||||
#: pretix/control/templates/pretixcontrol/order/cancel.html:46
|
||||
@@ -25689,8 +25688,8 @@ msgid ""
|
||||
"Download an app that is compatible with pretix. For example, our check-in "
|
||||
"app <strong>pretixSCAN</strong> is available on all major platforms."
|
||||
msgstr ""
|
||||
"Laden Sie eine App herunter, die mit pretix kompatibel ist, wie z.B. unsere "
|
||||
"Check-in-App <strong>pretixSCAN</strong>."
|
||||
"Lade eine App herunter, die mit pretix kompatibel ist, wie z.B. unsere Check-"
|
||||
"in-App <strong>pretixSCAN</strong>."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/organizers/device_connect.html:14
|
||||
msgid "Download pretixSCAN"
|
||||
@@ -25917,7 +25916,7 @@ msgid ""
|
||||
"target=\"_blank\">our documentation</a>."
|
||||
msgstr ""
|
||||
"In einigen Regionen, einschließlich der Europäischen Union, bist du "
|
||||
"verpflichtet, Informationen über die Barrierefreiheit Ihres Ticketshops zu "
|
||||
"verpflichtet, Informationen über die Barrierefreiheit deines Ticketshops zu "
|
||||
"veröffentlichen. Du findest eine Vorlage in <a href=\"https://docs.pretix.eu/"
|
||||
"de/trust/accessibility/\" target=\"_blank\">unserer Dokumentation</a>."
|
||||
|
||||
@@ -26023,7 +26022,7 @@ msgstr "Station löschen:"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/organizers/gate_delete.html:8
|
||||
msgid "Are you sure you want to delete the gate?"
|
||||
msgstr "Möchten Sie die Station wirklich löschen?"
|
||||
msgstr "Möchtest du die Station wirklich löschen?"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/organizers/gate_edit.html:6
|
||||
msgid "Gate:"
|
||||
@@ -26389,7 +26388,7 @@ msgid ""
|
||||
"The plugin \"%(name)s\" is enabled for your organizer account, but also "
|
||||
"needs to be enabled for the specific events you want to use it with."
|
||||
msgstr ""
|
||||
"Die Erweiterung \"%(name)s\" ist für Ihr Veranstalterkonto aktiv, muss "
|
||||
"Die Erweiterung \"%(name)s\" ist für dein Veranstalterkonto aktiv, muss "
|
||||
"jedoch auch für die einzelnen Veranstaltungen aktiviert werden, für die sie "
|
||||
"benutzt werden soll."
|
||||
|
||||
@@ -26936,7 +26935,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Dieser Editor wurde mit aktuellen Versionen von Google Chrome, Mozilla "
|
||||
"Firefox und Opera getestet. Andere Browser, besonders Internet Explorer oder "
|
||||
"Microsoft Edge, haben möglicherweise Probleme Ihr Hintergrund-PDF korrekt "
|
||||
"Microsoft Edge, haben möglicherweise Probleme, dein Hintergrund-PDF korrekt "
|
||||
"darzustellen oder die richtigen Schriftarten zu laden."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/pdf/index.html:207
|
||||
@@ -27121,9 +27120,9 @@ msgid ""
|
||||
"use pretixPRINT version %(print_version)s (or newer) or pretixSCAN Desktop "
|
||||
"version %(scan_version)s (or newer)."
|
||||
msgstr ""
|
||||
"Dieses Layout verwendet neue Funktionen. Wenn Sie mit einem Gerät drucken, "
|
||||
"stellen Sie sicher, dass pretixPRINT-Version %(print_version)s (oder neuer) "
|
||||
"oder pretixSCAN Desktop Version %(scan_version)s (oder neuer) im Einsatz ist."
|
||||
"Dieses Layout verwendet neue Funktionen. Wenn du mit einem Gerät druckst, "
|
||||
"stell sicher, dass pretixPRINT-Version %(print_version)s (oder neuer) oder "
|
||||
"pretixSCAN Desktop Version %(scan_version)s (oder neuer) im Einsatz ist."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/pdf/placeholders.html:16
|
||||
msgid "Available placeholders"
|
||||
@@ -27397,7 +27396,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Du kannst entweder eine oder mehrere Check-in-Listen für jeden Termin deiner "
|
||||
"Veranstaltungsreihe einzeln anlegen oder nur eine Check-in-Liste für alle "
|
||||
"Ihre Termine verwenden und den Einlass über Check-in-Regeln limitieren. "
|
||||
"deine Termine verwenden und den Einlass über Check-in-Regeln limitieren. "
|
||||
"Welcher Ansatz besser geeignet ist, hängt von mehreren Faktoren ab, wie z.B. "
|
||||
"der Menge an Terminen in deiner Veranstaltungsreihe. Für Reihen mit weniger "
|
||||
"als einem Termin pro Tag sind einzelne Check-in-Listen in der Regel "
|
||||
@@ -29233,7 +29232,7 @@ msgid ""
|
||||
"participants won't be able to buy the bundle unless you remove this item "
|
||||
"from it."
|
||||
msgstr ""
|
||||
"Sie haben dieses Produkt deaktiviert, obwohl es Teil eines Paketes ist. "
|
||||
"Du hast dieses Produkt deaktiviert, obwohl es Teil eines Paketes ist. "
|
||||
"Solange dies so ist, kann auch das Paket nicht mehr gekauft werden."
|
||||
|
||||
#: pretix/control/views/item.py:1622
|
||||
@@ -30396,7 +30395,7 @@ msgid ""
|
||||
"and requested a reset of the credentials."
|
||||
msgstr ""
|
||||
"Ein Zwei-Faktor-Notfall-Token wurde von einem Systemadministrator generiert. "
|
||||
"Dies passiert üblicherweise, wenn du den Zugriff auf Ihren zweiten Faktor "
|
||||
"Dies passiert üblicherweise, wenn du den Zugriff auf deinen zweiten Faktor "
|
||||
"verloren und ein Zurücksetzen der Zugangsdaten angefordert hast."
|
||||
|
||||
#: pretix/control/views/users.py:169
|
||||
@@ -32143,8 +32142,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Während die meisten Zahlungsmethoden keinen grundlosen Widerruf vorsehen, "
|
||||
"können SEPA-Lastschriften per Mausklick zurückgerufen werden. Aus diesem "
|
||||
"Grund - und abhängig von der Art Ihrer Veranstaltung - kann es notwendig "
|
||||
"sein SEPA-Lastschriften nicht anzubieten und damit das Risiko von "
|
||||
"Grund – und abhängig von der Art deiner Veranstaltung – kann es notwendig "
|
||||
"sein, SEPA-Lastschriften nicht anzubieten und damit das Risiko von "
|
||||
"kostspieligen Rücklastschriften zu vermeiden."
|
||||
|
||||
#: pretix/plugins/paypal2/payment.py:182
|
||||
@@ -32474,7 +32473,7 @@ msgid ""
|
||||
"Your PayPal account is now connected to pretix. You can change the settings "
|
||||
"in detail below."
|
||||
msgstr ""
|
||||
"Ihr PayPal-Konto ist nun mit pretix verbunden. Auf dieser Seite können Sie "
|
||||
"Dein PayPal-Konto ist nun mit pretix verbunden. Auf dieser Seite kannst du "
|
||||
"die Einstellungen im Detail anpassen."
|
||||
|
||||
#: pretix/plugins/pretixdroid/apps.py:30 pretix/plugins/pretixdroid/apps.py:33
|
||||
@@ -33051,7 +33050,8 @@ msgstr "Neue Regel erstellen"
|
||||
#: pretix/plugins/sendmail/templates/pretixplugins/sendmail/rule_update.html:10
|
||||
msgid "Scheduled emails are not sent as long as your ticket shop is offline."
|
||||
msgstr ""
|
||||
"Geplante E-Mails werden nicht verschickt, solange Ihr Ticketshop offline ist."
|
||||
"Geplante E-Mails werden nicht verschickt, solange dein Ticketshop offline "
|
||||
"ist."
|
||||
|
||||
#: pretix/plugins/sendmail/templates/pretixplugins/sendmail/rule_create.html:49
|
||||
#: pretix/plugins/sendmail/templates/pretixplugins/sendmail/rule_update.html:63
|
||||
@@ -33383,7 +33383,7 @@ msgid ""
|
||||
"methods such as iDEAL, Alipay,and many more."
|
||||
msgstr ""
|
||||
"Akzeptiere Zahlungen über Stripe, einen weltweit beliebten "
|
||||
"Zahlungsdienstleister. PayPal unterstützt Zahlungen per Kreditkarte sowie "
|
||||
"Zahlungsdienstleister. Stripe unterstützt Zahlungen per Kreditkarte sowie "
|
||||
"viele lokale Zahlungsarten wie z.B. iDEAL, Alipay, und viele mehr."
|
||||
|
||||
#: pretix/plugins/stripe/forms.py:40
|
||||
@@ -33968,8 +33968,8 @@ msgid ""
|
||||
"Pay by bank allows you to authorize a secure Open Banking payment from your "
|
||||
"banking app. Currently available only with a UK bank account."
|
||||
msgstr ""
|
||||
"Zahlung per Onlinebanking erlaubt die sichere Zahlung über Ihre Banking-App. "
|
||||
"Derzeit nur für britische Bankkonten verfügbar."
|
||||
"Zahlung per Onlinebanking erlaubt die sichere Zahlung über deine Banking-"
|
||||
"App. Derzeit nur für britische Bankkonten verfügbar."
|
||||
|
||||
#: pretix/plugins/stripe/payment.py:1886
|
||||
msgid "PayPal via Stripe"
|
||||
@@ -34356,7 +34356,7 @@ msgid ""
|
||||
"Your Stripe account is now connected to pretix. You can change the settings "
|
||||
"in detail below."
|
||||
msgstr ""
|
||||
"Ihr Stripe-Konto ist nun mit pretix verbunden. Auf dieser Seite können Sie "
|
||||
"Dein Stripe-Konto ist nun mit pretix verbunden. Auf dieser Seite kannst du "
|
||||
"die Einstellungen im Detail anpassen."
|
||||
|
||||
#: pretix/plugins/stripe/views.py:488
|
||||
|
||||
@@ -8,8 +8,8 @@ msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-04-28 09:04+0000\n"
|
||||
"PO-Revision-Date: 2026-03-17 14:30+0000\n"
|
||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||
"PO-Revision-Date: 2026-05-04 07:42+0000\n"
|
||||
"Last-Translator: Martin Gross <gross@rami.io>\n"
|
||||
"Language-Team: German (informal) <https://translate.pretix.eu/projects/"
|
||||
"pretix/pretix-js/de_Informal/>\n"
|
||||
"Language: de_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.17\n"
|
||||
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
|
||||
@@ -428,8 +428,7 @@ msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:276
|
||||
msgid "If this takes longer than a few minutes, please contact us."
|
||||
msgstr ""
|
||||
"Wenn dies länger als einige Minuten dauert, kontaktieren Sie uns bitte."
|
||||
msgstr "Wenn dies länger als einige Minuten dauert, kontaktiere uns bitte."
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:331
|
||||
msgid "Close message"
|
||||
|
||||
@@ -8,8 +8,8 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-04-28 09:03+0000\n"
|
||||
"PO-Revision-Date: 2026-04-17 03:00+0000\n"
|
||||
"Last-Translator: Tim <plicnetwork@gmail.com>\n"
|
||||
"PO-Revision-Date: 2026-05-01 21:00+0000\n"
|
||||
"Last-Translator: Paul Berschick <paul@plainschwarz.com>\n"
|
||||
"Language-Team: Spanish <https://translate.pretix.eu/projects/pretix/pretix/"
|
||||
"es/>\n"
|
||||
"Language: es\n"
|
||||
@@ -8231,10 +8231,8 @@ msgstr ""
|
||||
"2x complemento 2"
|
||||
|
||||
#: pretix/base/pdf.py:383
|
||||
#, fuzzy
|
||||
#| msgid "List of Add-Ons"
|
||||
msgid "List of Checked-In Add-Ons"
|
||||
msgstr "Lista de add-ons"
|
||||
msgstr "Lista de complementos registrados"
|
||||
|
||||
#: pretix/base/pdf.py:390 pretix/control/forms/filter.py:1537
|
||||
#: pretix/control/forms/filter.py:1539
|
||||
@@ -9237,10 +9235,8 @@ msgid "Czech National Bank"
|
||||
msgstr "Banco Nacional Checo"
|
||||
|
||||
#: pretix/base/services/currencies.py:41
|
||||
#, fuzzy
|
||||
#| msgid "Czech National Bank"
|
||||
msgid "National Bank of Poland"
|
||||
msgstr "Banco Nacional Checo"
|
||||
msgstr "Banco Nacional de Polonia"
|
||||
|
||||
#: pretix/base/services/export.py:95 pretix/base/services/export.py:155
|
||||
msgid ""
|
||||
@@ -10336,16 +10332,12 @@ msgstr ""
|
||||
"importe de la factura no esté en CZK."
|
||||
|
||||
#: pretix/base/settings.py:577 pretix/base/settings.py:586
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "Based on Czech National Bank daily rates, whenever the invoice amount is "
|
||||
#| "not in CZK."
|
||||
msgid ""
|
||||
"Based on National Bank of Poland daily rates, whenever the invoice amount is "
|
||||
"not in PLN."
|
||||
msgstr ""
|
||||
"Basado en las tarifas diarias del Banco Nacional Checo, siempre que el "
|
||||
"importe de la factura no esté en CZK."
|
||||
"Según los tipos de cambio diarios del Banco Nacional de Polonia, siempre que "
|
||||
"el importe de la factura no esté en PLN."
|
||||
|
||||
#: pretix/base/settings.py:597
|
||||
msgid "Require invoice address"
|
||||
@@ -16430,10 +16422,8 @@ msgid "Allow to overbook quotas when performing this operation"
|
||||
msgstr "Permitir sobrevender cupos cuando se realice esta operación"
|
||||
|
||||
#: pretix/control/forms/orders.py:335
|
||||
#, fuzzy
|
||||
#| msgid "Number of orders"
|
||||
msgid "Number of products to add"
|
||||
msgstr "Número de pedidos"
|
||||
msgstr "Número de productos que se van a añadir"
|
||||
|
||||
#: pretix/control/forms/orders.py:344
|
||||
msgid "Add-on to"
|
||||
@@ -16465,10 +16455,8 @@ msgstr ""
|
||||
"defecto del producto"
|
||||
|
||||
#: pretix/control/forms/orders.py:441
|
||||
#, fuzzy
|
||||
#| msgid "You can not select the same seat multiple times."
|
||||
msgid "You can not choose a seat when adding multiple products at once."
|
||||
msgstr "No se puede seleccionar la misma butaca varias veces."
|
||||
msgstr "No es posible elegir un asiento al añadir varios productos a la vez."
|
||||
|
||||
#: pretix/control/forms/orders.py:478 pretix/control/forms/orders.py:482
|
||||
#: pretix/control/forms/orders.py:510 pretix/control/forms/orders.py:552
|
||||
@@ -17076,7 +17064,7 @@ msgstr "Día de fin de semana"
|
||||
#: pretix/control/forms/subevents.py:106
|
||||
msgctxt "subevent"
|
||||
msgid "Skip dates that overlap with any existing date"
|
||||
msgstr ""
|
||||
msgstr "Omite las fechas que coincidan con alguna fecha ya existente"
|
||||
|
||||
#: pretix/control/forms/subevents.py:109
|
||||
msgctxt "subevent"
|
||||
@@ -17086,6 +17074,11 @@ msgid ""
|
||||
"This respects even inactive dates and works best if all dates have both a "
|
||||
"start and end time."
|
||||
msgstr ""
|
||||
"Esto puede resultar útil si todas tus citas tienen lugar en el mismo lugar y "
|
||||
"no se deben crear citas repetidas que entren en conflicto con eventos "
|
||||
"especiales ya existentes. Esta función tiene en cuenta incluso las citas "
|
||||
"inactivas y funciona mejor si todas las citas tienen una hora de inicio y "
|
||||
"una hora de finalización."
|
||||
|
||||
#: pretix/control/forms/subevents.py:128
|
||||
msgid "Keep the current values"
|
||||
@@ -24364,7 +24357,7 @@ msgstr "Escaneo de entrada: %(date)s"
|
||||
#: pretix/control/templates/pretixcontrol/order/index.html:465
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:55
|
||||
msgid "Voucher code used:"
|
||||
msgstr "Código de vale de compra utilizado:"
|
||||
msgstr "Código de descuento utilizado:"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/order/index.html:467
|
||||
#, python-format
|
||||
@@ -30280,6 +30273,8 @@ msgstr "No cree más de 100.000 fechas a la vez."
|
||||
#: pretix/control/views/subevents.py:966
|
||||
msgid "All dates would be skipped because they conflict with existing dates."
|
||||
msgstr ""
|
||||
"Se omitirían todas las fechas, ya que entran en conflicto con las ya "
|
||||
"existentes."
|
||||
|
||||
#: pretix/control/views/subevents.py:1102
|
||||
#, python-brace-format
|
||||
@@ -34927,7 +34922,7 @@ msgstr "tiene errores"
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_voucher_form.html:14
|
||||
msgctxt "form"
|
||||
msgid "required"
|
||||
msgstr "requerido"
|
||||
msgstr "obligatorio"
|
||||
|
||||
#: pretix/presale/ical.py:87 pretix/presale/ical.py:146
|
||||
#: pretix/presale/ical.py:182
|
||||
|
||||
@@ -4,16 +4,16 @@ msgstr ""
|
||||
"Project-Id-Version: 1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-04-28 09:03+0000\n"
|
||||
"PO-Revision-Date: 2026-03-31 17:00+0000\n"
|
||||
"Last-Translator: CVZ-es <damien.bremont@casadevelazquez.org>\n"
|
||||
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix/fr/"
|
||||
">\n"
|
||||
"PO-Revision-Date: 2026-05-08 04:00+0000\n"
|
||||
"Last-Translator: corentin-spec <corentin@spectentaculaire.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.17.1\n"
|
||||
|
||||
#: pretix/_base_settings.py:87
|
||||
msgid "English"
|
||||
@@ -8279,10 +8279,8 @@ msgstr ""
|
||||
"2x Add-on 2"
|
||||
|
||||
#: pretix/base/pdf.py:383
|
||||
#, fuzzy
|
||||
#| msgid "List of Add-Ons"
|
||||
msgid "List of Checked-In Add-Ons"
|
||||
msgstr "Liste des Addons"
|
||||
msgstr "Liste des modules complémentaires enregistrés"
|
||||
|
||||
#: pretix/base/pdf.py:390 pretix/control/forms/filter.py:1537
|
||||
#: pretix/control/forms/filter.py:1539
|
||||
@@ -9296,10 +9294,8 @@ msgid "Czech National Bank"
|
||||
msgstr "Banque nationale tchèque"
|
||||
|
||||
#: pretix/base/services/currencies.py:41
|
||||
#, fuzzy
|
||||
#| msgid "Czech National Bank"
|
||||
msgid "National Bank of Poland"
|
||||
msgstr "Banque nationale tchèque"
|
||||
msgstr "Banque nationale de Pologne"
|
||||
|
||||
#: pretix/base/services/export.py:95 pretix/base/services/export.py:155
|
||||
msgid ""
|
||||
@@ -10396,16 +10392,12 @@ msgstr ""
|
||||
"que le montant de la facture n’est pas en CZK."
|
||||
|
||||
#: pretix/base/settings.py:577 pretix/base/settings.py:586
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "Based on Czech National Bank daily rates, whenever the invoice amount is "
|
||||
#| "not in CZK."
|
||||
msgid ""
|
||||
"Based on National Bank of Poland daily rates, whenever the invoice amount is "
|
||||
"not in PLN."
|
||||
msgstr ""
|
||||
"Sur la base des taux journaliers de la Banque nationale tchèque, chaque fois "
|
||||
"que le montant de la facture n’est pas en CZK."
|
||||
"Sur la base des taux quotidiens de la Banque nationale de Pologne, lorsque "
|
||||
"le montant de la facture n'est pas libellé en PLN."
|
||||
|
||||
#: pretix/base/settings.py:597
|
||||
msgid "Require invoice address"
|
||||
@@ -16569,10 +16561,8 @@ msgstr ""
|
||||
"Autoriser à surbooker les quotas lors de l’exécution de cette opération"
|
||||
|
||||
#: pretix/control/forms/orders.py:335
|
||||
#, fuzzy
|
||||
#| msgid "Number of orders"
|
||||
msgid "Number of products to add"
|
||||
msgstr "Nombre de commandes"
|
||||
msgstr "Nombre d'articles à ajouter"
|
||||
|
||||
#: pretix/control/forms/orders.py:344
|
||||
msgid "Add-on to"
|
||||
@@ -16604,10 +16594,10 @@ msgstr ""
|
||||
"produit"
|
||||
|
||||
#: pretix/control/forms/orders.py:441
|
||||
#, fuzzy
|
||||
#| msgid "You can not select the same seat multiple times."
|
||||
msgid "You can not choose a seat when adding multiple products at once."
|
||||
msgstr "Vous ne pouvez pas sélectionner le même siège plusieurs fois."
|
||||
msgstr ""
|
||||
"Vous ne pouvez pas choisir de siège lorsque vous ajoutez plusieurs produits "
|
||||
"à la fois."
|
||||
|
||||
#: pretix/control/forms/orders.py:478 pretix/control/forms/orders.py:482
|
||||
#: pretix/control/forms/orders.py:510 pretix/control/forms/orders.py:552
|
||||
@@ -17230,7 +17220,7 @@ msgstr "Jour de fin de semaine"
|
||||
#: pretix/control/forms/subevents.py:106
|
||||
msgctxt "subevent"
|
||||
msgid "Skip dates that overlap with any existing date"
|
||||
msgstr ""
|
||||
msgstr "Ignorer les dates qui coïncident avec une date existante"
|
||||
|
||||
#: pretix/control/forms/subevents.py:109
|
||||
msgctxt "subevent"
|
||||
@@ -17240,6 +17230,11 @@ msgid ""
|
||||
"This respects even inactive dates and works best if all dates have both a "
|
||||
"start and end time."
|
||||
msgstr ""
|
||||
"Cette option peut s'avérer utile si toutes vos dates se déroulent au même "
|
||||
"endroit et qu'aucune date ne doit être créée en double et entrer en conflit "
|
||||
"avec des événements spéciaux existants. Elle prend en compte même les dates "
|
||||
"inactives et fonctionne mieux si toutes les dates comportent à la fois une "
|
||||
"heure de début et une heure de fin."
|
||||
|
||||
#: pretix/control/forms/subevents.py:128
|
||||
msgid "Keep the current values"
|
||||
@@ -30512,6 +30507,8 @@ msgstr "Veuillez ne pas créer plus de 100.000 dates à la fois."
|
||||
#: pretix/control/views/subevents.py:966
|
||||
msgid "All dates would be skipped because they conflict with existing dates."
|
||||
msgstr ""
|
||||
"Toutes ces dates seraient ignorées car elles entrent en conflit avec des "
|
||||
"dates déjà existantes."
|
||||
|
||||
#: pretix/control/views/subevents.py:1102
|
||||
#, python-brace-format
|
||||
@@ -37887,7 +37884,7 @@ msgstr "Votre panier a été mis à jour."
|
||||
|
||||
#: pretix/presale/views/cart.py:525 pretix/presale/views/cart.py:551
|
||||
msgid "Your cart is now empty."
|
||||
msgstr "Votre panier à été vidé."
|
||||
msgstr "Votre panier a été vidé."
|
||||
|
||||
#: pretix/presale/views/cart.py:584
|
||||
msgid ""
|
||||
|
||||
@@ -8,8 +8,8 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-04-28 09:03+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-05-12 04:00+0000\n"
|
||||
"Last-Translator: Stefano Campus <stefano.campus@regione.piemonte.it>\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.17.1\n"
|
||||
|
||||
#: pretix/_base_settings.py:87
|
||||
msgid "English"
|
||||
@@ -8598,6 +8598,8 @@ msgid ""
|
||||
"Includes the ability to give someone (including oneself) additional "
|
||||
"permissions."
|
||||
msgstr ""
|
||||
"Consente di assegnare a qualcuno (compreso se stessi) autorizzazioni "
|
||||
"aggiuntive."
|
||||
|
||||
#: pretix/base/permissions.py:298 pretix/control/navigation.py:608
|
||||
#: pretix/control/templates/pretixcontrol/organizers/customers.html:6
|
||||
@@ -8609,13 +8611,14 @@ msgstr "Indirizzi Email (file di testo)"
|
||||
#: pretix/base/permissions.py:310 pretix/control/navigation.py:666
|
||||
#: pretix/control/navigation.py:673
|
||||
msgid "Devices"
|
||||
msgstr ""
|
||||
msgstr "Dispositivi"
|
||||
|
||||
#: pretix/base/permissions.py:316
|
||||
msgid ""
|
||||
"Includes the ability to give access to events and data oneself does not have "
|
||||
"access to."
|
||||
msgstr ""
|
||||
"Consente di concedere l'accesso a eventi e dati a cui non si ha accesso."
|
||||
|
||||
#: pretix/base/permissions.py:321
|
||||
#, fuzzy
|
||||
@@ -8747,6 +8750,8 @@ msgid ""
|
||||
"Some products can no longer be purchased and have been removed from your "
|
||||
"cart for the following reason: %s"
|
||||
msgstr ""
|
||||
"Alcuni prodotti non sono più disponibili e sono stati rimossi dal tuo "
|
||||
"carrello per il seguente motivo: %s"
|
||||
|
||||
#: pretix/base/services/cart.py:117
|
||||
msgid ""
|
||||
@@ -10060,6 +10065,8 @@ msgid ""
|
||||
"For business customers, compute taxes based on net total. For individuals, "
|
||||
"use line-based rounding"
|
||||
msgstr ""
|
||||
"Per i clienti aziendali, calcolare le imposte sul totale al netto. Per i "
|
||||
"privati, applicare l'arrotondamento per singola voce"
|
||||
|
||||
#: pretix/base/settings.py:85
|
||||
msgid "Compute taxes based on net total with stable gross prices"
|
||||
@@ -10096,6 +10103,8 @@ msgstr ""
|
||||
#: pretix/base/settings.py:190
|
||||
msgid "Require login to access order confirmation pages"
|
||||
msgstr ""
|
||||
"È necessario effettuare l'accesso per visualizzare le pagine di conferma "
|
||||
"dell'ordine"
|
||||
|
||||
#: pretix/base/settings.py:191
|
||||
msgid ""
|
||||
|
||||
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-04-28 09:03+0000\n"
|
||||
"PO-Revision-Date: 2026-04-20 08:07+0000\n"
|
||||
"PO-Revision-Date: 2026-05-12 06:34+0000\n"
|
||||
"Last-Translator: Yasunobu YesNo Kawaguchi <kawaguti@gmail.com>\n"
|
||||
"Language-Team: Japanese <https://translate.pretix.eu/projects/pretix/pretix/"
|
||||
"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.17\n"
|
||||
"X-Generator: Weblate 5.17.1\n"
|
||||
|
||||
#: pretix/_base_settings.py:87
|
||||
msgid "English"
|
||||
@@ -4441,7 +4441,7 @@ msgstr "全ての製品(新規に作成されたものを含む)"
|
||||
#: pretix/base/models/checkin.py:56 pretix/plugins/badges/exporters.py:436
|
||||
#: pretix/plugins/checkinlists/exporters.py:854
|
||||
msgid "Limit to products"
|
||||
msgstr "商品の上限"
|
||||
msgstr "対象製品を限定"
|
||||
|
||||
#: pretix/base/models/checkin.py:60
|
||||
msgid ""
|
||||
@@ -6896,8 +6896,8 @@ msgstr "免税輸出品目、VAT非課税"
|
||||
msgctxt "tax_code"
|
||||
msgid "VAT exempt for EEA intra-community supply of goods and services"
|
||||
msgstr ""
|
||||
"EEA(欧州経済領域)域内事業者間取引における商品・サービス供給のVAT(付加価値"
|
||||
"税)免税"
|
||||
"EEA(欧州経済領域)域内事業者間取引における物品・サービス供給のVAT(付加価値税)"
|
||||
"免税"
|
||||
|
||||
#: pretix/base/models/tax.py:186
|
||||
msgid "Special cases"
|
||||
@@ -7144,10 +7144,10 @@ msgid ""
|
||||
"usages in some cases can be lower than this limit, e.g. in case of "
|
||||
"cancellations."
|
||||
msgstr ""
|
||||
"複数(1を超える値)に設定した場合、バウチャーは初回使用時にこの数の製品と引き"
|
||||
"換える必要があります。その後の使用では、より少ない数の製品にも使用できます。"
|
||||
"ただし、キャンセルなどの場合には、合計使用回数がこの制限を下回ることがありま"
|
||||
"す。"
|
||||
"1より大きい値を設定すると、バウチャーを最初に使用する際に、この数の製品に対し"
|
||||
"て引き換える必要があります。2回目以降の使用では、これより少ない数の製品に対し"
|
||||
"ても使用できます。この場合、キャンセルなどにより、合計の使用回数がこの上限を"
|
||||
"下回ることがある点にご注意ください。"
|
||||
|
||||
#: pretix/base/models/vouchers.py:217
|
||||
msgid ""
|
||||
@@ -8059,10 +8059,8 @@ msgstr ""
|
||||
"2x アドオン2"
|
||||
|
||||
#: pretix/base/pdf.py:383
|
||||
#, fuzzy
|
||||
#| msgid "List of Add-Ons"
|
||||
msgid "List of Checked-In Add-Ons"
|
||||
msgstr "アドオンのリスト"
|
||||
msgstr "チェックイン済みアドオン一覧"
|
||||
|
||||
#: pretix/base/pdf.py:390 pretix/control/forms/filter.py:1537
|
||||
#: pretix/control/forms/filter.py:1539
|
||||
@@ -9019,10 +9017,8 @@ msgid "Czech National Bank"
|
||||
msgstr "チェコ国立銀行"
|
||||
|
||||
#: pretix/base/services/currencies.py:41
|
||||
#, fuzzy
|
||||
#| msgid "Czech National Bank"
|
||||
msgid "National Bank of Poland"
|
||||
msgstr "チェコ国立銀行"
|
||||
msgstr "ポーランド国立銀行"
|
||||
|
||||
#: pretix/base/services/export.py:95 pretix/base/services/export.py:155
|
||||
msgid ""
|
||||
@@ -10068,14 +10064,10 @@ msgid ""
|
||||
msgstr "チェコ国立銀行の日次レートに基づいて、請求書の金額がCZK以外の場合。"
|
||||
|
||||
#: pretix/base/settings.py:577 pretix/base/settings.py:586
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "Based on Czech National Bank daily rates, whenever the invoice amount is "
|
||||
#| "not in CZK."
|
||||
msgid ""
|
||||
"Based on National Bank of Poland daily rates, whenever the invoice amount is "
|
||||
"not in PLN."
|
||||
msgstr "チェコ国立銀行の日次レートに基づいて、請求書の金額がCZK以外の場合。"
|
||||
msgstr "ポーランド国立銀行の日次レートに基づいて、請求書の金額がPLN以外の場合。"
|
||||
|
||||
#: pretix/base/settings.py:597
|
||||
msgid "Require invoice address"
|
||||
@@ -15956,10 +15948,8 @@ msgid "Allow to overbook quotas when performing this operation"
|
||||
msgstr "この操作を実行する際にクォータの超過予約を許可する"
|
||||
|
||||
#: pretix/control/forms/orders.py:335
|
||||
#, fuzzy
|
||||
#| msgid "Number of orders"
|
||||
msgid "Number of products to add"
|
||||
msgstr "注文数"
|
||||
msgstr "追加する製品の数"
|
||||
|
||||
#: pretix/control/forms/orders.py:344
|
||||
msgid "Add-on to"
|
||||
@@ -15991,10 +15981,8 @@ msgstr ""
|
||||
"さい"
|
||||
|
||||
#: pretix/control/forms/orders.py:441
|
||||
#, fuzzy
|
||||
#| msgid "You can not select the same seat multiple times."
|
||||
msgid "You can not choose a seat when adding multiple products at once."
|
||||
msgstr "同じ席を複数回選択することはできません。"
|
||||
msgstr "複数の製品を同時に追加する場合、座席を選択することはできません。"
|
||||
|
||||
#: pretix/control/forms/orders.py:478 pretix/control/forms/orders.py:482
|
||||
#: pretix/control/forms/orders.py:510 pretix/control/forms/orders.py:552
|
||||
@@ -16596,7 +16584,7 @@ msgstr "週末の日"
|
||||
#: pretix/control/forms/subevents.py:106
|
||||
msgctxt "subevent"
|
||||
msgid "Skip dates that overlap with any existing date"
|
||||
msgstr ""
|
||||
msgstr "既存の日付と重複する日付をスキップする"
|
||||
|
||||
#: pretix/control/forms/subevents.py:109
|
||||
msgctxt "subevent"
|
||||
@@ -16606,6 +16594,9 @@ msgid ""
|
||||
"This respects even inactive dates and works best if all dates have both a "
|
||||
"start and end time."
|
||||
msgstr ""
|
||||
"これは、すべての日付が同じ場所で行われ、既存の特別イベントと競合して重複した"
|
||||
"日付が作成されない場合に有用です。これは、非アクティブな日付さえも尊重し、す"
|
||||
"べての日付に開始時刻と終了時刻の両方がある場合に最も効果的です。"
|
||||
|
||||
#: pretix/control/forms/subevents.py:128
|
||||
msgid "Keep the current values"
|
||||
@@ -22963,12 +22954,11 @@ msgid ""
|
||||
"total number of tickets sold and the number of a specific ticket type at the "
|
||||
"same time."
|
||||
msgstr ""
|
||||
"製品を実際に利用可能にするには、クォータも必要です。クォータは、pretixが製品"
|
||||
"のインスタンスをいくつ販売するかを定義します。これにより、イベントが無制限の"
|
||||
"参加者を受け入れることができるか、参加者数が制限されるかを設定できます。より"
|
||||
"複雑な要件を満たすために、製品を複数のクォータに割り当てることができます。例"
|
||||
"えば、販売されるチケットの総数と特定のチケット種別の数を同時に制限したい場合"
|
||||
"などです。"
|
||||
"製品を実際に販売可能にするには、クォータも必要です。クォータは、製品をどれだ"
|
||||
"けpretixが販売するかを定義します。これにより、イベントの参加者数を無制限にす"
|
||||
"るか、人数を制限するかを設定できます。1つの製品を複数のクォータに割り当てるこ"
|
||||
"とで、より複雑な要件にも対応できます。たとえば、販売するチケットの総数と特定"
|
||||
"のチケット種別の数を同時に制限したい場合などです。"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/items/quotas.html:25
|
||||
msgid "Your search did not match any quotas."
|
||||
@@ -23685,7 +23675,7 @@ msgid ""
|
||||
"this product was part of the discount calculation for a different product in "
|
||||
"this order."
|
||||
msgstr ""
|
||||
"自動割引によりこの商品の価格が引き下げられたか、同じ注文内の別の商品に対する"
|
||||
"自動割引によりこの製品の価格が引き下げられたか、同じ注文内の別の製品に対する"
|
||||
"割引計算の対象になっています。"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/order/index.html:496
|
||||
@@ -29425,7 +29415,7 @@ msgstr "一度に10万以上の日付を作成しないでください。"
|
||||
|
||||
#: pretix/control/views/subevents.py:966
|
||||
msgid "All dates would be skipped because they conflict with existing dates."
|
||||
msgstr ""
|
||||
msgstr "すべての日付は、既存の日付と衝突するため、スキップされます。"
|
||||
|
||||
#: pretix/control/views/subevents.py:1102
|
||||
#, python-brace-format
|
||||
@@ -34613,7 +34603,7 @@ msgid ""
|
||||
"changed because they are not on sale:"
|
||||
msgstr ""
|
||||
"このアドオンカテゴリで選択された製品の中には、現在セール対象外のため変更でき"
|
||||
"ない商品があります:"
|
||||
"ない製品があります:"
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html:392
|
||||
msgid "There are no add-ons available for this product."
|
||||
|
||||
@@ -8,8 +8,8 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-04-28 09:04+0000\n"
|
||||
"PO-Revision-Date: 2026-03-23 21:00+0000\n"
|
||||
"Last-Translator: Hijiri Umemoto <hijiri@umemoto.org>\n"
|
||||
"PO-Revision-Date: 2026-05-12 06:34+0000\n"
|
||||
"Last-Translator: Yasunobu YesNo Kawaguchi <kawaguti@gmail.com>\n"
|
||||
"Language-Team: Japanese <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
"js/ja/>\n"
|
||||
"Language: 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.17.1\n"
|
||||
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
|
||||
@@ -572,7 +572,7 @@ msgstr "未入場"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:289
|
||||
msgid "Error: Product not found!"
|
||||
msgstr "エラー:商品が見つかりません!"
|
||||
msgstr "エラー:製品が見つかりません!"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:296
|
||||
msgid "Error: Variation not found!"
|
||||
@@ -743,7 +743,7 @@ msgstr "カートの有効期限が近づいています。"
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:62
|
||||
msgid "The items in your cart are reserved for you for one minute."
|
||||
msgid_plural "The items in your cart are reserved for you for {num} minutes."
|
||||
msgstr[0] "カート内の商品はあと {num} 分間確保されています。"
|
||||
msgstr[0] "カート内の製品はあと {num} 分間確保されています。"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:83
|
||||
msgid "Your cart has expired."
|
||||
@@ -754,7 +754,7 @@ msgid ""
|
||||
"The items in your cart are no longer reserved for you. You can still "
|
||||
"complete your order as long as they're available."
|
||||
msgstr ""
|
||||
"カート内の商品の確保期限が切れました。在庫があれば、このまま注文を完了するこ"
|
||||
"カート内の製品の確保期限が切れました。在庫があれば、このまま注文を完了するこ"
|
||||
"とができます。"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:87
|
||||
@@ -987,7 +987,7 @@ msgid ""
|
||||
"You currently have an active cart for this event. If you select more "
|
||||
"products, they will be added to your existing cart."
|
||||
msgstr ""
|
||||
"このイベントのカートに商品が入っています。商品を追加すると、既存のカートに追"
|
||||
"このイベントのカートに製品が入っています。製品を追加すると、既存のカートに追"
|
||||
"加されます。"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:57
|
||||
|
||||
@@ -8,8 +8,8 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-04-28 09:03+0000\n"
|
||||
"PO-Revision-Date: 2024-05-10 15:47+0000\n"
|
||||
"Last-Translator: Martin Gross <gross@rami.io>\n"
|
||||
"PO-Revision-Date: 2026-05-02 23:00+0000\n"
|
||||
"Last-Translator: Daniel Musketa <daniel@musketa.de>\n"
|
||||
"Language-Team: Norwegian Bokmål <https://translate.pretix.eu/projects/pretix/"
|
||||
"pretix/nb_NO/>\n"
|
||||
"Language: nb_NO\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.4.3\n"
|
||||
"X-Generator: Weblate 5.17\n"
|
||||
|
||||
#: pretix/_base_settings.py:87
|
||||
msgid "English"
|
||||
@@ -37,11 +37,11 @@ msgstr "Arabisk"
|
||||
|
||||
#: pretix/_base_settings.py:91
|
||||
msgid "Basque"
|
||||
msgstr ""
|
||||
msgstr "Baskisk"
|
||||
|
||||
#: pretix/_base_settings.py:92
|
||||
msgid "Catalan"
|
||||
msgstr ""
|
||||
msgstr "Katalansk"
|
||||
|
||||
#: pretix/_base_settings.py:93
|
||||
msgid "Chinese (simplified)"
|
||||
@@ -57,7 +57,7 @@ msgstr "Tsjekkisk"
|
||||
|
||||
#: pretix/_base_settings.py:96
|
||||
msgid "Croatian"
|
||||
msgstr ""
|
||||
msgstr "Kroatisk"
|
||||
|
||||
#: pretix/_base_settings.py:97
|
||||
msgid "Danish"
|
||||
@@ -89,7 +89,7 @@ msgstr "Gresk"
|
||||
|
||||
#: pretix/_base_settings.py:104
|
||||
msgid "Hebrew"
|
||||
msgstr ""
|
||||
msgstr "Hebraisk"
|
||||
|
||||
#: pretix/_base_settings.py:105
|
||||
msgid "Indonesian"
|
||||
@@ -101,7 +101,7 @@ msgstr "Italiensk"
|
||||
|
||||
#: pretix/_base_settings.py:107
|
||||
msgid "Japanese"
|
||||
msgstr ""
|
||||
msgstr "Japansk"
|
||||
|
||||
#: pretix/_base_settings.py:108
|
||||
msgid "Latvian"
|
||||
@@ -133,11 +133,11 @@ msgstr "Russisk"
|
||||
|
||||
#: pretix/_base_settings.py:115
|
||||
msgid "Slovak"
|
||||
msgstr ""
|
||||
msgstr "Slovakisk"
|
||||
|
||||
#: pretix/_base_settings.py:116
|
||||
msgid "Swedish"
|
||||
msgstr ""
|
||||
msgstr "Svensk"
|
||||
|
||||
#: pretix/_base_settings.py:117
|
||||
msgid "Spanish"
|
||||
@@ -145,7 +145,7 @@ msgstr "Spansk"
|
||||
|
||||
#: pretix/_base_settings.py:118
|
||||
msgid "Spanish (Latin America)"
|
||||
msgstr ""
|
||||
msgstr "Spansk (Latin-Amerika)"
|
||||
|
||||
#: pretix/_base_settings.py:119
|
||||
msgid "Turkish"
|
||||
|
||||
@@ -7,16 +7,16 @@ msgstr ""
|
||||
"Project-Id-Version: 1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-04-28 09:03+0000\n"
|
||||
"PO-Revision-Date: 2026-03-31 17:00+0000\n"
|
||||
"PO-Revision-Date: 2026-05-05 13:26+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"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 5.16.2\n"
|
||||
"X-Generator: Weblate 5.17.1\n"
|
||||
|
||||
#: pretix/_base_settings.py:87
|
||||
msgid "English"
|
||||
@@ -8209,10 +8209,8 @@ msgstr ""
|
||||
"2x Add-on 2"
|
||||
|
||||
#: pretix/base/pdf.py:383
|
||||
#, fuzzy
|
||||
#| msgid "List of Add-Ons"
|
||||
msgid "List of Checked-In Add-Ons"
|
||||
msgstr "Lijst met add-ons"
|
||||
msgstr "Lijst met ingecheckte add-ons"
|
||||
|
||||
#: pretix/base/pdf.py:390 pretix/control/forms/filter.py:1537
|
||||
#: pretix/control/forms/filter.py:1539
|
||||
@@ -9206,10 +9204,8 @@ msgid "Czech National Bank"
|
||||
msgstr "Tsjechische Nationale Bank"
|
||||
|
||||
#: pretix/base/services/currencies.py:41
|
||||
#, fuzzy
|
||||
#| msgid "Czech National Bank"
|
||||
msgid "National Bank of Poland"
|
||||
msgstr "Tsjechische Nationale Bank"
|
||||
msgstr "Nationale Bank van Polen"
|
||||
|
||||
#: pretix/base/services/export.py:95 pretix/base/services/export.py:155
|
||||
msgid ""
|
||||
@@ -10296,16 +10292,12 @@ msgstr ""
|
||||
"wanneer het factuurbedrag niet in CZK is."
|
||||
|
||||
#: pretix/base/settings.py:577 pretix/base/settings.py:586
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "Based on Czech National Bank daily rates, whenever the invoice amount is "
|
||||
#| "not in CZK."
|
||||
msgid ""
|
||||
"Based on National Bank of Poland daily rates, whenever the invoice amount is "
|
||||
"not in PLN."
|
||||
msgstr ""
|
||||
"Gebaseerd op de dagelijkse tarieven van de Tsjechische Nationale Bank, "
|
||||
"wanneer het factuurbedrag niet in CZK is."
|
||||
"Op basis van de dagkoersen van de Nationale Bank van Polen, wanneer het "
|
||||
"factuurbedrag niet in PLN is uitgedrukt."
|
||||
|
||||
#: pretix/base/settings.py:597
|
||||
msgid "Require invoice address"
|
||||
@@ -16375,10 +16367,8 @@ msgid "Allow to overbook quotas when performing this operation"
|
||||
msgstr "Quota overboeken bij deze handeling toestaan"
|
||||
|
||||
#: pretix/control/forms/orders.py:335
|
||||
#, fuzzy
|
||||
#| msgid "Number of orders"
|
||||
msgid "Number of products to add"
|
||||
msgstr "Aantal bestellingen"
|
||||
msgstr "Aantal producten dat moet worden toegevoegd"
|
||||
|
||||
#: pretix/control/forms/orders.py:344
|
||||
msgid "Add-on to"
|
||||
@@ -16410,10 +16400,9 @@ msgstr ""
|
||||
"standaardprijs van het product"
|
||||
|
||||
#: pretix/control/forms/orders.py:441
|
||||
#, fuzzy
|
||||
#| msgid "You can not select the same seat multiple times."
|
||||
msgid "You can not choose a seat when adding multiple products at once."
|
||||
msgstr "U kunt dezelfde stoel niet meerdere keren kiezen."
|
||||
msgstr ""
|
||||
"U kunt geen zitplaats kiezen wanneer u meerdere producten tegelijk toevoegt."
|
||||
|
||||
#: pretix/control/forms/orders.py:478 pretix/control/forms/orders.py:482
|
||||
#: pretix/control/forms/orders.py:510 pretix/control/forms/orders.py:552
|
||||
@@ -17027,7 +17016,7 @@ msgstr "Weekenddag"
|
||||
#: pretix/control/forms/subevents.py:106
|
||||
msgctxt "subevent"
|
||||
msgid "Skip dates that overlap with any existing date"
|
||||
msgstr ""
|
||||
msgstr "Sla data over die samenvallen met bestaande data"
|
||||
|
||||
#: pretix/control/forms/subevents.py:109
|
||||
msgctxt "subevent"
|
||||
@@ -17037,6 +17026,11 @@ msgid ""
|
||||
"This respects even inactive dates and works best if all dates have both a "
|
||||
"start and end time."
|
||||
msgstr ""
|
||||
"Dit kan handig zijn als alle afspraken op dezelfde locatie plaatsvinden en "
|
||||
"je wilt voorkomen dat terugkerende afspraken in conflict komen met bestaande "
|
||||
"speciale afspraken. Hierbij wordt ook rekening gehouden met inactieve "
|
||||
"afspraken en het werkt het beste als alle afspraken een begin- en eindtijd "
|
||||
"hebben."
|
||||
|
||||
#: pretix/control/forms/subevents.py:128
|
||||
msgid "Keep the current values"
|
||||
@@ -30168,6 +30162,8 @@ msgstr "U kunt maximaal 100.000 datums tegelijk aanmaken."
|
||||
#: pretix/control/views/subevents.py:966
|
||||
msgid "All dates would be skipped because they conflict with existing dates."
|
||||
msgstr ""
|
||||
"Alle afspraken zouden worden overgeslagen omdat ze samenvallen met bestaande "
|
||||
"afspraken."
|
||||
|
||||
#: pretix/control/views/subevents.py:1102
|
||||
#, python-brace-format
|
||||
|
||||
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-04-28 09:03+0000\n"
|
||||
"PO-Revision-Date: 2026-04-08 18:00+0000\n"
|
||||
"PO-Revision-Date: 2026-05-05 13:26+0000\n"
|
||||
"Last-Translator: Ruud Hendrickx <ruud@leckxicon.eu>\n"
|
||||
"Language-Team: Dutch (Belgium) <https://translate.pretix.eu/projects/pretix/"
|
||||
"pretix/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.17.1\n"
|
||||
|
||||
#: pretix/_base_settings.py:87
|
||||
msgid "English"
|
||||
@@ -8204,10 +8204,8 @@ msgstr ""
|
||||
"2x Add-on 2"
|
||||
|
||||
#: pretix/base/pdf.py:383
|
||||
#, fuzzy
|
||||
#| msgid "List of Add-Ons"
|
||||
msgid "List of Checked-In Add-Ons"
|
||||
msgstr "Lijst met add-ons"
|
||||
msgstr "Lijst met ingecheckte add-ons"
|
||||
|
||||
#: pretix/base/pdf.py:390 pretix/control/forms/filter.py:1537
|
||||
#: pretix/control/forms/filter.py:1539
|
||||
@@ -9202,10 +9200,8 @@ msgid "Czech National Bank"
|
||||
msgstr "Tsjechische Nationale Bank"
|
||||
|
||||
#: pretix/base/services/currencies.py:41
|
||||
#, fuzzy
|
||||
#| msgid "Czech National Bank"
|
||||
msgid "National Bank of Poland"
|
||||
msgstr "Tsjechische Nationale Bank"
|
||||
msgstr "Nationale Bank van Polen"
|
||||
|
||||
#: pretix/base/services/export.py:95 pretix/base/services/export.py:155
|
||||
msgid ""
|
||||
@@ -10293,16 +10289,12 @@ msgstr ""
|
||||
"wanneer het factuurbedrag niet in CZK is."
|
||||
|
||||
#: pretix/base/settings.py:577 pretix/base/settings.py:586
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "Based on Czech National Bank daily rates, whenever the invoice amount is "
|
||||
#| "not in CZK."
|
||||
msgid ""
|
||||
"Based on National Bank of Poland daily rates, whenever the invoice amount is "
|
||||
"not in PLN."
|
||||
msgstr ""
|
||||
"Gebaseerd op de dagelijkse tarieven van de Tsjechische Nationale Bank, "
|
||||
"wanneer het factuurbedrag niet in CZK is."
|
||||
"Op basis van de dagkoersen van de Nationale Bank van Polen, wanneer het "
|
||||
"factuurbedrag niet in PLN is uitgedrukt."
|
||||
|
||||
#: pretix/base/settings.py:597
|
||||
msgid "Require invoice address"
|
||||
@@ -16369,10 +16361,8 @@ msgid "Allow to overbook quotas when performing this operation"
|
||||
msgstr "Quota overboeken bij deze handeling toestaan"
|
||||
|
||||
#: pretix/control/forms/orders.py:335
|
||||
#, fuzzy
|
||||
#| msgid "Number of orders"
|
||||
msgid "Number of products to add"
|
||||
msgstr "Aantal bestellingen"
|
||||
msgstr "Aantal producten dat moet worden toegevoegd"
|
||||
|
||||
#: pretix/control/forms/orders.py:344
|
||||
msgid "Add-on to"
|
||||
@@ -16404,10 +16394,9 @@ msgstr ""
|
||||
"standaardprijs van het product"
|
||||
|
||||
#: pretix/control/forms/orders.py:441
|
||||
#, fuzzy
|
||||
#| msgid "You can not select the same seat multiple times."
|
||||
msgid "You can not choose a seat when adding multiple products at once."
|
||||
msgstr "U kunt dezelfde stoel niet meerdere keren kiezen."
|
||||
msgstr ""
|
||||
"U kunt geen zitplaats kiezen wanneer u meerdere producten tegelijk toevoegt."
|
||||
|
||||
#: pretix/control/forms/orders.py:478 pretix/control/forms/orders.py:482
|
||||
#: pretix/control/forms/orders.py:510 pretix/control/forms/orders.py:552
|
||||
@@ -17020,7 +17009,7 @@ msgstr "Weekenddag"
|
||||
#: pretix/control/forms/subevents.py:106
|
||||
msgctxt "subevent"
|
||||
msgid "Skip dates that overlap with any existing date"
|
||||
msgstr ""
|
||||
msgstr "Sla data over die samenvallen met bestaande data"
|
||||
|
||||
#: pretix/control/forms/subevents.py:109
|
||||
msgctxt "subevent"
|
||||
@@ -17030,6 +17019,11 @@ msgid ""
|
||||
"This respects even inactive dates and works best if all dates have both a "
|
||||
"start and end time."
|
||||
msgstr ""
|
||||
"Dit kan handig zijn als alle afspraken op dezelfde locatie plaatsvinden en "
|
||||
"je wilt voorkomen dat terugkerende afspraken in conflict komen met bestaande "
|
||||
"speciale afspraken. Hierbij wordt ook rekening gehouden met inactieve "
|
||||
"afspraken en het werkt het beste als alle afspraken een begin- en eindtijd "
|
||||
"hebben."
|
||||
|
||||
#: pretix/control/forms/subevents.py:128
|
||||
msgid "Keep the current values"
|
||||
@@ -30145,6 +30139,8 @@ msgstr "U kunt maximaal 100.000 datums tegelijk aanmaken."
|
||||
#: pretix/control/views/subevents.py:966
|
||||
msgid "All dates would be skipped because they conflict with existing dates."
|
||||
msgstr ""
|
||||
"Alle afspraken zouden worden overgeslagen omdat ze samenvallen met bestaande "
|
||||
"afspraken."
|
||||
|
||||
#: pretix/control/views/subevents.py:1102
|
||||
#, python-brace-format
|
||||
@@ -31447,10 +31443,16 @@ msgid ""
|
||||
"refunds.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" Let op: terugbetalingen zullen als uitgevoerd worden "
|
||||
"gemarkeerd zodra er een exportbestand wordt aangemaakt.\n"
|
||||
" Zorg ervoor dat u het exportbestand downloadt en de "
|
||||
"terugbetalingen daadwerkelijk uitvoert.\n"
|
||||
" "
|
||||
|
||||
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/refund_export.html:50
|
||||
msgid "Exported files"
|
||||
msgstr ""
|
||||
msgstr "Geëxporteerde bestanden"
|
||||
|
||||
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/refund_export.html:55
|
||||
msgid "Export date"
|
||||
@@ -31462,17 +31464,17 @@ msgstr "Aantal bestellingen"
|
||||
|
||||
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/refund_export.html:80
|
||||
msgid "not downloaded"
|
||||
msgstr ""
|
||||
msgstr "niet gedownload"
|
||||
|
||||
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/refund_export.html:85
|
||||
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/refund_export.html:96
|
||||
msgid "Download CSV"
|
||||
msgstr ""
|
||||
msgstr "CSV downloaden"
|
||||
|
||||
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/refund_export.html:90
|
||||
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/refund_export.html:101
|
||||
msgid "SEPA XML"
|
||||
msgstr ""
|
||||
msgstr "SEPA-XML"
|
||||
|
||||
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/refund_export.html:110
|
||||
msgid "No exports have been created yet."
|
||||
@@ -31480,7 +31482,7 @@ msgstr "Er zijn nog geen exports aangemaakt."
|
||||
|
||||
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/sepa_export.html:10
|
||||
msgid "Export SEPA xml"
|
||||
msgstr ""
|
||||
msgstr "Exporteer SEPA-XML"
|
||||
|
||||
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/sepa_export.html:13
|
||||
#, python-format
|
||||
|
||||
@@ -8,16 +8,16 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-04-28 09:03+0000\n"
|
||||
"PO-Revision-Date: 2026-02-14 06:10+0000\n"
|
||||
"Last-Translator: Nate Horst <nate@agcthailand.org>\n"
|
||||
"Language-Team: Thai <https://translate.pretix.eu/projects/pretix/pretix/th/"
|
||||
">\n"
|
||||
"PO-Revision-Date: 2026-05-20 10:58+0000\n"
|
||||
"Last-Translator: Phumraphee Sae-tang <phumraphee@gmail.com>\n"
|
||||
"Language-Team: Thai <https://translate.pretix.eu/projects/pretix/pretix/th/>"
|
||||
"\n"
|
||||
"Language: th\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"X-Generator: Weblate 5.15.2\n"
|
||||
"X-Generator: Weblate 2026.5\n"
|
||||
|
||||
#: pretix/_base_settings.py:87
|
||||
msgid "English"
|
||||
@@ -395,10 +395,8 @@ msgstr "มีบัตรของขวัญที่มีรหัสลั
|
||||
|
||||
#: pretix/api/serializers/organizer.py:495
|
||||
#: pretix/control/views/organizer.py:1039
|
||||
#, fuzzy
|
||||
#| msgid "Account information"
|
||||
msgid "Account invitation"
|
||||
msgstr "ข้อมูลบัญชี"
|
||||
msgstr "คำเชิญเข้าร่วมบัญชี"
|
||||
|
||||
#: pretix/api/serializers/organizer.py:516
|
||||
#: pretix/control/views/organizer.py:1138
|
||||
@@ -634,22 +632,16 @@ msgid "Customer account anonymized"
|
||||
msgstr "กำหนดบัญชีลูกค้าเป็นแบบไม่ระบุตัวตนแล้ว"
|
||||
|
||||
#: pretix/api/webhooks.py:470
|
||||
#, fuzzy
|
||||
#| msgid "Gift card code"
|
||||
msgid "Gift card added"
|
||||
msgstr "รหัสบัตรของขวัญ"
|
||||
msgstr "บัตรของขวัญได้ถูกเพิ่มแล้ว"
|
||||
|
||||
#: pretix/api/webhooks.py:474
|
||||
#, fuzzy
|
||||
#| msgid "Gift card code"
|
||||
msgid "Gift card modified"
|
||||
msgstr "รหัสบัตรของขวัญ"
|
||||
msgstr "บัตรของขวัญได้ถูกแก้ไขแล้ว"
|
||||
|
||||
#: pretix/api/webhooks.py:478
|
||||
#, fuzzy
|
||||
#| msgid "Gift card transactions"
|
||||
msgid "Gift card used in transaction"
|
||||
msgstr "ธุรกรรมบัตรของขวัญ"
|
||||
msgstr "บัตรของขวัญที่ถูกใช้ในธุรกรรม"
|
||||
|
||||
#: pretix/base/addressvalidation.py:100 pretix/base/addressvalidation.py:103
|
||||
#: pretix/base/addressvalidation.py:108 pretix/base/forms/questions.py:1060
|
||||
@@ -721,7 +713,7 @@ msgid "Your password may not be the same as your previous password."
|
||||
msgid_plural ""
|
||||
"Your password may not be the same as one of your %(history_length)s previous "
|
||||
"passwords."
|
||||
msgstr[0] ""
|
||||
msgstr[0] "รหัสผ่านของคุณอาจจะไม่เหมือนกับรหัสผ่านเก่าของคุณ %(history_length)s"
|
||||
|
||||
#: pretix/base/channels.py:168
|
||||
msgid "Online shop"
|
||||
@@ -2548,10 +2540,8 @@ msgid "Voucher budget usage"
|
||||
msgstr "ยอดการใช้เวาเชอร์"
|
||||
|
||||
#: pretix/base/exporters/orderlist.py:656
|
||||
#, fuzzy
|
||||
#| msgid "Voucher"
|
||||
msgid "Voucher tag"
|
||||
msgstr "เวาเชอร์"
|
||||
msgstr "ป้ายคูปอง"
|
||||
|
||||
#: pretix/base/exporters/orderlist.py:657
|
||||
msgid "Pseudonymization ID"
|
||||
@@ -3351,39 +3341,34 @@ msgid "Street and Number"
|
||||
msgstr "ถนนและบ้านเลขที่"
|
||||
|
||||
#: pretix/base/forms/questions.py:899
|
||||
#, fuzzy, python-brace-format
|
||||
#| msgid "Please enter a shorter name."
|
||||
#, python-brace-format
|
||||
msgid "Please enter a date between {min} and {max}."
|
||||
msgstr "โปรดระบุชื่อที่สั้นกว่านี้"
|
||||
msgstr "กรุณาระบุวันที่ระหว่าง {min} และ {max}"
|
||||
|
||||
#: pretix/base/forms/questions.py:905
|
||||
#, fuzzy, python-brace-format
|
||||
#| msgid "Please enter a valid sales channel."
|
||||
#, python-brace-format
|
||||
msgid "Please enter a date no earlier than {min}."
|
||||
msgstr "โปรดระบุช่องทางการขายที่ถูกต้อง"
|
||||
msgstr "โปรดระบุวันที่ตั้งแต่ {min} เป็นต้นไป"
|
||||
|
||||
#: pretix/base/forms/questions.py:910
|
||||
#, fuzzy, python-brace-format
|
||||
#| msgid "Please enter a shorter name."
|
||||
#, python-brace-format
|
||||
msgid "Please enter a date no later than {max}."
|
||||
msgstr "โปรดระบุชื่อที่สั้นกว่านี้"
|
||||
msgstr "กรุณาระบุวันที่ก่อนวันที่ {max}"
|
||||
|
||||
#: pretix/base/forms/questions.py:948
|
||||
#, python-brace-format
|
||||
msgid "Please enter a date and time between {min} and {max}."
|
||||
msgstr ""
|
||||
msgstr "กรุณาระบุวันที่และเวลาระหว่าง {min} และ {max}"
|
||||
|
||||
#: pretix/base/forms/questions.py:954
|
||||
#, fuzzy, python-brace-format
|
||||
#| msgid "Please enter a valid sales channel."
|
||||
#, python-brace-format
|
||||
msgid "Please enter a date and time no earlier than {min}."
|
||||
msgstr "โปรดระบุช่องทางการขายที่ถูกต้อง"
|
||||
msgstr "กรุณาระบุวันที่และเวลาตั้งแต่ {min} เป็นต้นไป"
|
||||
|
||||
#: pretix/base/forms/questions.py:959
|
||||
#, fuzzy, python-brace-format
|
||||
#| msgid "Please enter a valid state."
|
||||
#, python-brace-format
|
||||
msgid "Please enter a date and time no later than {max}."
|
||||
msgstr "โปรดระบุรัฐ/จังหวัดที่ถูกต้อง"
|
||||
msgstr "กรุณาระบวันที่และเวลาก่อน {max}"
|
||||
|
||||
#: pretix/base/forms/questions.py:1178
|
||||
msgid ""
|
||||
@@ -3570,27 +3555,27 @@ msgstr "รับไฟล์ PDF ทางอีเมล"
|
||||
#: pretix/base/invoicing/national.py:37
|
||||
msgctxt "italian_invoice"
|
||||
msgid "Italian Exchange System (SdI)"
|
||||
msgstr ""
|
||||
msgstr "Italian Exchange System (SdI)"
|
||||
|
||||
#: pretix/base/invoicing/national.py:38
|
||||
msgctxt "italian_invoice"
|
||||
msgid "Exchange System (SdI)"
|
||||
msgstr ""
|
||||
msgstr "Exchange System (SdI)"
|
||||
|
||||
#: pretix/base/invoicing/national.py:51
|
||||
msgctxt "italian_invoice"
|
||||
msgid "Fiscal code"
|
||||
msgstr ""
|
||||
msgstr "Fiscal code"
|
||||
|
||||
#: pretix/base/invoicing/national.py:55
|
||||
msgctxt "italian_invoice"
|
||||
msgid "Address for certified electronic mail"
|
||||
msgstr ""
|
||||
msgstr "อีเมลสำหรับการติดต่อทางอิเล็กทรอนิกส์แบบรับรอง"
|
||||
|
||||
#: pretix/base/invoicing/national.py:59
|
||||
msgctxt "italian_invoice"
|
||||
msgid "Recipient code"
|
||||
msgstr ""
|
||||
msgstr "รหัสผู้รับปลายทาง"
|
||||
|
||||
#: pretix/base/invoicing/national.py:83
|
||||
msgctxt "italian_invoice"
|
||||
@@ -3600,6 +3585,10 @@ msgid ""
|
||||
"in accordance with the procedures and terms set forth in No. 89757/2018 of "
|
||||
"April 30, 2018, issued by the Director of the Revenue Agency."
|
||||
msgstr ""
|
||||
"เอกสาร PDF นี้เป็นเพียงสำเนาเพื่อการแสดงผลของใบแจ้งหนี้ และไม่ใช่ใบแจ้งหนี้ที่ใช้เพื่อวัตถุประสงค์ด้"
|
||||
"านภาษีมูลค่าเพิ่ม (VAT)ใบแจ้งหนี้ฉบับสมบูรณ์ถูกจัดทำในรูปแบบ XML และส่งตามหลักเกณฑ์และวิธีการที่กำ"
|
||||
"หนดในประกาศเลขที่ 89757/2018 ลงวันที่ 30 เมษายน 2018 ซึ่งออกโดยผู้อำนวยการสำนักงานสรรพา"
|
||||
"กร"
|
||||
|
||||
#: pretix/base/invoicing/pdf.py:142
|
||||
#, python-format
|
||||
@@ -3847,6 +3836,7 @@ msgstr "วันที่จัดกิจกรรม: {date_range}"
|
||||
msgid ""
|
||||
"A Peppol participant ID always starts with a prefix, followed by a colon (:)."
|
||||
msgstr ""
|
||||
"รหัสผู้เข้าร่วม Peppol ต้องเริ่มต้นด้วย prefix และตามด้วยเครื่องหมาย \":\""
|
||||
|
||||
#: pretix/base/invoicing/peppol.py:140
|
||||
#, python-format
|
||||
@@ -3854,6 +3844,8 @@ msgid ""
|
||||
"The Peppol participant ID prefix %(number)s is not known to our system. "
|
||||
"Please reach out to us if you are sure this ID is correct."
|
||||
msgstr ""
|
||||
"ระบบของเราไม่รองรับ prefix ของ Peppol participant ID หมายเลข %(number)s หากท่านมั่"
|
||||
"นใจว่ารหัสดังกล่าวถูกต้อง กรุณาติดต่อเราเพื่อขอความช่วยเหลือ"
|
||||
|
||||
#: pretix/base/invoicing/peppol.py:144
|
||||
#, python-format
|
||||
@@ -3861,23 +3853,25 @@ msgid ""
|
||||
"The Peppol participant ID does not match the validation rules for the prefix "
|
||||
"%(number)s. Please reach out to us if you are sure this ID is correct."
|
||||
msgstr ""
|
||||
"รหัส Peppol participant ID ไม่ถูกต้องตามรูปแบบของ prefix 1%(number)s หากรหัสดังกล่าวถู"
|
||||
"กต้อง กรุณาติดต่อเรา"
|
||||
|
||||
#: pretix/base/invoicing/peppol.py:166
|
||||
msgid "The Peppol participant ID is not registered on the Peppol network."
|
||||
msgstr ""
|
||||
msgstr "Peppol participant ID นี้ไม่ได้ลงทะเบียนอยู่ในเครือข่าย Peppol"
|
||||
|
||||
#: pretix/base/invoicing/peppol.py:192
|
||||
msgid "Peppol participant ID"
|
||||
msgstr ""
|
||||
msgstr "Peppol participant ID นี้ไม่ได้ลงทะเบียนอยู่ในเครือข่าย Peppol"
|
||||
|
||||
#: pretix/base/invoicing/peppol.py:211
|
||||
msgid "The Peppol participant ID does not match your VAT ID."
|
||||
msgstr ""
|
||||
msgstr "Peppol participant ID ไม่ตรงกับหมายเลข VAT ID ของคุณ"
|
||||
|
||||
#: pretix/base/invoicing/peppol.py:214
|
||||
msgctxt "peppol_invoice"
|
||||
msgid "Visual copy"
|
||||
msgstr ""
|
||||
msgstr "สำเนาเพื่อแสดงผล"
|
||||
|
||||
#: pretix/base/invoicing/peppol.py:219
|
||||
msgctxt "peppol_invoice"
|
||||
@@ -3886,12 +3880,16 @@ msgid ""
|
||||
"invoice for VAT purposes. The original invoice is issued in XML format and "
|
||||
"transmitted through the Peppol network."
|
||||
msgstr ""
|
||||
"เอกสาร PDF นี้เป็นเพียงสำเนาเพื่อการแสดงผลของใบแจ้งหนี้ และไม่ถือเป็นใบแจ้งหนี้เพื่อวัตถุประสงค์ด้"
|
||||
"านภาษีมูลค่าเพิ่ม (VAT) ใบแจ้งหนี้ต้นฉบับถูกจัดทำในรูปแบบ XML และส่งผ่านเครือข่าย Peppol"
|
||||
|
||||
#: pretix/base/logentrytype_registry.py:43
|
||||
msgid ""
|
||||
"The relevant plugin is currently not active. To activate it, click here to "
|
||||
"go to the plugin settings."
|
||||
msgstr ""
|
||||
"ปลั๊กอินที่เกี่ยวข้องยังไม่ได้เปิดใช้งานในขณะนี้ หากต้องการเปิดใช้งาน คลิกที่นี่เพื่อไปยังหน้าการตั้งค่าปลั๊ก"
|
||||
"อิน"
|
||||
|
||||
#: pretix/base/logentrytype_registry.py:53
|
||||
msgid "The relevant plugin is currently not active."
|
||||
@@ -4229,23 +4227,21 @@ msgstr "อนุญาตให้ข้ามการตรวจสอบโ
|
||||
#: pretix/control/templates/pretixcontrol/vouchers/detail.html:70
|
||||
#: pretix/control/views/vouchers.py:121
|
||||
msgid "Price effect"
|
||||
msgstr ""
|
||||
msgstr "ผลต่างด้านราคา"
|
||||
|
||||
#: pretix/base/modelimport_vouchers.py:150
|
||||
#, fuzzy, python-brace-format
|
||||
#| msgid "Could not parse {value} as a price mode, use one of {options}."
|
||||
#, python-brace-format
|
||||
msgid "Could not parse {value} as a price effect, use one of {options}."
|
||||
msgstr "ไม่สามารถประมวลผล {value} เป็นโหมดราคาได้ โปรดใช้ค่าใดค่าหนึ่งจาก {options}"
|
||||
msgstr ""
|
||||
"ไม่สามารถประมวลผล {value} เป็นค่าผลต่างด้านราคาได้ โปรดใช้ค่าใดค่าหนึ่งจาก {options}"
|
||||
|
||||
#: pretix/base/modelimport_vouchers.py:160 pretix/base/models/vouchers.py:248
|
||||
msgid "Voucher value"
|
||||
msgstr "มูลค่าเวาเชอร์"
|
||||
|
||||
#: pretix/base/modelimport_vouchers.py:165
|
||||
#, fuzzy
|
||||
#| msgid "It is pointless to set a value without a price mode."
|
||||
msgid "It is pointless to set a value without a price effect."
|
||||
msgstr "การกำหนดมูลค่าจะไม่มีผลหากไม่ได้กำหนดโหมดราคา"
|
||||
msgstr "การกำหนดมูลค่าจะไม่มีผลหากไม่ได้กำหนดผลต่างด้านราคา"
|
||||
|
||||
#: pretix/base/modelimport_vouchers.py:237 pretix/base/models/items.py:2121
|
||||
#: pretix/base/models/vouchers.py:275
|
||||
@@ -6177,46 +6173,41 @@ msgstr "สิ้นสุด"
|
||||
#: pretix/control/templates/pretixcontrol/organizers/outgoing_mail.html:38
|
||||
#: pretix/control/templates/pretixcontrol/organizers/outgoing_mails.html:139
|
||||
msgid "queued"
|
||||
msgstr ""
|
||||
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 ""
|
||||
msgstr "กำลังส่ง"
|
||||
|
||||
#: pretix/base/models/mail.py:54
|
||||
#, fuzzy
|
||||
#| msgid "Waiting list entry"
|
||||
msgid "awaiting retry"
|
||||
msgstr "รายการในรายการรอ"
|
||||
msgstr "รอการลองใหม่"
|
||||
|
||||
#: pretix/base/models/mail.py:55
|
||||
#: pretix/control/templates/pretixcontrol/organizers/outgoing_mail.html:48
|
||||
#: pretix/control/templates/pretixcontrol/organizers/outgoing_mails.html:149
|
||||
msgid "withheld"
|
||||
msgstr ""
|
||||
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 ""
|
||||
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
|
||||
#, fuzzy
|
||||
#| msgctxt "checkin state"
|
||||
#| msgid "Present"
|
||||
msgid "sent"
|
||||
msgstr "มา"
|
||||
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 ""
|
||||
msgstr "ตีกลับ"
|
||||
|
||||
#: pretix/base/models/memberships.py:44
|
||||
#: pretix/presale/templates/pretixpresale/organizers/customer_memberships.html:28
|
||||
@@ -6622,24 +6613,22 @@ msgstr ""
|
||||
"ทั้งนี้ การตั้งค่าอาจใช้เวลาสักครู่เพื่อให้มีผลกับผู้ใช้ทุกคน"
|
||||
|
||||
#: pretix/base/models/organizer.py:384
|
||||
#, fuzzy
|
||||
#| msgid "Event admission"
|
||||
msgid "All event permissions"
|
||||
msgstr "เริ่มเปิดให้เข้างาน"
|
||||
msgstr "สิทธิทั้งหมดของกิจกรรม"
|
||||
|
||||
#: pretix/base/models/organizer.py:385
|
||||
#: pretix/control/templates/pretixcontrol/organizers/team_edit.html:34
|
||||
msgid "Event permissions"
|
||||
msgstr ""
|
||||
msgstr "สิทธิของกิจกรรม"
|
||||
|
||||
#: pretix/base/models/organizer.py:386
|
||||
msgid "All organizer permissions"
|
||||
msgstr ""
|
||||
msgstr "สิทธิขององค์กรทั้งหมด"
|
||||
|
||||
#: pretix/base/models/organizer.py:387
|
||||
#: pretix/control/templates/pretixcontrol/organizers/team_edit.html:25
|
||||
msgid "Organizer permissions"
|
||||
msgstr ""
|
||||
msgstr "สิทธิขององค์กร"
|
||||
|
||||
#: pretix/base/models/organizer.py:407
|
||||
#, python-format
|
||||
@@ -7889,10 +7878,8 @@ msgstr ""
|
||||
"2x สินค้าเพิ่มเติม 2"
|
||||
|
||||
#: pretix/base/pdf.py:383
|
||||
#, fuzzy
|
||||
#| msgid "List of Add-Ons"
|
||||
msgid "List of Checked-In Add-Ons"
|
||||
msgstr "รายการสินค้าเพิ่มเติม"
|
||||
msgstr "รายการส่วนเสริมที่เช็คอินแล้ว"
|
||||
|
||||
#: pretix/base/pdf.py:390 pretix/control/forms/filter.py:1537
|
||||
#: pretix/control/forms/filter.py:1539
|
||||
@@ -8073,145 +8060,129 @@ 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 "Voucher changed"
|
||||
msgctxt "permission_level"
|
||||
msgid "View and change"
|
||||
msgstr "แก้ไขรหัสส่วนลดแล้ว"
|
||||
msgstr "แสดงและแก้ไข"
|
||||
|
||||
#: pretix/base/permissions.py:168
|
||||
msgid "API only"
|
||||
msgstr ""
|
||||
msgstr "API เท่านั้น"
|
||||
|
||||
#: pretix/base/permissions.py:173
|
||||
msgid ""
|
||||
"Menu item will only show up if the user has permission for general settings."
|
||||
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 "Read access"
|
||||
msgctxt "permission_level"
|
||||
msgid "No access"
|
||||
msgstr "สิทธิ์ในการอ่านข้อมูล"
|
||||
msgstr "ไม่มีสิทธิในการเข้าถึง"
|
||||
|
||||
#: pretix/base/permissions.py:188
|
||||
#: pretix/control/templates/pretixcontrol/event/settings.html:7
|
||||
#: pretix/control/templates/pretixcontrol/event/settings.html:13
|
||||
#: pretix/control/templates/pretixcontrol/user/settings.html:29
|
||||
msgid "General settings"
|
||||
msgstr ""
|
||||
msgstr "การตั้งค่าทั่วไป"
|
||||
|
||||
#: pretix/base/permissions.py:192
|
||||
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
|
||||
#: pretix/control/templates/pretixcontrol/event/payment_provider.html:5
|
||||
msgid "Payment settings"
|
||||
msgstr ""
|
||||
msgstr "การตั้งค่าการชำระเงิน"
|
||||
|
||||
#: pretix/base/permissions.py:203
|
||||
#: pretix/control/templates/pretixcontrol/event/tax.html:120
|
||||
msgid "Tax settings"
|
||||
msgstr ""
|
||||
msgstr "การตั้งค่าเกี่ยวกับภาษี"
|
||||
|
||||
#: pretix/base/permissions.py:209
|
||||
#, fuzzy
|
||||
#| msgid "Invoice lines"
|
||||
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 "Web Check-in"
|
||||
msgctxt "permission_level"
|
||||
msgid "Only check-in"
|
||||
msgstr "เว็บเช็คอิน (Web Check-in)"
|
||||
msgstr "สิทธิ์สำหรับเช็กอินเท่านั้น"
|
||||
|
||||
#: pretix/base/permissions.py:233
|
||||
msgctxt "permission_level"
|
||||
msgid "View all"
|
||||
msgstr ""
|
||||
msgstr "ดูทั้งหมด"
|
||||
|
||||
#: pretix/base/permissions.py:234
|
||||
#, fuzzy
|
||||
#| msgid "Web-based check-in"
|
||||
msgctxt "permission_level"
|
||||
msgid "View all and check-in"
|
||||
msgstr "การเช็คอินผ่านเว็บ"
|
||||
msgstr "ดูทั้งหมดและเช็คอิน"
|
||||
|
||||
#: pretix/base/permissions.py:235
|
||||
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
|
||||
msgid "Also includes related objects like the waiting list."
|
||||
msgstr ""
|
||||
msgstr "รวมถึงข้อมูลที่เกี่ยวข้อง เช่น รายชื่อผู้รอคิวด้วย"
|
||||
|
||||
#: pretix/base/permissions.py:248
|
||||
#, fuzzy
|
||||
#| msgctxt "subevent"
|
||||
#| msgid "Event or date information"
|
||||
msgid "Full event or date cancellation"
|
||||
msgstr "ข้อมูลกิจกรรมหรือวันที่"
|
||||
msgstr "การยกเลิกทั้งงานหรือวันที่จัดงาน"
|
||||
|
||||
#: pretix/base/permissions.py:252
|
||||
msgctxt "permission_level"
|
||||
msgid "Not allowed"
|
||||
msgstr ""
|
||||
msgstr "ไม่ได้รับอนุญาต"
|
||||
|
||||
#: pretix/base/permissions.py:253
|
||||
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
|
||||
@@ -8223,47 +8194,48 @@ msgstr ""
|
||||
#: pretix/plugins/returnurl/apps.py:40
|
||||
#: pretix/plugins/ticketoutputpdf/apps.py:55
|
||||
msgid "Settings"
|
||||
msgstr ""
|
||||
msgstr "การตั้งค่า"
|
||||
|
||||
#: pretix/base/permissions.py:278
|
||||
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
|
||||
#: pretix/control/templates/pretixcontrol/organizers/customers.html:9
|
||||
msgid "Customers"
|
||||
msgstr ""
|
||||
msgstr "ลูกค้า"
|
||||
|
||||
#: pretix/base/permissions.py:310 pretix/control/navigation.py:666
|
||||
#: pretix/control/navigation.py:673
|
||||
msgid "Devices"
|
||||
msgstr ""
|
||||
msgstr "อุปกรณ์"
|
||||
|
||||
#: pretix/base/permissions.py:316
|
||||
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 "ผังที่นั่ง"
|
||||
|
||||
#: pretix/base/permissions.py:327 pretix/control/navigation.py:712
|
||||
#: pretix/control/templates/pretixcontrol/organizers/outgoing_mails.html:8
|
||||
msgid "Outgoing emails"
|
||||
msgstr ""
|
||||
msgstr "อีเมลขาออก"
|
||||
|
||||
#: pretix/base/plugins.py:138
|
||||
#: pretix/control/templates/pretixcontrol/event/quick_setup.html:132
|
||||
@@ -8418,24 +8390,21 @@ msgstr ""
|
||||
#, python-format
|
||||
msgid "You cannot select more than %s item per order."
|
||||
msgid_plural "You cannot select more than %s items per order."
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
msgstr[0] "คุณไม่สามารถเลือกได้เกิน %s รายการต่อคำสั่งซื้อ"
|
||||
|
||||
#: pretix/base/services/cart.py:138 pretix/base/services/orders.py:1602
|
||||
#, python-format
|
||||
msgid "You cannot select more than %(max)s item of the product %(product)s."
|
||||
msgid_plural ""
|
||||
"You cannot select more than %(max)s items of the product %(product)s."
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
msgstr[0] "คุณไม่สามารถเลือกสินค้า %(product)s ได้เกิน %(max)s รายการ"
|
||||
|
||||
#: pretix/base/services/cart.py:143 pretix/base/services/orders.py:1607
|
||||
#, python-format
|
||||
msgid "You need to select at least %(min)s item of the product %(product)s."
|
||||
msgid_plural ""
|
||||
"You need to select at least %(min)s items of the product %(product)s."
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
msgstr[0] "คุณต้องเลือกสินค้า %(product)s อย่างน้อย %(min)s รายการ"
|
||||
|
||||
#: pretix/base/services/cart.py:148
|
||||
#, python-format
|
||||
@@ -8446,7 +8415,8 @@ msgid_plural ""
|
||||
"We removed %(product)s from your cart as you can not buy less than %(min)s "
|
||||
"items of it."
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
"เราได้นำสินค้า %(product)s ออกจากตะกร้าของคุณ เนื่องจากไม่สามารถซื้อสินค้านี้ต่ำกว่า %(min)s "
|
||||
"รายการได้"
|
||||
|
||||
#: pretix/base/services/cart.py:152 pretix/base/services/orders.py:164
|
||||
#: pretix/presale/templates/pretixpresale/event/index.html:170
|
||||
@@ -8501,7 +8471,8 @@ msgid_plural ""
|
||||
"The voucher code \"%(voucher)s\" can only be used if you select at least "
|
||||
"%(number)s matching products."
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
"สามารถใช้โค้ดส่วนลด “%(voucher)s” ได้ก็ต่อเมื่อเลือกสินค้าที่ร่วมรายการอย่างน้อย %(number)s ร"
|
||||
"ายการ"
|
||||
|
||||
#: pretix/base/services/cart.py:170
|
||||
#, python-format
|
||||
@@ -8514,7 +8485,8 @@ msgid_plural ""
|
||||
"%(number)s matching products. We have therefore removed some positions from "
|
||||
"your cart that can no longer be purchased like this."
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
"โค้ดส่วนลด “%(voucher)s” ใช้ได้เมื่อเลือกสินค้าที่เข้าเงื่อนไขอย่างน้อย %(number)s รายการ เรา"
|
||||
"จึงนำบางรายการออกจากตะกร้าของคุณ เนื่องจากไม่สามารถสั่งซื้อภายใต้เงื่อนไขนี้ได้อีก"
|
||||
|
||||
#: pretix/base/services/cart.py:176
|
||||
msgid ""
|
||||
@@ -8606,7 +8578,7 @@ msgid_plural ""
|
||||
"You can select at most %(max)s add-ons from the category %(cat)s for the "
|
||||
"product %(base)s."
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
"สามารถเลือกส่วนเสริมในหมวดหมู่ %(cat)s สำหรับสินค้า %(base)s ได้ไม่เกิน %(max)s รายการ"
|
||||
|
||||
#: pretix/base/services/cart.py:210 pretix/base/services/orders.py:199
|
||||
#, python-format
|
||||
@@ -30685,7 +30657,7 @@ msgstr ""
|
||||
|
||||
#: pretix/plugins/stripe/payment.py:1572
|
||||
msgid "iDEAL | Wero"
|
||||
msgstr ""
|
||||
msgstr "iDEAL | Wero"
|
||||
|
||||
#: pretix/plugins/stripe/payment.py:1575
|
||||
msgid ""
|
||||
|
||||
@@ -8,8 +8,8 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-04-28 09:04+0000\n"
|
||||
"PO-Revision-Date: 2026-01-29 13:00+0000\n"
|
||||
"Last-Translator: Nate Horst <nate@agcthailand.org>\n"
|
||||
"PO-Revision-Date: 2026-05-20 10:58+0000\n"
|
||||
"Last-Translator: Phumraphee Sae-tang <phumraphee@gmail.com>\n"
|
||||
"Language-Team: Thai <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
"th/>\n"
|
||||
"Language: th\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.15.2\n"
|
||||
"X-Generator: Weblate 2026.5\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 ""
|
||||
msgstr "iDEAL | Wero"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:42
|
||||
msgid "SEPA Direct Debit"
|
||||
@@ -712,8 +712,7 @@ msgstr "จำนวน"
|
||||
#: pretix/static/pretixcontrol/js/ui/subevent.js:112
|
||||
msgid "(one more date)"
|
||||
msgid_plural "({num} more dates)"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
msgstr[0] "อีก {num} วัน"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:47
|
||||
msgid ""
|
||||
@@ -734,8 +733,7 @@ msgstr "ตะกร้าสินค้าของคุณกำลังจ
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:62
|
||||
msgid "The items in your cart are reserved for you for one minute."
|
||||
msgid_plural "The items in your cart are reserved for you for {num} minutes."
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
msgstr[0] "สินค้าในตะกร้าของคุณจะถูกจองไว้ให้คุณเป็นเวลา {num} นาที"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:83
|
||||
msgid "Your cart has expired."
|
||||
|
||||
@@ -32,7 +32,10 @@
|
||||
# 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 time
|
||||
from datetime import datetime
|
||||
from http.cookies import Morsel
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from django.conf import settings
|
||||
@@ -58,6 +61,8 @@ from pretix.base.models import Event, Organizer
|
||||
from pretix.helpers.cookies import set_cookie_without_samesite
|
||||
from pretix.multidomain.models import KnownDomain
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
LOCAL_HOST_NAMES = ('testserver', 'localhost')
|
||||
|
||||
|
||||
@@ -254,6 +259,9 @@ class CsrfViewMiddleware(BaseCsrfMiddleware):
|
||||
if is_secure and settings.CSRF_COOKIE_NAME in request.COOKIES: # remove legacy cookie
|
||||
response.delete_cookie(settings.CSRF_COOKIE_NAME)
|
||||
response.delete_cookie(settings.CSRF_COOKIE_NAME, samesite="None")
|
||||
|
||||
handle_duplicated_csrftoken(request, response)
|
||||
|
||||
set_cookie_without_samesite(
|
||||
request, response,
|
||||
'__Host-' + settings.CSRF_COOKIE_NAME if is_secure else settings.CSRF_COOKIE_NAME,
|
||||
@@ -265,3 +273,55 @@ class CsrfViewMiddleware(BaseCsrfMiddleware):
|
||||
)
|
||||
# Content varies with the CSRF cookie, so set the Vary header.
|
||||
patch_vary_headers(response, ('Cookie',))
|
||||
|
||||
|
||||
def handle_duplicated_csrftoken(request, response):
|
||||
# Due to a Safari bug, in some browser, two csrftoken cookies with different values
|
||||
# exist: one unpartitioned, one partitioned. This function generates an additional
|
||||
# Set-Cookie header to get rid of the unpartitioned one.
|
||||
|
||||
cookie_name = '__Host-' + settings.CSRF_COOKIE_NAME
|
||||
|
||||
if request.scheme == 'https' and cookie_name in request.COOKIES:
|
||||
values = get_all_values_of_cookie(request.headers.get('Cookie'), cookie_name)
|
||||
if len(values) > 1:
|
||||
logger.info('Trying to remove duplicated %s cookies: %r', cookie_name, values)
|
||||
|
||||
# Make sure the set_cookie_without_samesite below will add a new item in the dictionary, placing
|
||||
# it below our deletion header.
|
||||
response.cookies.pop(cookie_name, None)
|
||||
|
||||
# Add the deletion Set-Cookie header to the cookie dict under a wrong name, so it doesn't get
|
||||
# overwritten by the set_cookie_without_samesite call below. This works because the code in
|
||||
# django.core.handlers.wsgi/asgi, that generates the actual Set-Cookie headers, only iterates
|
||||
# over cookie.values(), ignoring the keys.
|
||||
response.cookies['___DELETECOOKIE___' + cookie_name] = make_delete_morsel(cookie_name)
|
||||
|
||||
|
||||
def get_all_values_of_cookie(cookie_header, cookie_name):
|
||||
# like django.http.cookie.parse_cookie, but returns all values of duplicated cookies instead of only the last
|
||||
values = list()
|
||||
if not cookie_header:
|
||||
return values
|
||||
for chunk in cookie_header.split(";"):
|
||||
if "=" in chunk:
|
||||
key, val = chunk.split("=", 1)
|
||||
else:
|
||||
# Assume an empty name per
|
||||
# https://bugzilla.mozilla.org/show_bug.cgi?id=169091
|
||||
key, val = "", chunk
|
||||
key, val = key.strip(), val.strip()
|
||||
if key == cookie_name:
|
||||
values.append(val)
|
||||
return values
|
||||
|
||||
|
||||
def make_delete_morsel(name):
|
||||
m = Morsel()
|
||||
m.set(name, '', '')
|
||||
m['expires'] = datetime.utcfromtimestamp(0).strftime("%a, %d %b %Y %H:%M:%S GMT")
|
||||
m['samesite'] = 'None'
|
||||
m['secure'] = True
|
||||
m['path'] = settings.CSRF_COOKIE_PATH
|
||||
m['httponly'] = settings.CSRF_COOKIE_HTTPONLY
|
||||
return m
|
||||
|
||||
@@ -405,7 +405,7 @@ def process_banktransfers(self, job: int, data: list) -> None:
|
||||
# We need to sort prefixes by length with long ones first. In case we have an event with slug
|
||||
# "CONF" and one with slug "CONF2022", we want CONF2022 to match first, to avoid the parser
|
||||
# thinking "2022" is already the order code.
|
||||
"|".join(sorted([re.escape(p).replace("\\-", r"[\- ]*") for p in prefixes], key=lambda p: len(p), reverse=True)),
|
||||
"|".join([re.escape(p).replace("\\-", r"[\- ]*") for p in sorted(prefixes, key=lambda p: len(p), reverse=True)]),
|
||||
min(code_len_agg['min'] or 1, inr_len_agg['min'] or 1),
|
||||
max(code_len_agg['max'] or 5, inr_len_agg['max'] or 5)
|
||||
)
|
||||
|
||||
@@ -82,7 +82,8 @@ class CheckInListMixin(BaseExporter):
|
||||
widget=forms.RadioSelect(
|
||||
attrs={'class': 'scrolling-choice'}
|
||||
),
|
||||
initial=self.event.checkin_lists.first()
|
||||
initial=self.event.checkin_lists.first(),
|
||||
required=True
|
||||
)),
|
||||
('date_range',
|
||||
DateFrameField(
|
||||
@@ -143,7 +144,6 @@ class CheckInListMixin(BaseExporter):
|
||||
if not self.event.has_subevents:
|
||||
del d['date_range']
|
||||
|
||||
d['list'].queryset = self.event.checkin_lists.all()
|
||||
d['list'].widget = Select2(
|
||||
attrs={
|
||||
'data-model-select2': 'generic',
|
||||
@@ -155,7 +155,6 @@ class CheckInListMixin(BaseExporter):
|
||||
}
|
||||
)
|
||||
d['list'].widget.choices = d['list'].choices
|
||||
d['list'].required = True
|
||||
|
||||
return d
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
from django.db import migrations
|
||||
from django.db.models import F
|
||||
|
||||
|
||||
def remove_cross_event_scheduled_mails(apps, schema_editor):
|
||||
Rule = apps.get_model("sendmail", "Rule")
|
||||
ScheduledMail = apps.get_model("sendmail", "ScheduledMail")
|
||||
ScheduledMail.objects.filter(rule__subevent__isnull=False).exclude(rule__subevent__event=F('rule__event')).delete()
|
||||
Rule.objects.filter(subevent__isnull=False).exclude(subevent__event=F('event')).delete()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("sendmail", "0010_auto_20250801_1342"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(remove_cross_event_scheduled_mails),
|
||||
]
|
||||
@@ -0,0 +1,25 @@
|
||||
# Generated by Django 5.2.13 on 2026-05-06 15:45
|
||||
|
||||
from django.db import migrations
|
||||
from django.db.models import F
|
||||
|
||||
|
||||
def remove_cross_event_scheduled_mails(apps, schema_editor):
|
||||
Rule = apps.get_model("sendmail", "Rule")
|
||||
ScheduledMail = apps.get_model("sendmail", "ScheduledMail")
|
||||
ScheduledMail.objects.filter(subevent__isnull=False).exclude(subevent__event=F('rule__event')).delete()
|
||||
Rule.objects.filter(subevent__isnull=False).exclude(subevent__event=F('event')).delete()
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [('sendmail', '0011_remove_cross_event_scheduled_mails'), ('sendmail', '0012_remove_cross_event_scheduled_mails')]
|
||||
|
||||
dependencies = [
|
||||
('sendmail', '0010_auto_20250801_1342'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(
|
||||
code=remove_cross_event_scheduled_mails,
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,17 @@
|
||||
from django.db import migrations
|
||||
from django.db.models import F
|
||||
|
||||
|
||||
def remove_cross_event_scheduled_mails(apps, schema_editor):
|
||||
ScheduledMail = apps.get_model("sendmail", "ScheduledMail")
|
||||
ScheduledMail.objects.filter(subevent__isnull=False).exclude(subevent__event=F('rule__event')).delete()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("sendmail", "0011_remove_cross_event_scheduled_mails"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(remove_cross_event_scheduled_mails),
|
||||
]
|
||||
@@ -232,7 +232,7 @@ def sendmail_copy_data_receiver(sender, other, item_map, **kwargs):
|
||||
if sender.sendmail_rules.exists(): # idempotency
|
||||
return
|
||||
|
||||
for r in other.sendmail_rules.prefetch_related('limit_products'):
|
||||
for r in other.sendmail_rules.filter(subevent__isnull=True).prefetch_related('limit_products'):
|
||||
limit_products = list(r.limit_products.all())
|
||||
r = copy.copy(r)
|
||||
r.pk = None
|
||||
|
||||
@@ -386,7 +386,7 @@ class StripeSettingsHolder(BasePaymentProvider):
|
||||
disabled=self.event.currency != 'EUR',
|
||||
help_text=(
|
||||
_('Some payment methods might need to be enabled in the settings of your Stripe account '
|
||||
'before work properly.') +
|
||||
'before they work properly.') +
|
||||
'<div class="alert alert-warning">%s</div>' % _(
|
||||
'SEPA Direct Debit payments via Stripe are <strong>not</strong> processed '
|
||||
'instantly but might take up to <strong>14 days</strong> to be confirmed in some cases. '
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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'
|
||||
})
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -126,7 +126,7 @@ footer_link = EventPluginSignal()
|
||||
Arguments: ``request``
|
||||
|
||||
The signal ``pretix.presale.signals.footer_link`` allows you to add links to the footer of an event page. You
|
||||
are expected to return a dictionary containing the keys ``label`` and ``url``.
|
||||
are expected to return a dictionary containing the keys ``label``, ``url`` and optionally ``cssclass``.
|
||||
|
||||
As with all event plugin signals, the ``sender`` keyword argument will contain the event.
|
||||
"""
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user