Compare commits
366 Commits
oauth-tool
...
fix-q-chec
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
17715ff5a5 | ||
|
|
9bed40fa09 | ||
|
|
ed1dae5fde | ||
|
|
c7060d188f | ||
|
|
9a53dc9c5e | ||
|
|
d1e0a7293b | ||
|
|
7d5b1eebcb | ||
|
|
e4a02c494e | ||
|
|
6ecac70727 | ||
|
|
8a1554323e | ||
|
|
cfc22c806a | ||
|
|
f70d6877dc | ||
|
|
4dfad2ef42 | ||
|
|
dc1d35cb1f | ||
|
|
6e657db882 | ||
|
|
91d3aaf20b | ||
|
|
2f1318e2b9 | ||
|
|
844a74d33f | ||
|
|
76788a874a | ||
|
|
e29d5c37cd | ||
|
|
258e66587e | ||
|
|
d13af2eeab | ||
|
|
14b9afcc40 | ||
|
|
b3ae675a14 | ||
|
|
3639577841 | ||
|
|
26202e82bc | ||
|
|
f81704be7b | ||
|
|
24b1c94140 | ||
|
|
aa85bae35f | ||
|
|
e1c10c4b01 | ||
|
|
211200cb4d | ||
|
|
37a07192da | ||
|
|
7d4b575150 | ||
|
|
c2d720b3b9 | ||
|
|
6a8ebcca1a | ||
|
|
79c7b53efa | ||
|
|
8a5463320a | ||
|
|
0bb99c911b | ||
|
|
04899c9540 | ||
|
|
ae8c6058cd | ||
|
|
4200562814 | ||
|
|
b0e580447f | ||
|
|
c6e650c69a | ||
|
|
bf49297c7e | ||
|
|
c4ea99646e | ||
|
|
78037e18d2 | ||
|
|
57ddc14fda | ||
|
|
f08333814f | ||
|
|
175921fbaf | ||
|
|
7d53150779 | ||
|
|
285150354a | ||
|
|
093eaace43 | ||
|
|
3c0bd0629b | ||
|
|
b58546c3c8 | ||
|
|
50f9cfd402 | ||
|
|
72aaf24a40 | ||
|
|
83b10ecd23 | ||
|
|
bb8657637f | ||
|
|
076f279a60 | ||
|
|
75f1ee9191 | ||
|
|
ca9ddd7d98 | ||
|
|
c63bc46d3b | ||
|
|
24fd8f404e | ||
|
|
6c7415a7ff | ||
|
|
b094c6861d | ||
|
|
3a14a3da49 | ||
|
|
6adc8b6876 | ||
|
|
440db4883c | ||
|
|
71e08012f5 | ||
|
|
2ba9514b6f | ||
|
|
e358bacfa3 | ||
|
|
fd78e31861 | ||
|
|
da387f4af2 | ||
|
|
c2c7e58fd6 | ||
|
|
f09878df9f | ||
|
|
a416ec09d7 | ||
|
|
20581cd31c | ||
|
|
e33fbaf9c0 | ||
|
|
1399f97e1e | ||
|
|
9b60dde718 | ||
|
|
35fb20fe76 | ||
|
|
ccebbb6307 | ||
|
|
63dde340b6 | ||
|
|
5ae3b27e83 | ||
|
|
04cb7d3ec5 | ||
|
|
538e1b4089 | ||
|
|
37d58c53a1 | ||
|
|
d6dc21ff89 | ||
|
|
f63408504e | ||
|
|
fdadda9910 | ||
|
|
399643fd5d | ||
|
|
d22b90beaa | ||
|
|
6d20500f52 | ||
|
|
2607b18833 | ||
|
|
f22698fdbf | ||
|
|
6941dda489 | ||
|
|
c99df679e7 | ||
|
|
dfb2f18ac3 | ||
|
|
e4b9877444 | ||
|
|
28eb730fdd | ||
|
|
c44ff6244d | ||
|
|
37e633812b | ||
|
|
1b7bb1f3e6 | ||
|
|
34f8503251 | ||
|
|
9b049db2f8 | ||
|
|
ef560c0f68 | ||
|
|
efb41c5220 | ||
|
|
18986caa49 | ||
|
|
fba55f0292 | ||
|
|
80ee99d46a | ||
|
|
2893f72d5b | ||
|
|
ba0edd6261 | ||
|
|
04d34fac8c | ||
|
|
cef7b33c4b | ||
|
|
6902725f3c | ||
|
|
7b0d07065f | ||
|
|
04b0a3d5d4 | ||
|
|
fd16e4e78e | ||
|
|
ac16adba4c | ||
|
|
289e0096e8 | ||
|
|
df41caacf7 | ||
|
|
50f001221c | ||
|
|
a4dca6b10d | ||
|
|
e7019c6913 | ||
|
|
f03aa71a02 | ||
|
|
ec51078047 | ||
|
|
66c1a321b0 | ||
|
|
14f129bc51 | ||
|
|
97ec07139e | ||
|
|
ee720cd9db | ||
|
|
e8785f4117 | ||
|
|
accb9f8b13 | ||
|
|
98d290992c | ||
|
|
9a56874083 | ||
|
|
82dd417a8e | ||
|
|
ba2c6e1e58 | ||
|
|
38b8269f14 | ||
|
|
749f5c7e6c | ||
|
|
d1e8504481 | ||
|
|
108408366e | ||
|
|
4543d8093f | ||
|
|
513a90f976 | ||
|
|
6f61155deb | ||
|
|
714ce28b6a | ||
|
|
b3bcad38a8 | ||
|
|
90978e5cab | ||
|
|
84fb481cdb | ||
|
|
854b41c955 | ||
|
|
79ee89bde9 | ||
|
|
d8d31bab51 | ||
|
|
d47bebb403 | ||
|
|
32927bfd4f | ||
|
|
41c8d646d9 | ||
|
|
c828873d21 | ||
|
|
b4e372ce04 | ||
|
|
7b301b6027 | ||
|
|
68430f01a3 | ||
|
|
363c62a6ca | ||
|
|
a7f32b8647 | ||
|
|
8786397910 | ||
|
|
d078a42250 | ||
|
|
f1a73cd440 | ||
|
|
8bba1a2ea6 | ||
|
|
aeb5c52bfe | ||
|
|
a684aca212 | ||
|
|
59d46ddded | ||
|
|
e4e7d50659 | ||
|
|
1d46a96821 | ||
|
|
8b81ef6f43 | ||
|
|
cb734510ac | ||
|
|
b29f5c69ed | ||
|
|
56ce37225c | ||
|
|
afae6fdd45 | ||
|
|
4cad8eae93 | ||
|
|
c5b7ff66b7 | ||
|
|
04e16bbb39 | ||
|
|
eea48af60a | ||
|
|
fc63f60960 | ||
|
|
3b94125471 | ||
|
|
00d901b04b | ||
|
|
a7f9e100d2 | ||
|
|
59f409b1c6 | ||
|
|
e03bebf5ab | ||
|
|
065e6d4024 | ||
|
|
f99e1dd5be | ||
|
|
25949c6c2b | ||
|
|
6fe33077e9 | ||
|
|
29b8ee8408 | ||
|
|
15273ba32e | ||
|
|
6ff5b4431c | ||
|
|
a82ce69633 | ||
|
|
53156a4181 | ||
|
|
30142b013e | ||
|
|
c4bdfe7537 | ||
|
|
0972123614 | ||
|
|
cf71c4ed2b | ||
|
|
31e5d00093 | ||
|
|
0eba0f5e3e | ||
|
|
ce79647289 | ||
|
|
acc34c29f7 | ||
|
|
ee6fbbf648 | ||
|
|
57fa29a0e9 | ||
|
|
5d42dc97c2 | ||
|
|
ddf0d551f3 | ||
|
|
a5570dc475 | ||
|
|
3c1f3a26cf | ||
|
|
8ca128912e | ||
|
|
b9d8429da8 | ||
|
|
034a32b048 | ||
|
|
9eb2d43016 | ||
|
|
f81b7bcf53 | ||
|
|
234f9d43c5 | ||
|
|
7f09b4c903 | ||
|
|
3bc8450d4f | ||
|
|
fdcad926f9 | ||
|
|
433262f6fc | ||
|
|
50596b7543 | ||
|
|
988188b00a | ||
|
|
fdc15a753c | ||
|
|
785cc49a2e | ||
|
|
863fd3065a | ||
|
|
ac361a8f47 | ||
|
|
56d928d5ec | ||
|
|
6c3e745d5d | ||
|
|
b29efb9694 | ||
|
|
5ee1213dbf | ||
|
|
c29dc49819 | ||
|
|
8b74f791f4 | ||
|
|
4d75438a11 | ||
|
|
781002b27e | ||
|
|
f7c0e8c8d0 | ||
|
|
70a3516725 | ||
|
|
3133e18b22 | ||
|
|
3257c59117 | ||
|
|
19d1a8de71 | ||
|
|
0bb5af191b | ||
|
|
8fe56b7278 | ||
|
|
df432b1958 | ||
|
|
54434f07a9 | ||
|
|
0ecbee48ae | ||
|
|
ff2fa43ba1 | ||
|
|
3a1cefbbe7 | ||
|
|
7aa433e9af | ||
|
|
c5a5d13158 | ||
|
|
2e256e30be | ||
|
|
0fbc0c3ffb | ||
|
|
93950d3fac | ||
|
|
e8269ed1bf | ||
|
|
3fa1fbf6e2 | ||
|
|
8114b47c8c | ||
|
|
dcf5e67196 | ||
|
|
bf4569b080 | ||
|
|
95979143d7 | ||
|
|
4c5e77c2ef | ||
|
|
95b4f08aeb | ||
|
|
d6605e668b | ||
|
|
6ee348548f | ||
|
|
fca8e48f6a | ||
|
|
5a295934f7 | ||
|
|
4385b41e8b | ||
|
|
92dacfb966 | ||
|
|
d1acbad181 | ||
|
|
d0676765a4 | ||
|
|
9dd3b12625 | ||
|
|
738301d2af | ||
|
|
f7f29e8a55 | ||
|
|
ad69ec293f | ||
|
|
3443296a28 | ||
|
|
7a69e00d39 | ||
|
|
bddc91d595 | ||
|
|
0c0d8b2c55 | ||
|
|
c018921a18 | ||
|
|
f33aa3fdba | ||
|
|
7b55f85663 | ||
|
|
fb9909ca83 | ||
|
|
35e8bab7a5 | ||
|
|
bf34e73121 | ||
|
|
39e2715f3c | ||
|
|
97d2b015cf | ||
|
|
ca30a07da3 | ||
|
|
81d31ce64c | ||
|
|
0ae66ab7f6 | ||
|
|
cb4af51c01 | ||
|
|
6b44cae607 | ||
|
|
1a4d4029c9 | ||
|
|
3563653d55 | ||
|
|
e4c9afa87a | ||
|
|
6938397a6a | ||
|
|
24e5b593ea | ||
|
|
cd237d4c19 | ||
|
|
9b1d7cc522 | ||
|
|
d07948613a | ||
|
|
eadc1b4812 | ||
|
|
787d4ec06b | ||
|
|
ca1d13421f | ||
|
|
495ae25b9e | ||
|
|
d98accdd2d | ||
|
|
746ced9e93 | ||
|
|
d72bbffc51 | ||
|
|
8503623472 | ||
|
|
4f097e279a | ||
|
|
603225d042 | ||
|
|
e5528f7784 | ||
|
|
2e702b87de | ||
|
|
59730ff501 | ||
|
|
280c24528f | ||
|
|
ff09ed422c | ||
|
|
b3be64b9f3 | ||
|
|
018c3d70e3 | ||
|
|
a2f2d25169 | ||
|
|
4747a4c480 | ||
|
|
ed9a9246e3 | ||
|
|
6e63d34932 | ||
|
|
db06ed132a | ||
|
|
ddbe38ca53 | ||
|
|
d3698b3e2f | ||
|
|
ff828ecc92 | ||
|
|
d0236572f0 | ||
|
|
8ea6f3bc7d | ||
|
|
5587aebcd8 | ||
|
|
0b708067de | ||
|
|
e75dc74661 | ||
|
|
f0c5e54e34 | ||
|
|
eeb6e11934 | ||
|
|
1238165e7a | ||
|
|
bcf65603e4 | ||
|
|
c4aed04a18 | ||
|
|
8be09ee937 | ||
|
|
a1ec45daf6 | ||
|
|
9b0b8e2061 | ||
|
|
b6e65e7356 | ||
|
|
5d82305e18 | ||
|
|
c8983ca863 | ||
|
|
52f6b7c971 | ||
|
|
809177397a | ||
|
|
b83cb7d8c4 | ||
|
|
bfd980fc30 | ||
|
|
5bc3503d04 | ||
|
|
a582db3280 | ||
|
|
bd4ea5d8f8 | ||
|
|
5dec94606b | ||
|
|
ab97082c85 | ||
|
|
0723ff92ee | ||
|
|
15272cc3e6 | ||
|
|
60554dad9a | ||
|
|
b288ea1e96 | ||
|
|
6a4b792501 | ||
|
|
8dd83e5a35 | ||
|
|
bd5c9a4cb5 | ||
|
|
0cd8bbf9a9 | ||
|
|
d46989473b | ||
|
|
b31b2d34c0 | ||
|
|
5e963d87d9 | ||
|
|
a8e0eea69a | ||
|
|
efa9f6dfe5 | ||
|
|
857377d16c | ||
|
|
229b6fed4a | ||
|
|
b2e4fb6db3 | ||
|
|
16b15057fd | ||
|
|
e4168ff06a | ||
|
|
b208db32c7 | ||
|
|
ce177227c7 | ||
|
|
2cd70ef434 | ||
|
|
633755ab13 | ||
|
|
6ade32d7cb | ||
|
|
cea6c340be |
1
.github/dependabot.yml
vendored
@@ -9,6 +9,7 @@ updates:
|
||||
directory: "/src"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
versioning-strategy: increase
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/src/pretix/static/npm_dir"
|
||||
schedule:
|
||||
|
||||
7
.github/workflows/docs.yml
vendored
@@ -17,16 +17,19 @@ on:
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
|
||||
jobs:
|
||||
spelling:
|
||||
name: Spellcheck
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python 3.9
|
||||
- name: Set up Python 3.11
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: 3.9
|
||||
python-version: 3.11
|
||||
- uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
|
||||
11
.github/workflows/strings.yml
vendored
@@ -15,16 +15,19 @@ on:
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
|
||||
jobs:
|
||||
compile:
|
||||
runs-on: ubuntu-22.04
|
||||
name: Check gettext syntax
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python 3.9
|
||||
- name: Set up Python 3.11
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: 3.9
|
||||
python-version: 3.11
|
||||
- uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
@@ -47,10 +50,10 @@ jobs:
|
||||
name: Spellcheck
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python 3.9
|
||||
- name: Set up Python 3.11
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: 3.9
|
||||
python-version: 3.11
|
||||
- uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
|
||||
15
.github/workflows/style.yml
vendored
@@ -15,16 +15,19 @@ on:
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
|
||||
jobs:
|
||||
isort:
|
||||
name: isort
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python 3.9
|
||||
- name: Set up Python 3.11
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: 3.9
|
||||
python-version: 3.11
|
||||
- uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
@@ -42,10 +45,10 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python 3.9
|
||||
- name: Set up Python 3.11
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: 3.9
|
||||
python-version: 3.11
|
||||
- uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
@@ -63,10 +66,10 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python 3.9
|
||||
- name: Set up Python 3.11
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: 3.9
|
||||
python-version: 3.11
|
||||
- name: Install Dependencies
|
||||
run: pip3 install licenseheaders
|
||||
- name: Run licenseheaders
|
||||
|
||||
16
.github/workflows/tests.yml
vendored
@@ -15,28 +15,31 @@ on:
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-22.04
|
||||
name: Tests
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.7", "3.9", "3.10"]
|
||||
python-version: ["3.9", "3.10", "3.11"]
|
||||
database: [sqlite, postgres, mysql]
|
||||
exclude:
|
||||
- database: mysql
|
||||
python-version: "3.10"
|
||||
- database: mysql
|
||||
python-version: "3.9"
|
||||
- database: mysql
|
||||
python-version: "3.11"
|
||||
- database: sqlite
|
||||
python-version: "3.7"
|
||||
python-version: "3.9"
|
||||
- database: sqlite
|
||||
python-version: "3.10"
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: getong/mariadb-action@v1.1
|
||||
with:
|
||||
mariadb version: '10.4'
|
||||
mariadb version: '10.10'
|
||||
mysql database: 'pretix'
|
||||
mysql root password: ''
|
||||
if: matrix.database == 'mysql'
|
||||
@@ -78,5 +81,6 @@ jobs:
|
||||
uses: codecov/codecov-action@v1
|
||||
with:
|
||||
file: src/coverage.xml
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
fail_ci_if_error: true
|
||||
if: matrix.database == 'postgres' && matrix.python-version == '3.10'
|
||||
if: matrix.database == 'postgres' && matrix.python-version == '3.11'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM python:3.9-bullseye
|
||||
FROM python:3.11-bullseye
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
|
||||
113
doc/_themes/pretix_theme/layout.html
vendored
@@ -18,67 +18,82 @@
|
||||
<title>{{ title|striptags|e }}{{ titlesuffix }}</title>
|
||||
{% endblock %}
|
||||
|
||||
{# FAVICON #}
|
||||
{% if favicon %}
|
||||
<link rel="shortcut icon" href="{{ pathto('_static/' + favicon, 1) }}"/>
|
||||
{% endif %}
|
||||
{# CANONICAL URL #}
|
||||
{% if theme_canonical_url %}
|
||||
|
||||
{#- CSS #}
|
||||
{%- for css in css_files %}
|
||||
{%- if css|attr("rel") %}
|
||||
<link rel="{{ css.rel }}" href="{{ pathto(css.filename, 1) }}" type="text/css"{% if css.title is not none %} title="{{ css.title }}"{% endif %} />
|
||||
{%- else %}
|
||||
<link rel="stylesheet" href="{{ pathto(css, 1) }}" type="text/css" />
|
||||
{%- endif %}
|
||||
{%- endfor %}
|
||||
|
||||
{%- for cssfile in extra_css_files %}
|
||||
<link rel="stylesheet" href="{{ pathto(cssfile, 1) }}" type="text/css" />
|
||||
{%- endfor -%}
|
||||
|
||||
{#- FAVICON
|
||||
favicon_url is the only context var necessary since Sphinx 4.
|
||||
In Sphinx<4, we use favicon but need to prepend path info.
|
||||
#}
|
||||
{%- set _favicon_url = favicon_url | default(pathto('_static/' + (favicon or ""), 1)) %}
|
||||
{%- if favicon_url or favicon %}
|
||||
<link rel="shortcut icon" href="{{ _favicon_url }}"/>
|
||||
{%- endif %}
|
||||
|
||||
{#- CANONICAL URL (deprecated) #}
|
||||
{%- if theme_canonical_url and not pageurl %}
|
||||
<link rel="canonical" href="{{ theme_canonical_url }}{{ pagename }}.html"/>
|
||||
{% endif %}
|
||||
{%- endif -%}
|
||||
|
||||
{# CSS #}
|
||||
{#- CANONICAL URL #}
|
||||
{%- if pageurl %}
|
||||
<link rel="canonical" href="{{ pageurl|e }}" />
|
||||
{%- endif -%}
|
||||
|
||||
{# OPENSEARCH #}
|
||||
{% if not embedded %}
|
||||
{% if use_opensearch %}
|
||||
<link rel="search" type="application/opensearchdescription+xml" title="{% trans docstitle=docstitle|e %}Search within {{ docstitle }}{% endtrans %}" href="{{ pathto('_static/opensearch.xml', 1) }}"/>
|
||||
{% endif %}
|
||||
{#- JAVASCRIPTS #}
|
||||
{%- block scripts %}
|
||||
<!--[if lt IE 9]>
|
||||
<script src="{{ pathto('_static/js/html5shiv.min.js', 1) }}"></script>
|
||||
<![endif]-->
|
||||
{%- if not embedded %}
|
||||
{# XXX Sphinx 1.8.0 made this an external js-file, quick fix until we refactor the template to inherert more blocks directly from sphinx #}
|
||||
{%- for scriptfile in script_files %}
|
||||
{{ js_tag(scriptfile) }}
|
||||
{%- endfor %}
|
||||
<script src="{{ pathto('_static/js/theme.js', 1) }}"></script>
|
||||
|
||||
{% endif %}
|
||||
|
||||
{# RTD hosts this file, so just load on non RTD builds #}
|
||||
<link rel="stylesheet" href="{{ pathto('_static/' + style, 1) }}" type="text/css" />
|
||||
|
||||
{% for cssfile in css_files %}
|
||||
<link rel="stylesheet" href="{{ pathto(cssfile, 1) }}" type="text/css" />
|
||||
{% endfor %}
|
||||
|
||||
{% for cssfile in extra_css_files %}
|
||||
<link rel="stylesheet" href="{{ pathto(cssfile, 1) }}" type="text/css" />
|
||||
{% endfor %}
|
||||
{#- OPENSEARCH #}
|
||||
{%- if use_opensearch %}
|
||||
<link rel="search" type="application/opensearchdescription+xml"
|
||||
title="{% trans docstitle=docstitle|e %}Search within {{ docstitle }}{% endtrans %}"
|
||||
href="{{ pathto('_static/opensearch.xml', 1) }}"/>
|
||||
{%- endif %}
|
||||
{%- endif %}
|
||||
{%- endblock %}
|
||||
|
||||
{%- block linktags %}
|
||||
{%- if hasdoc('about') %}
|
||||
<link rel="author" title="{{ _('About these documents') }}"
|
||||
href="{{ pathto('about') }}"/>
|
||||
<link rel="author" title="{{ _('About these documents') }}" href="{{ pathto('about') }}" />
|
||||
{%- endif %}
|
||||
{%- if hasdoc('genindex') %}
|
||||
<link rel="index" title="{{ _('Index') }}"
|
||||
href="{{ pathto('genindex') }}"/>
|
||||
<link rel="index" title="{{ _('Index') }}" href="{{ pathto('genindex') }}" />
|
||||
{%- endif %}
|
||||
{%- if hasdoc('search') %}
|
||||
<link rel="search" title="{{ _('Search') }}" href="{{ pathto('search') }}"/>
|
||||
<link rel="search" title="{{ _('Search') }}" href="{{ pathto('search') }}" />
|
||||
{%- endif %}
|
||||
{%- if hasdoc('copyright') %}
|
||||
<link rel="copyright" title="{{ _('Copyright') }}" href="{{ pathto('copyright') }}"/>
|
||||
{%- endif %}
|
||||
<link rel="top" title="{{ docstitle|e }}" href="{{ pathto('index') }}"/>
|
||||
{%- if parents %}
|
||||
<link rel="up" title="{{ parents[-1].title|striptags|e }}" href="{{ parents[-1].link|e }}"/>
|
||||
<link rel="copyright" title="{{ _('Copyright') }}" href="{{ pathto('copyright') }}" />
|
||||
{%- endif %}
|
||||
{%- if next %}
|
||||
<link rel="next" title="{{ next.title|striptags|e }}" href="{{ next.link|e }}"/>
|
||||
<link rel="next" title="{{ next.title|striptags|e }}" href="{{ next.link|e }}" />
|
||||
{%- endif %}
|
||||
{%- if prev %}
|
||||
<link rel="prev" title="{{ prev.title|striptags|e }}" href="{{ prev.link|e }}"/>
|
||||
<link rel="prev" title="{{ prev.title|striptags|e }}" href="{{ prev.link|e }}" />
|
||||
{%- endif %}
|
||||
{%- endblock %}
|
||||
{%- block extrahead %} {% endblock %}
|
||||
|
||||
{# Keep modernizr in head - http://modernizr.com/docs/#installing #}
|
||||
<script src="{{ pathto('_static/js/modernizr.min.js', 1) }}"></script>
|
||||
|
||||
</head>
|
||||
|
||||
<body class="wy-body-for-nav" role="document">
|
||||
@@ -92,16 +107,14 @@
|
||||
<div class="wy-side-nav-search">
|
||||
{% block sidebartitle %}
|
||||
|
||||
{% if logo and theme_logo_only %}
|
||||
<a href="{{ pathto('index') }}">
|
||||
{% else %}
|
||||
<a href="{{ pathto('index') }}" class="icon icon-home"> {{ project }}
|
||||
{% endif %}
|
||||
|
||||
{% if logo %}
|
||||
{# Not strictly valid HTML, but it's the only way to display/scale it properly, without weird scripting or heaps of work #}
|
||||
<img src="{{ pathto('_static/' + logo, 1) }}" class="logo" />
|
||||
{% endif %}
|
||||
{# the logo helper function was removed in Sphinx 6 and deprecated since Sphinx 4 #}
|
||||
{# the master_doc variable was renamed to root_doc in Sphinx 4 (master_doc still exists in later Sphinx versions) #}
|
||||
{%- set _logo_url = logo_url|default(pathto('_static/' + (logo or ""), 1)) %}
|
||||
{%- set _root_doc = root_doc|default(master_doc) %}
|
||||
<a href="{{ pathto(_root_doc) }}"{% if not theme_logo_only %} class="icon icon-home"{% endif %}>
|
||||
{%- if logo or logo_url %}
|
||||
<img src="{{ _logo_url }}" class="logo" alt="{{ _('Logo') }}"/>
|
||||
{%- endif %}
|
||||
</a>
|
||||
|
||||
{% include "searchbox.html" %}
|
||||
|
||||
18
doc/_themes/pretix_theme/search.html
vendored
@@ -5,31 +5,37 @@
|
||||
Template for the search page.
|
||||
|
||||
:copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
:license: BSD, see https://github.com/sphinx-doc/sphinx/blob/master/LICENSE for details.
|
||||
#}
|
||||
{%- extends "layout.html" %}
|
||||
{% set title = _('Search') %}
|
||||
{% set script_files = script_files + ['_static/searchtools.js'] %}
|
||||
{% set display_vcs_links = False %}
|
||||
{%- block scripts %}
|
||||
{{ super() }}
|
||||
<script src="{{ pathto('_static/searchtools.js', 1) }}"></script>
|
||||
<script src="{{ pathto('_static/language_data.js', 1) }}"></script>
|
||||
{%- endblock %}
|
||||
{% block footer %}
|
||||
<script type="text/javascript">
|
||||
<script>
|
||||
jQuery(function() { Search.loadIndex("{{ pathto('searchindex.js', 1) }}"); });
|
||||
</script>
|
||||
{# this is used when loading the search index using $.ajax fails,
|
||||
such as on Chrome for documents on localhost #}
|
||||
<script type="text/javascript" id="searchindexloader"></script>
|
||||
<script id="searchindexloader"></script>
|
||||
{{ super() }}
|
||||
{% endblock %}
|
||||
{% block body %}
|
||||
<noscript>
|
||||
<div id="fallback" class="admonition warning">
|
||||
<p class="last">
|
||||
{% trans %}Please activate JavaScript to enable the search
|
||||
{% trans trimmed %}Please activate JavaScript to enable the search
|
||||
functionality.{% endtrans %}
|
||||
</p>
|
||||
</div>
|
||||
</noscript>
|
||||
|
||||
{% if search_performed %}
|
||||
{# Translators: Search is a noun, not a verb #}
|
||||
<h2>{{ _('Search Results') }}</h2>
|
||||
{% if not search_results %}
|
||||
<p>{{ _('Your search did not match any documents. Please make sure that all words are spelled correctly and that you\'ve selected enough categories.') }}</p>
|
||||
@@ -47,4 +53,4 @@
|
||||
</ul>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
.. _`config`:
|
||||
|
||||
.. spelling:: Galera
|
||||
.. spelling:word-list:: Galera
|
||||
|
||||
Configuration file
|
||||
==================
|
||||
@@ -84,7 +84,7 @@ Example::
|
||||
Enables or disables the "keep me logged in" button. Defaults to ``on``.
|
||||
|
||||
``ecb_rates``
|
||||
By default, pretix periodically downloads a XML file from the European Central Bank to retrieve exchange rates
|
||||
By default, pretix periodically downloads currency rates from the European Central Bank as well as other authorities
|
||||
that are used to print tax amounts in the customer currency on invoices for some currencies. Set to ``off`` to
|
||||
disable this feature. Defaults to ``on``.
|
||||
|
||||
@@ -106,6 +106,11 @@ Example::
|
||||
proxy that actively removes and re-adds the header to make sure the correct value is set.
|
||||
Defaults to ``off``.
|
||||
|
||||
``trust_x_forwarded_host``
|
||||
Specifies whether the ``X-Forwarded-Host`` header can be trusted. Only set to ``on`` if you have a reverse
|
||||
proxy that actively removes and re-adds the header to make sure the correct value is set.
|
||||
Defaults to ``off``.
|
||||
|
||||
``csp_log``
|
||||
Log violations of the Content Security Policy (CSP). Defaults to ``on``.
|
||||
|
||||
@@ -141,7 +146,7 @@ Database settings
|
||||
Example::
|
||||
|
||||
[database]
|
||||
backend=mysql
|
||||
backend=postgresql
|
||||
name=pretix
|
||||
user=pretix
|
||||
password=abcd
|
||||
@@ -149,7 +154,7 @@ Example::
|
||||
port=3306
|
||||
|
||||
``backend``
|
||||
One of ``mysql``, ``sqlite3``, ``oracle`` and ``postgresql``.
|
||||
One of ``mysql`` (deprecated), ``sqlite3`` and ``postgresql``.
|
||||
Default: ``sqlite3``.
|
||||
|
||||
If you use MySQL, be sure to create your database using
|
||||
@@ -163,7 +168,7 @@ Example::
|
||||
Connection details for the database connection. Empty by default.
|
||||
|
||||
``galera``
|
||||
Indicates if the database backend is a MySQL/MariaDB Galera cluster and
|
||||
(Deprecated) Indicates if the database backend is a MySQL/MariaDB Galera cluster and
|
||||
turns on some optimizations/special case handlers. Default: ``False``
|
||||
|
||||
.. _`config-replica`:
|
||||
@@ -194,7 +199,7 @@ Example::
|
||||
|
||||
[urls]
|
||||
media=/media/
|
||||
static=/media/
|
||||
static=/static/
|
||||
|
||||
``media``
|
||||
The URL to be used to serve user-uploaded content. You should not need to modify
|
||||
|
||||
@@ -14,4 +14,5 @@ This documentation is for everyone who wants to install pretix on a server.
|
||||
maintainance
|
||||
scaling
|
||||
errors
|
||||
mysql2postgres
|
||||
indexes
|
||||
|
||||
@@ -45,8 +45,8 @@ Here is the currently recommended set of commands::
|
||||
CREATE INDEX CONCURRENTLY pretix_addidx_order_comment
|
||||
ON pretixbase_order
|
||||
USING gin (upper("comment") gin_trgm_ops);
|
||||
CREATE INDEX CONCURRENTLY pretix_addidx_order_event_date
|
||||
ON public.pretixbase_order (event_id, datetime);
|
||||
CREATE INDEX CONCURRENTLY pretix_addidx_order_event_date_id
|
||||
ON public.pretixbase_order (event_id, datetime, id);
|
||||
CREATE INDEX CONCURRENTLY pretix_addidx_orderpos_name
|
||||
ON pretixbase_orderposition
|
||||
USING gin (upper("attendee_name_cached") gin_trgm_ops);
|
||||
@@ -66,10 +66,10 @@ Here is the currently recommended set of commands::
|
||||
ON public.pretixbase_orderposition (upper((attendee_email)::text));
|
||||
CREATE INDEX CONCURRENTLY pretix_addidx_voucher_code_upper
|
||||
ON public.pretixbase_voucher (upper((code)::text));
|
||||
CREATE INDEX CONCURRENTLY pretix_addidx_logentry_event_date
|
||||
ON public.pretixbase_logentry (event_id, datetime);
|
||||
CREATE INDEX CONCURRENTLY pretix_addidx_logentry_event_cid_date
|
||||
ON public.pretixbase_logentry (event_id, content_type_id, datetime);
|
||||
CREATE INDEX CONCURRENTLY pretix_addidx_logentry_event_date_id
|
||||
ON public.pretixbase_logentry (event_id, datetime, id);
|
||||
CREATE INDEX CONCURRENTLY pretix_addidx_logentry_event_cid_date_id
|
||||
ON public.pretixbase_logentry (event_id, content_type_id, datetime, id);
|
||||
|
||||
|
||||
Also, if you use our ``pretix-shipping`` plugin::
|
||||
|
||||
@@ -14,7 +14,7 @@ This has some trade-offs in terms of performance and isolation but allows a rath
|
||||
get it right. If you're not feeling comfortable managing a Linux server, check out our hosting and service
|
||||
offers at `pretix.eu`_.
|
||||
|
||||
We tested this guide on the Linux distribution **Debian 8.0** but it should work very similar on other
|
||||
We tested this guide on the Linux distribution **Debian 11.0** but it should work very similar on other
|
||||
modern distributions, especially on all systemd-based ones.
|
||||
|
||||
Requirements
|
||||
@@ -26,7 +26,7 @@ installation guides):
|
||||
* `Docker`_
|
||||
* A SMTP server to send out mails, e.g. `Postfix`_ on your machine or some third-party server you have credentials for
|
||||
* A HTTP reverse proxy, e.g. `nginx`_ or Apache to allow HTTPS connections
|
||||
* A `PostgreSQL`_ 9.6+, `MySQL`_ 5.7+, or MariaDB 10.2.7+ database server
|
||||
* A `PostgreSQL`_ 9.6+ database server
|
||||
* A `redis`_ server
|
||||
|
||||
We also recommend that you use a firewall, although this is not a pretix-specific recommendation. If you're new to
|
||||
@@ -58,9 +58,6 @@ directory writable to the user that runs pretix inside the docker container::
|
||||
Database
|
||||
--------
|
||||
|
||||
.. warning:: **Please use PostgreSQL for all new installations**. If you need to go for MySQL, make sure you run
|
||||
**MySQL 5.7 or newer** or **MariaDB 10.2.7 or newer**.
|
||||
|
||||
Next, we need a database and a database user. We can create these with any kind of database managing tool or directly on
|
||||
our database's shell. Please make sure that UTF8 is used as encoding for the best compatibility. You can check this with
|
||||
the following command::
|
||||
@@ -86,13 +83,6 @@ Restart PostgreSQL after you changed these files::
|
||||
|
||||
If you have a firewall running, you should also make sure that port 5432 is reachable from the ``172.17.0.1/16`` subnet.
|
||||
|
||||
For MySQL, you can either also use network-based connections or mount the ``/var/run/mysqld/mysqld.sock`` socket into the docker container.
|
||||
When using MySQL, make sure you set the character set of the database to ``utf8mb4``, e.g. like this::
|
||||
|
||||
mysql > CREATE DATABASE pretix DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
You will also need to make sure that ``sql_mode`` in your ``my.cnf`` file does **not** include ``ONLY_FULL_GROUP_BY``.
|
||||
|
||||
Redis
|
||||
-----
|
||||
|
||||
@@ -152,15 +142,13 @@ Fill the configuration file ``/etc/pretix/pretix.cfg`` with the following conten
|
||||
trust_x_forwarded_proto=on
|
||||
|
||||
[database]
|
||||
; Replace postgresql with mysql for MySQL
|
||||
backend=postgresql
|
||||
name=pretix
|
||||
user=pretix
|
||||
; Replace with the password you chose above
|
||||
password=*********
|
||||
; In most docker setups, 172.17.0.1 is the address of the docker host. Adjust
|
||||
; this to wherever your database is running, e.g. the name of a linked container
|
||||
; or of a mounted MySQL socket.
|
||||
; this to wherever your database is running, e.g. the name of a linked container.
|
||||
host=172.17.0.1
|
||||
|
||||
[mail]
|
||||
@@ -212,8 +200,6 @@ named ``/etc/systemd/system/pretix.service`` with the following content::
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
When using MySQL and socket mounting, you'll need the additional flag ``-v /var/run/mysqld:/var/run/mysqld`` in the command.
|
||||
|
||||
You can now run the following commands
|
||||
to enable and start the service::
|
||||
|
||||
@@ -339,7 +325,6 @@ workers, e.g. ``docker run … taskworker -Q notifications --concurrency 32``.
|
||||
.. _nginx: https://botleg.com/stories/https-with-lets-encrypt-and-nginx/
|
||||
.. _Let's Encrypt: https://letsencrypt.org/
|
||||
.. _pretix.eu: https://pretix.eu/
|
||||
.. _MySQL: https://dev.mysql.com/doc/refman/5.7/en/linux-installation-apt-repo.html
|
||||
.. _PostgreSQL: https://www.digitalocean.com/community/tutorials/how-to-install-and-use-postgresql-on-ubuntu-20-04
|
||||
.. _redis: https://blog.programster.org/debian-8-install-redis-server/
|
||||
.. _ufw: https://en.wikipedia.org/wiki/Uncomplicated_Firewall
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.. highlight:: ini
|
||||
|
||||
.. spelling:: SQL
|
||||
.. spelling:word-list:: SQL
|
||||
|
||||
General remarks
|
||||
===============
|
||||
|
||||
@@ -12,7 +12,7 @@ solution with many things readily set-up, look at :ref:`dockersmallscale`.
|
||||
get it right. If you're not feeling comfortable managing a Linux server, check out our hosting and service
|
||||
offers at `pretix.eu`_.
|
||||
|
||||
We tested this guide on the Linux distribution **Debian 10.0** but it should work very similar on other
|
||||
We tested this guide on the Linux distribution **Debian 11.6** but it should work very similar on other
|
||||
modern distributions, especially on all systemd-based ones.
|
||||
|
||||
Requirements
|
||||
@@ -23,7 +23,7 @@ installation guides):
|
||||
|
||||
* A SMTP server to send out mails, e.g. `Postfix`_ on your machine or some third-party server you have credentials for
|
||||
* A HTTP reverse proxy, e.g. `nginx`_ or Apache to allow HTTPS connections
|
||||
* A `PostgreSQL`_ 9.6+, `MySQL`_ 5.7+, or MariaDB 10.2.7+ database server
|
||||
* A `PostgreSQL`_ 11+ database server
|
||||
* A `redis`_ server
|
||||
* A `nodejs`_ installation
|
||||
|
||||
@@ -47,9 +47,6 @@ In this guide, all code lines prepended with a ``#`` symbol are commands that yo
|
||||
Database
|
||||
--------
|
||||
|
||||
.. warning:: **Please use PostgreSQL for all new installations**. If you need to go for MySQL, make sure you run
|
||||
**MySQL 5.7 or newer** or **MariaDB 10.2.7 or newer**.
|
||||
|
||||
Having the database server installed, we still need a database and a database user. We can create these with any kind
|
||||
of database managing tool or directly on our database's shell. Please make sure that UTF8 is used as encoding for the
|
||||
best compatibility. You can check this with the following command::
|
||||
@@ -61,12 +58,6 @@ For PostgreSQL database creation, we would do::
|
||||
# sudo -u postgres createuser pretix
|
||||
# sudo -u postgres createdb -O pretix pretix
|
||||
|
||||
When using MySQL, make sure you set the character set of the database to ``utf8mb4``, e.g. like this::
|
||||
|
||||
mysql > CREATE DATABASE pretix DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
You will also need to make sure that ``sql_mode`` in your ``my.cnf`` file does **not** include ``ONLY_FULL_GROUP_BY``.
|
||||
|
||||
Package dependencies
|
||||
--------------------
|
||||
|
||||
@@ -74,7 +65,7 @@ To build and run pretix, you will need the following debian packages::
|
||||
|
||||
# apt-get install git build-essential python-dev python3-venv python3 python3-pip \
|
||||
python3-dev libxml2-dev libxslt1-dev libffi-dev zlib1g-dev libssl-dev \
|
||||
gettext libpq-dev libmariadb-dev libjpeg-dev libopenjp2-7-dev
|
||||
gettext libpq-dev libjpeg-dev libopenjp2-7-dev
|
||||
|
||||
Config file
|
||||
-----------
|
||||
@@ -97,16 +88,12 @@ Fill the configuration file ``/etc/pretix/pretix.cfg`` with the following conten
|
||||
trust_x_forwarded_proto=on
|
||||
|
||||
[database]
|
||||
; For MySQL, replace with "mysql"
|
||||
backend=postgresql
|
||||
name=pretix
|
||||
user=pretix
|
||||
; For MySQL, enter the user password. For PostgreSQL on the same host,
|
||||
; we don't need one because we can use peer authentification if our
|
||||
; PostgreSQL user matches our unix user.
|
||||
; For PostgreSQL on the same host, we don't need a password because we can use
|
||||
; peer authentication if our PostgreSQL user matches our unix user.
|
||||
password=
|
||||
; For MySQL, use local socket, e.g. /var/run/mysqld/mysqld.sock
|
||||
; For a remote host, supply an IP address
|
||||
; For local postgres authentication, you can leave it empty
|
||||
host=
|
||||
|
||||
@@ -140,11 +127,7 @@ We now install pretix, its direct dependencies and gunicorn::
|
||||
|
||||
(venv)$ pip3 install pretix gunicorn
|
||||
|
||||
If you're running MySQL, also install the client library::
|
||||
|
||||
(venv)$ pip3 install mysqlclient
|
||||
|
||||
Note that you need Python 3.7 or newer. You can find out your Python version using ``python -V``.
|
||||
Note that you need Python 3.9 or newer. You can find out your Python version using ``python -V``.
|
||||
|
||||
We also need to create a data directory::
|
||||
|
||||
@@ -318,12 +301,32 @@ example::
|
||||
(venv)$ python -m pretix rebuild
|
||||
# systemctl restart pretix-web pretix-worker
|
||||
|
||||
System updates
|
||||
--------------
|
||||
|
||||
After system updates, such as updates to a new Ubuntu or Debian release, you might be using a new Python version.
|
||||
That's great, but requires some adjustments. First, adjust any old version paths in your nginx configuration file.
|
||||
Then, re-create your Python environment::
|
||||
|
||||
$ source /var/pretix/venv/bin/activate
|
||||
(venv)$ pip3 freeze > /tmp/pip-backup.txt
|
||||
$ rm -rf /var/pretix/venv
|
||||
$ python3 -m venv /var/pretix/venv
|
||||
$ source /var/pretix/venv/bin/activate
|
||||
(venv)$ pip3 install -U pip wheel setuptools
|
||||
(venv)$ pip3 install -r /tmp/pip-backup.txt
|
||||
|
||||
Then, proceed like after any plugin installation::
|
||||
|
||||
(venv)$ python -m pretix migrate
|
||||
(venv)$ python -m pretix rebuild
|
||||
(venv)$ python -m pretix updatestyles
|
||||
# systemctl restart pretix-web pretix-worker
|
||||
|
||||
.. _Postfix: https://www.digitalocean.com/community/tutorials/how-to-install-and-configure-postfix-as-a-send-only-smtp-server-on-ubuntu-16-04
|
||||
.. _nginx: https://botleg.com/stories/https-with-lets-encrypt-and-nginx/
|
||||
.. _Let's Encrypt: https://letsencrypt.org/
|
||||
.. _pretix.eu: https://pretix.eu/
|
||||
.. _MySQL: https://dev.mysql.com/doc/refman/5.7/en/linux-installation-apt-repo.html
|
||||
.. _PostgreSQL: https://www.digitalocean.com/community/tutorials/how-to-install-and-use-postgresql-on-ubuntu-20-04
|
||||
.. _redis: https://blog.programster.org/debian-8-install-redis-server/
|
||||
.. _ufw: https://en.wikipedia.org/wiki/Uncomplicated_Firewall
|
||||
|
||||
@@ -17,11 +17,11 @@ Backups
|
||||
There are essentially two things which you should create backups of:
|
||||
|
||||
Database
|
||||
Your SQL database (MySQL or PostgreSQL). This is critical and you should **absolutely
|
||||
always create automatic backups of your database**. There are tons of tutorials on the
|
||||
internet on how to do this, and the exact process depends on the choice of your database.
|
||||
For MySQL, see ``mysqldump`` and for PostgreSQL, see the ``pg_dump`` tool. You probably
|
||||
want to create a cronjob that does the backups for you on a regular schedule.
|
||||
Your SQL database. This is critical and you should **absolutely always create automatic
|
||||
backups of your database**. There are tons of tutorials on the internet on how to do this,
|
||||
and the exact process depends on the choice of your database. For PostgreSQL, see the
|
||||
``pg_dump`` tool. You probably want to create a cronjob that does the backups for you on a
|
||||
regular schedule.
|
||||
|
||||
Data directory
|
||||
The data directory of your pretix configuration might contain some things that you should
|
||||
|
||||
156
doc/admin/mysql2postgres.rst
Normal file
@@ -0,0 +1,156 @@
|
||||
.. highlight:: none
|
||||
|
||||
Migrating from MySQL/MariaDB to PostgreSQL
|
||||
==========================================
|
||||
|
||||
Our recommended database for all production installations is PostgreSQL. Support for MySQL/MariaDB will be removed in
|
||||
pretix 5.0.
|
||||
|
||||
In order to follow this guide, your pretix installation needs to be a version that fully supports MySQL/MariaDB. If you
|
||||
already upgraded to pretix 5.0, downgrade back to the last 4.x release using ``pip``.
|
||||
|
||||
.. note:: We have tested this guide carefully, but we can't assume any liability for its correctness. The data loss
|
||||
risk should be low as long as pretix is not running while you do the migration. If you are a pretix Enterprise
|
||||
customer, feel free to reach out in advance if you want us to support you along the way.
|
||||
|
||||
Update database schema
|
||||
----------------------
|
||||
|
||||
Before you start, make sure your database schema is up to date::
|
||||
|
||||
# sudo -u pretix -s
|
||||
$ source /var/pretix/venv/bin/activate
|
||||
(venv)$ python -m pretix migrate
|
||||
|
||||
Install PostgreSQL
|
||||
------------------
|
||||
|
||||
Now, install and set up a PostgreSQL server. For a local installation on Debian or Ubuntu, use::
|
||||
|
||||
# apt install postgresql
|
||||
|
||||
Having the database server installed, we still need a database and a database user. We can create these with any kind
|
||||
of database managing tool or directly on our database's shell. Please make sure that UTF8 is used as encoding for the
|
||||
best compatibility. You can check this with the following command::
|
||||
|
||||
# sudo -u postgres psql -c 'SHOW SERVER_ENCODING'
|
||||
|
||||
Without Docker
|
||||
""""""""""""""
|
||||
|
||||
For our standard manual installation, create the database and user like this::
|
||||
|
||||
# sudo -u postgres createuser pretix
|
||||
# sudo -u postgres createdb -O pretix pretix
|
||||
|
||||
With Docker
|
||||
"""""""""""
|
||||
|
||||
For our standard docker installation, create the database and user like this::
|
||||
|
||||
# sudo -u postgres createuser -P pretix
|
||||
# sudo -u postgres createdb -O pretix pretix
|
||||
|
||||
Make sure that your database listens on the network. If PostgreSQL on the same same host as docker, but not inside a docker container, we recommend that you just listen on the Docker interface by changing the following line in ``/etc/postgresql/<version>/main/postgresql.conf``::
|
||||
|
||||
listen_addresses = 'localhost,172.17.0.1'
|
||||
|
||||
You also need to add a new line to ``/etc/postgresql/<version>/main/pg_hba.conf`` to allow network connections to this user and database::
|
||||
|
||||
host pretix pretix 172.17.0.1/16 md5
|
||||
|
||||
Restart PostgreSQL after you changed these files::
|
||||
|
||||
# systemctl restart postgresql
|
||||
|
||||
If you have a firewall running, you should also make sure that port 5432 is reachable from the ``172.17.0.1/16`` subnet.
|
||||
|
||||
Of course, instead of all this you can also run a PostgreSQL docker container and link it to the pretix container.
|
||||
|
||||
Stop pretix
|
||||
-----------
|
||||
|
||||
To prevent any more changes to your data, stop pretix from running::
|
||||
|
||||
# systemctl stop pretix-web pretix-worker
|
||||
|
||||
Change configuration
|
||||
--------------------
|
||||
|
||||
Change the database configuration in your ``/etc/pretix/pretix.cfg`` file::
|
||||
|
||||
[database]
|
||||
backend=postgresql
|
||||
name=pretix
|
||||
user=pretix
|
||||
password= ; only required for docker or remote database, can be kept empty for local auth
|
||||
host= ; set to 172.17.0.1 in docker setup, keep empty for local auth
|
||||
|
||||
|
||||
Create database schema
|
||||
-----------------------
|
||||
|
||||
To create the schema in your new PostgreSQL database, use the following commands::
|
||||
|
||||
# sudo -u pretix -s
|
||||
$ source /var/pretix/venv/bin/activate
|
||||
(venv)$ python -m pretix migrate
|
||||
|
||||
|
||||
Migrate your data
|
||||
-----------------
|
||||
|
||||
Install ``pgloader``::
|
||||
|
||||
# apt install pgloader
|
||||
|
||||
.. note::
|
||||
|
||||
If you are using Ubuntu 20.04, the ``pgloader`` version from the repositories seems to be incompatible with PostgreSQL
|
||||
12+. You can install ``pgloader`` from the `PostgreSQL repositories`_ instead.
|
||||
See also `this discussion <https://github.com/pretix/pretix/issues/3090>`_.
|
||||
|
||||
Create a new file ``/tmp/pretix.load``, replacing the MySQL and PostgreSQL connection strings with the correct user names, passwords, and/or database names::
|
||||
|
||||
LOAD DATABASE
|
||||
FROM mysql://pretix:password@localhost/pretix -- replace with mysql://username:password@hostname/dbname
|
||||
INTO postgresql:///pretix -- replace with dbname
|
||||
|
||||
WITH data only, include no drop, truncate, disable triggers,
|
||||
create no indexes, drop indexes, reset sequences
|
||||
|
||||
ALTER SCHEMA 'pretix' RENAME TO 'public' -- replace pretix with the name of the MySQL database
|
||||
|
||||
ALTER TABLE NAMES MATCHING ~/.*/
|
||||
SET SCHEMA 'public'
|
||||
|
||||
SET timezone TO '+00:00'
|
||||
|
||||
SET PostgreSQL PARAMETERS
|
||||
maintenance_work_mem to '128MB',
|
||||
work_mem to '12MB';
|
||||
|
||||
Then, run::
|
||||
|
||||
# sudo -u postgres pgloader /tmp/pretix.load
|
||||
|
||||
The output should end with a table summarizing the results for every table. You can ignore warnings about type casts
|
||||
and missing constraints.
|
||||
|
||||
Afterwards, delete the file again::
|
||||
|
||||
# rm -rf /tmp/pretix.load
|
||||
|
||||
Start pretix
|
||||
------------
|
||||
|
||||
Now, restart pretix. Maybe stop your MySQL server as a verification step that you are no longer using it::
|
||||
|
||||
# systemctl stop mariadb
|
||||
# systemctl start pretix-web pretix-worker
|
||||
|
||||
And you're done! After you've verified everything has been copied correctly, you can delete the old MySQL database.
|
||||
|
||||
.. note:: Don't forget to update your backup process to back up your PostgreSQL database instead of your MySQL database now.
|
||||
|
||||
.. _PostgreSQL repositories: https://wiki.postgresql.org/wiki/Apt
|
||||
@@ -42,7 +42,7 @@ A pretix installation usually consists of the following components which run per
|
||||
|
||||
* ``pretix-worker`` is a Celery-based application that processes tasks that should be run asynchronously outside of the web application process.
|
||||
|
||||
* A **SQL database** keeps all the important data and processes the actual transactions. We recommend using PostgreSQL, but MySQL/MariaDB works as well.
|
||||
* A **PostgreSQL database** keeps all the important data and processes the actual transactions.
|
||||
|
||||
* A **web server** that terminates TLS and HTTP connections and forwards them to ``pretix-web``. In some cases, e.g. when serving static files, the web servers might return a response directly. We recommend using ``nginx``.
|
||||
|
||||
@@ -74,7 +74,7 @@ We recommend reading up on tuning your web server for high concurrency. For ngin
|
||||
processes and the number of connections each worker process accepts. Double-check that TLS session caching works, because TLS
|
||||
handshakes can get really expensive.
|
||||
|
||||
During a traffic peak, your web server will be able to make us of more CPU resources, while memory usage will stay comparatively low,
|
||||
During a traffic peak, your web server will be able to make use of more CPU resources, while memory usage will stay comparatively low,
|
||||
so if you invest in more hardware here, invest in more and faster CPU cores.
|
||||
|
||||
Make sure that pretix' static files (such as CSS and JavaScript assets) as well as user-uploaded media files (event logos, etc)
|
||||
|
||||
@@ -48,10 +48,11 @@ Possible permissions are:
|
||||
Compatibility
|
||||
-------------
|
||||
|
||||
We currently see pretix' API as a beta-stage feature. We therefore do not give any guarantees
|
||||
for compatibility between feature releases of pretix (such as 1.5 and 1.6). However, as always,
|
||||
we try not to break things when we don't need to. Any backwards-incompatible changes will be
|
||||
prominently noted in the release notes.
|
||||
We try to avoid any breaking changes to our API to avoid hassle on your end. If possible, we'll
|
||||
build new features in a way that keeps all pre-existing API usage unchanged. In some cases,
|
||||
this might not be possible or only possible with restrictions. In these case, any
|
||||
backwards-incompatible changes will be prominently noted in the "Changes to the REST API"
|
||||
section of our release notes. If possible, we will announce them multiple releases in advance.
|
||||
|
||||
We treat the following types of changes as *backwards-compatible* so we ask you to make sure
|
||||
that your clients can deal with them properly:
|
||||
@@ -60,6 +61,7 @@ that your clients can deal with them properly:
|
||||
* Support of new HTTP methods for a given API endpoint
|
||||
* Support of new query parameters for a given API endpoint
|
||||
* New fields contained in API responses
|
||||
* New possible values of enumeration-like fields
|
||||
* Response body structure or message texts on failed requests (``4xx``, ``5xx`` response codes)
|
||||
|
||||
We treat the following types of changes as *backwards-incompatible*:
|
||||
@@ -190,6 +192,9 @@ Relative date *either* String in ISO 8601 ``"2017-12-27"``,
|
||||
File URL in responses, ``file:`` ``"https://…"``, ``"file:…"``
|
||||
specifiers in requests
|
||||
(see below).
|
||||
Date range *either* two dates separated ``2022-03-18/2022-03-23``, ``2022-03-18/``,
|
||||
by ``/`` *or* the name of a ``/2022-03-23``, ``week_this``, ``week_next``,
|
||||
defined range. ``month_this``
|
||||
===================== ============================ ===================================
|
||||
|
||||
Query parameters
|
||||
|
||||
@@ -107,9 +107,9 @@ You can supply a valid access token as a ``Bearer``-type token in the ``Authoriz
|
||||
.. sourcecode:: http
|
||||
:emphasize-lines: 3
|
||||
|
||||
GET /api/v1/organizers/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Authorization: Bearer i3ytqTSRWsKp16fqjekHXa4tdM4qNC
|
||||
GET /api/v1/organizers/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Authorization: Bearer i3ytqTSRWsKp16fqjekHXa4tdM4qNC
|
||||
|
||||
Refreshing an access token
|
||||
--------------------------
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.. spelling:: checkin
|
||||
.. spelling:word-list:: checkin
|
||||
|
||||
.. _rest-checkin:
|
||||
|
||||
@@ -203,6 +203,8 @@ Checking a ticket in
|
||||
|
||||
* ``invalid`` - Ticket is not known.
|
||||
* ``unpaid`` - Ticket is not paid for.
|
||||
* ``blocked`` - Ticket has been blocked.
|
||||
* ``invalid_time`` - Ticket is not valid at this time.
|
||||
* ``canceled`` – Ticket is canceled or expired.
|
||||
* ``already_redeemed`` - Ticket already has been redeemed.
|
||||
* ``product`` - Tickets with this product may not be scanned at this device.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.. spelling:: checkin
|
||||
.. spelling:word-list:: checkin
|
||||
|
||||
.. _rest-checkinlists:
|
||||
|
||||
@@ -98,6 +98,8 @@ Endpoints
|
||||
:query string ends_after: Exclude all check-in lists attached to a sub-event that is already in the past at the given time.
|
||||
:query string expand: Expand a field into a full object. Currently only ``subevent`` is supported. Can be passed multiple times.
|
||||
:query string exclude: Exclude a field from the output, e.g. ``checkin_count``. Can be used as a performance optimization. Can be passed multiple times.
|
||||
:query string ordering: Manually set the ordering of results. Valid fields to be used are ``id``, ``name``, and ``subevent__date_from``,
|
||||
Default: ``subevent__date_from,name``
|
||||
:param organizer: The ``slug`` field of the organizer to fetch
|
||||
:param event: The ``slug`` field of the event to fetch
|
||||
:statuscode 200: no error
|
||||
@@ -362,7 +364,7 @@ Endpoints
|
||||
Stores a failed check-in. Only necessary for statistical purposes if you perform scan validation offline.
|
||||
|
||||
:<json boolean error_reason: One of ``canceled``, ``invalid``, ``unpaid``, ``product``, ``rules``, ``revoked``,
|
||||
``incomplete``, ``already_redeemed``, or ``error``. Required.
|
||||
``incomplete``, ``already_redeemed``, ``blocked``, ``invalid_time``, or ``error``. Required.
|
||||
:<json raw_barcode: The raw barcode you scanned. Required.
|
||||
:<json datetime: Date and time of the scan. Optional.
|
||||
:<json type: Type of scan, defaults to ``"entry"``.
|
||||
@@ -741,6 +743,8 @@ Order position endpoints
|
||||
|
||||
* ``invalid`` - Ticket code not known.
|
||||
* ``unpaid`` - Ticket is not paid for.
|
||||
* ``blocked`` - Ticket has been blocked.
|
||||
* ``invalid_time`` - Ticket is not valid at this time.
|
||||
* ``canceled`` – Ticket is canceled or expired. This reason is only sent when your request sets.
|
||||
``canceled_supported`` to ``true``, otherwise these orders return ``unpaid``.
|
||||
* ``already_redeemed`` - Ticket already has been redeemed.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.. spelling:: fullname
|
||||
.. spelling:word-list:: fullname
|
||||
|
||||
.. _`rest-devices`:
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.. spelling::
|
||||
.. spelling:word-list::
|
||||
|
||||
geo
|
||||
lat
|
||||
@@ -49,6 +49,7 @@ valid_keys object Cryptographic k
|
||||
only contained in detail views. Value can be cached.
|
||||
sales_channels list A list of sales channels this event is available for
|
||||
sale on.
|
||||
public_url string The public, customer-facing URL of the event (read-only).
|
||||
===================================== ========================== =======================================================
|
||||
|
||||
|
||||
@@ -65,6 +66,10 @@ Endpoints
|
||||
|
||||
The ``search`` query parameter has been added to filter events by their slug, name, or location in any language.
|
||||
|
||||
.. versionchanged:: 4.17
|
||||
|
||||
The ``public_url`` field has been added.
|
||||
|
||||
.. http:get:: /api/v1/organizers/(organizer)/events/
|
||||
|
||||
Returns a list of all events within a given organizer the authenticated user/token has access to.
|
||||
@@ -123,7 +128,8 @@ Endpoints
|
||||
"web",
|
||||
"pretixpos",
|
||||
"resellers"
|
||||
]
|
||||
],
|
||||
"public_url": "https://pretix.eu/bigevents/sampleconf/"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -211,7 +217,8 @@ Endpoints
|
||||
"web",
|
||||
"pretixpos",
|
||||
"resellers"
|
||||
]
|
||||
],
|
||||
"public_url": "https://pretix.eu/bigevents/sampleconf/"
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to fetch
|
||||
@@ -307,7 +314,8 @@ Endpoints
|
||||
"web",
|
||||
"pretixpos",
|
||||
"resellers"
|
||||
]
|
||||
],
|
||||
"public_url": "https://pretix.eu/bigevents/sampleconf/"
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer of the event to create.
|
||||
@@ -411,7 +419,8 @@ Endpoints
|
||||
"web",
|
||||
"pretixpos",
|
||||
"resellers"
|
||||
]
|
||||
],
|
||||
"public_url": "https://pretix.eu/bigevents/sampleconf/"
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer of the event to create.
|
||||
@@ -485,7 +494,8 @@ Endpoints
|
||||
"web",
|
||||
"pretixpos",
|
||||
"resellers"
|
||||
]
|
||||
],
|
||||
"public_url": "https://pretix.eu/bigevents/sampleconf/"
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer of the event to update
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.. spelling:: checkin
|
||||
.. spelling:word-list:: checkin
|
||||
|
||||
Data exporters
|
||||
==============
|
||||
|
||||
@@ -42,6 +42,7 @@ introductory_text string Text to be prin
|
||||
additional_text string Text to be printed below the product list
|
||||
payment_provider_text string Text to be printed below the product list with
|
||||
payment information
|
||||
payment_provider_stamp string Short text to be visibly printed to indicate payment status
|
||||
footer_text string Text to be printed in the page footer area
|
||||
lines list of objects The actual invoice contents
|
||||
├ position integer Number of the line within an invoice.
|
||||
@@ -178,6 +179,7 @@ Endpoints
|
||||
"internal_reference": "",
|
||||
"additional_text": "We are looking forward to see you on our conference!",
|
||||
"payment_provider_text": "Please transfer the money to our account ABC…",
|
||||
"payment_provider_stamp": null,
|
||||
"footer_text": "Big Events LLC - Registration No. 123456 - VAT ID: EU0987654321",
|
||||
"lines": [
|
||||
{
|
||||
@@ -268,6 +270,7 @@ Endpoints
|
||||
"internal_reference": "",
|
||||
"additional_text": "We are looking forward to see you on our conference!",
|
||||
"payment_provider_text": "Please transfer the money to our account ABC…",
|
||||
"payment_provider_stamp": null,
|
||||
"footer_text": "Big Events LLC - Registration No. 123456 - VAT ID: EU0987654321",
|
||||
"lines": [
|
||||
{
|
||||
|
||||
@@ -24,6 +24,9 @@ active boolean If ``false``, t
|
||||
description multi-lingual string A public description of the variation. May contain
|
||||
Markdown syntax or can be ``null``.
|
||||
position integer An integer, used for sorting
|
||||
checkin_attention boolean If ``true``, the check-in app should show a warning
|
||||
that this ticket requires special attention if such
|
||||
a variation is being scanned.
|
||||
require_approval boolean If ``true``, orders with this variation will need to be
|
||||
approved by the event organizer before they can be
|
||||
paid.
|
||||
@@ -48,7 +51,7 @@ meta_data object Values set for
|
||||
|
||||
.. versionchanged:: 4.16
|
||||
|
||||
The ``meta_data`` attribute has been added.
|
||||
The ``meta_data`` and ``checkin_attention`` attributes have been added.
|
||||
|
||||
Endpoints
|
||||
---------
|
||||
@@ -84,6 +87,7 @@ Endpoints
|
||||
"en": "S"
|
||||
},
|
||||
"active": true,
|
||||
"checkin_attention": false,
|
||||
"require_approval": false,
|
||||
"require_membership": false,
|
||||
"require_membership_hidden": false,
|
||||
@@ -107,6 +111,7 @@ Endpoints
|
||||
"en": "L"
|
||||
},
|
||||
"active": true,
|
||||
"checkin_attention": false,
|
||||
"require_approval": false,
|
||||
"require_membership": false,
|
||||
"require_membership_hidden": false,
|
||||
@@ -159,6 +164,7 @@ Endpoints
|
||||
"price": "10.00",
|
||||
"original_price": null,
|
||||
"active": true,
|
||||
"checkin_attention": false,
|
||||
"require_approval": false,
|
||||
"require_membership": false,
|
||||
"require_membership_hidden": false,
|
||||
@@ -197,6 +203,7 @@ Endpoints
|
||||
"value": {"en": "Student"},
|
||||
"default_price": "10.00",
|
||||
"active": true,
|
||||
"checkin_attention": false,
|
||||
"require_approval": false,
|
||||
"require_membership": false,
|
||||
"require_membership_hidden": false,
|
||||
@@ -225,6 +232,7 @@ Endpoints
|
||||
"price": "10.00",
|
||||
"original_price": null,
|
||||
"active": true,
|
||||
"checkin_attention": false,
|
||||
"require_approval": false,
|
||||
"require_membership": false,
|
||||
"require_membership_hidden": false,
|
||||
@@ -284,6 +292,7 @@ Endpoints
|
||||
"price": "10.00",
|
||||
"original_price": null,
|
||||
"active": false,
|
||||
"checkin_attention": false,
|
||||
"require_approval": false,
|
||||
"require_membership": false,
|
||||
"require_membership_hidden": false,
|
||||
|
||||
@@ -11,141 +11,165 @@ The item resource contains the following public fields:
|
||||
|
||||
.. rst-class:: rest-resource-table
|
||||
|
||||
===================================== ========================== =======================================================
|
||||
Field Type Description
|
||||
===================================== ========================== =======================================================
|
||||
id integer Internal ID of the item
|
||||
name multi-lingual string The item's visible name
|
||||
internal_name string An optional name that is only used in the backend
|
||||
default_price money (string) The item price that is applied if the price is not
|
||||
overwritten by variations or other options.
|
||||
category integer The ID of the category this item belongs to
|
||||
(or ``null``).
|
||||
active boolean If ``false``, the item is hidden from all public lists
|
||||
and will not be sold.
|
||||
description multi-lingual string A public description of the item. May contain Markdown
|
||||
syntax or can be ``null``.
|
||||
free_price boolean If ``true``, customers can change the price at which
|
||||
they buy the product (however, the price can't be set
|
||||
lower than the price defined by ``default_price`` or
|
||||
otherwise).
|
||||
tax_rate decimal (string) The VAT rate to be applied for this item (read-only,
|
||||
set through ``tax_rule``).
|
||||
tax_rule integer The internal ID of the applied tax rule (or ``null``).
|
||||
admission boolean ``true`` for items that grant admission to the event
|
||||
(such as primary tickets) and ``false`` for others
|
||||
(such as add-ons or merchandise).
|
||||
position integer An integer, used for sorting
|
||||
picture file A product picture to be displayed in the shop
|
||||
(can be ``null``).
|
||||
sales_channels list of strings Sales channels this product is available on, such as
|
||||
``"web"`` or ``"resellers"``. Defaults to ``["web"]``.
|
||||
available_from datetime The first date time at which this item can be bought
|
||||
(or ``null``).
|
||||
available_until datetime The last date time at which this item can be bought
|
||||
(or ``null``).
|
||||
hidden_if_available integer The internal ID of a quota object, or ``null``. If
|
||||
set, this item won't be shown publicly as long as this
|
||||
quota is available.
|
||||
require_voucher boolean If ``true``, this item can only be bought using a
|
||||
voucher that is specifically assigned to this item.
|
||||
hide_without_voucher boolean If ``true``, this item is only shown during the voucher
|
||||
redemption process, but not in the normal shop
|
||||
frontend.
|
||||
allow_cancel boolean If ``false``, customers cannot cancel orders containing
|
||||
this item.
|
||||
min_per_order integer This product can only be bought if it is included at
|
||||
least this many times in the order (or ``null`` for no
|
||||
limitation).
|
||||
max_per_order integer This product can only be bought if it is included at
|
||||
most this many times in the order (or ``null`` for no
|
||||
limitation).
|
||||
checkin_attention boolean If ``true``, the check-in app should show a warning
|
||||
that this ticket requires special attention if such
|
||||
a product is being scanned.
|
||||
original_price money (string) An original price, shown for comparison, not used
|
||||
for price calculations (or ``null``).
|
||||
require_approval boolean If ``true``, orders with this product will need to be
|
||||
approved by the event organizer before they can be
|
||||
paid.
|
||||
require_bundling boolean If ``true``, this item is only available as part of bundles.
|
||||
require_membership boolean If ``true``, booking this item requires an active membership.
|
||||
require_membership_hidden boolean If ``true`` and ``require_membership`` is set, this product will
|
||||
be hidden from users without a valid membership.
|
||||
require_membership_types list of integers Internal IDs of membership types valid if ``require_membership`` is ``true``
|
||||
grant_membership_type integer If set to the internal ID of a membership type, purchasing this item will
|
||||
create a membership of the given type.
|
||||
grant_membership_duration_like_event boolean If ``true``, the membership created through ``grant_membership_type`` will derive
|
||||
its term from ``date_from`` to ``date_to`` of the purchased (sub)event.
|
||||
grant_membership_duration_days integer If ``grant_membership_duration_like_event`` is ``false``, this sets the number of
|
||||
days for the membership.
|
||||
grant_membership_duration_months integer If ``grant_membership_duration_like_event`` is ``false``, this sets the number of
|
||||
calendar months for the membership.
|
||||
generate_tickets boolean If ``false``, tickets are never generated for this
|
||||
product, regardless of other settings. If ``true``,
|
||||
tickets are generated even if this is a
|
||||
non-admission or add-on product, regardless of event
|
||||
settings. If this is ``null``, regular ticketing
|
||||
rules apply.
|
||||
allow_waitinglist boolean If ``false``, no waiting list will be shown for this
|
||||
product when it is sold out.
|
||||
issue_giftcard boolean If ``true``, buying this product will yield a gift card.
|
||||
show_quota_left boolean Publicly show how many tickets are still available.
|
||||
If this is ``null``, the event default is used.
|
||||
has_variations boolean Shows whether or not this item has variations.
|
||||
variations list of objects A list with one object for each variation of this item.
|
||||
Can be empty. Only writable during creation,
|
||||
use separate endpoint to modify this later.
|
||||
├ id integer Internal ID of the variation
|
||||
├ value multi-lingual string The "name" of the variation
|
||||
├ default_price money (string) The price set directly for this variation or ``null``
|
||||
├ price money (string) The price used for this variation. This is either the
|
||||
same as ``default_price`` if that value is set or equal
|
||||
to the item's ``default_price``.
|
||||
├ original_price money (string) An original price, shown for comparison, not used
|
||||
for price calculations (or ``null``).
|
||||
├ active boolean If ``false``, this variation will not be sold or shown.
|
||||
├ description multi-lingual string A public description of the variation. May contain
|
||||
├ require_membership boolean If ``true``, booking this variation requires an active membership.
|
||||
├ require_membership_hidden boolean If ``true`` and ``require_membership`` is set, this variation will
|
||||
be hidden from users without a valid membership.
|
||||
├ require_membership_types list of integers Internal IDs of membership types valid if ``require_membership`` is ``true``
|
||||
Markdown syntax or can be ``null``.
|
||||
├ sales_channels list of strings Sales channels this variation is available on, such as
|
||||
``"web"`` or ``"resellers"``. Defaults to all existing sales channels.
|
||||
The item-level list takes precedence, i.e. a sales
|
||||
channel needs to be on both lists for the item to be
|
||||
available.
|
||||
├ available_from datetime The first date time at which this variation can be bought
|
||||
(or ``null``).
|
||||
├ available_until datetime The last date time at which this variation can be bought
|
||||
(or ``null``).
|
||||
├ hide_without_voucher boolean If ``true``, this variation is only shown during the voucher
|
||||
redemption process, but not in the normal shop
|
||||
frontend.
|
||||
├ meta_data object Values set for event-specific meta data parameters.
|
||||
└ position integer An integer, used for sorting
|
||||
addons list of objects Definition of add-ons that can be chosen for this item.
|
||||
Only writable during creation,
|
||||
use separate endpoint to modify this later.
|
||||
├ addon_category integer Internal ID of the item category the add-on can be
|
||||
chosen from.
|
||||
├ min_count integer The minimal number of add-ons that need to be chosen.
|
||||
├ max_count integer The maximal number of add-ons that can be chosen.
|
||||
├ position integer An integer, used for sorting
|
||||
├ multi_allowed boolean Adding the same item multiple times is allowed
|
||||
└ price_included boolean Adding this add-on to the item is free
|
||||
bundles list of objects Definition of bundles that are included in this item.
|
||||
Only writable during creation,
|
||||
use separate endpoint to modify this later.
|
||||
├ bundled_item integer Internal ID of the item that is included.
|
||||
├ bundled_variation integer Internal ID of the variation of the item (or ``null``).
|
||||
├ count integer Number of items included
|
||||
└ designated_price money (string) Designated price of the bundled product. This will be
|
||||
used to split the price of the base item e.g. for mixed
|
||||
taxation. This is not added to the price.
|
||||
meta_data object Values set for event-specific meta data parameters.
|
||||
===================================== ========================== =======================================================
|
||||
======================================= ========================== =======================================================
|
||||
Field Type Description
|
||||
======================================= ========================== =======================================================
|
||||
id integer Internal ID of the item
|
||||
name multi-lingual string The item's visible name
|
||||
internal_name string An optional name that is only used in the backend
|
||||
default_price money (string) The item price that is applied if the price is not
|
||||
overwritten by variations or other options.
|
||||
category integer The ID of the category this item belongs to
|
||||
(or ``null``).
|
||||
active boolean If ``false``, the item is hidden from all public lists
|
||||
and will not be sold.
|
||||
description multi-lingual string A public description of the item. May contain Markdown
|
||||
syntax or can be ``null``.
|
||||
free_price boolean If ``true``, customers can change the price at which
|
||||
they buy the product (however, the price can't be set
|
||||
lower than the price defined by ``default_price`` or
|
||||
otherwise).
|
||||
tax_rate decimal (string) The VAT rate to be applied for this item (read-only,
|
||||
set through ``tax_rule``).
|
||||
tax_rule integer The internal ID of the applied tax rule (or ``null``).
|
||||
admission boolean ``true`` for items that grant admission to the event
|
||||
(such as primary tickets) and ``false`` for others
|
||||
(such as add-ons or merchandise).
|
||||
personalized boolean ``true`` for items that require personalization according
|
||||
to event settings. Only affects system-level fields, not
|
||||
custom questions. Currently only allowed for products with
|
||||
``admission`` set to ``true``. For backwards compatibility,
|
||||
when creating new items and this field is not given, it defaults
|
||||
to the same value as ``admission``.
|
||||
position integer An integer, used for sorting
|
||||
picture file A product picture to be displayed in the shop
|
||||
(can be ``null``).
|
||||
sales_channels list of strings Sales channels this product is available on, such as
|
||||
``"web"`` or ``"resellers"``. Defaults to ``["web"]``.
|
||||
available_from datetime The first date time at which this item can be bought
|
||||
(or ``null``).
|
||||
available_until datetime The last date time at which this item can be bought
|
||||
(or ``null``).
|
||||
hidden_if_available integer The internal ID of a quota object, or ``null``. If
|
||||
set, this item won't be shown publicly as long as this
|
||||
quota is available.
|
||||
require_voucher boolean If ``true``, this item can only be bought using a
|
||||
voucher that is specifically assigned to this item.
|
||||
hide_without_voucher boolean If ``true``, this item is only shown during the voucher
|
||||
redemption process, but not in the normal shop
|
||||
frontend.
|
||||
allow_cancel boolean If ``false``, customers cannot cancel orders containing
|
||||
this item.
|
||||
min_per_order integer This product can only be bought if it is included at
|
||||
least this many times in the order (or ``null`` for no
|
||||
limitation).
|
||||
max_per_order integer This product can only be bought if it is included at
|
||||
most this many times in the order (or ``null`` for no
|
||||
limitation).
|
||||
checkin_attention boolean If ``true``, the check-in app should show a warning
|
||||
that this ticket requires special attention if such
|
||||
a product is being scanned.
|
||||
original_price money (string) An original price, shown for comparison, not used
|
||||
for price calculations (or ``null``).
|
||||
require_approval boolean If ``true``, orders with this product will need to be
|
||||
approved by the event organizer before they can be
|
||||
paid.
|
||||
require_bundling boolean If ``true``, this item is only available as part of bundles.
|
||||
require_membership boolean If ``true``, booking this item requires an active membership.
|
||||
require_membership_hidden boolean If ``true`` and ``require_membership`` is set, this product will
|
||||
be hidden from users without a valid membership.
|
||||
require_membership_types list of integers Internal IDs of membership types valid if ``require_membership`` is ``true``
|
||||
grant_membership_type integer If set to the internal ID of a membership type, purchasing this item will
|
||||
create a membership of the given type.
|
||||
grant_membership_duration_like_event boolean If ``true``, the membership created through ``grant_membership_type`` will derive
|
||||
its term from ``date_from`` to ``date_to`` of the purchased (sub)event.
|
||||
grant_membership_duration_days integer If ``grant_membership_duration_like_event`` is ``false``, this sets the number of
|
||||
days for the membership.
|
||||
grant_membership_duration_months integer If ``grant_membership_duration_like_event`` is ``false``, this sets the number of
|
||||
calendar months for the membership.
|
||||
validity_mode string If ``null``, tickets generated for this product do not
|
||||
have special validity behavior, but follow event configuration and
|
||||
can be limited e.g. through check-in rules. Other values are ``"fixed"`` and ``"dynamic"``
|
||||
validity_fixed_from datetime If ``validity_mode`` is ``"fixed"``, this is the start of validity for issued tickets.
|
||||
validity_fixed_until datetime If ``validity_mode`` is ``"fixed"``, this is the end of validity for issued tickets.
|
||||
validity_dynamic_duration_minutes integer If ``validity_mode`` is ``"dynamic"``, this is the "minutes" component of the ticket validity duration.
|
||||
validity_dynamic_duration_hours integer If ``validity_mode`` is ``"dynamic"``, this is the "hours" component of the ticket validity duration.
|
||||
validity_dynamic_duration_days integer If ``validity_mode`` is ``"dynamic"``, this is the "days" component of the ticket validity duration.
|
||||
validity_dynamic_duration_months integer If ``validity_mode`` is ``"dynamic"``, this is the "months" component of the ticket validity duration.
|
||||
validity_dynamic_start_choice boolean If ``validity_mode`` is ``"dynamic"`` and this is ``true``, customers can choose the start of validity.
|
||||
validity_dynamic_start_choice_day_limit boolean If ``validity_mode`` is ``"dynamic"`` and ``validity_dynamic_start_choice`` is ``true``,
|
||||
this is the maximum number of days the start can be in the future.
|
||||
generate_tickets boolean If ``false``, tickets are never generated for this
|
||||
product, regardless of other settings. If ``true``,
|
||||
tickets are generated even if this is a
|
||||
non-admission or add-on product, regardless of event
|
||||
settings. If this is ``null``, regular ticketing
|
||||
rules apply.
|
||||
allow_waitinglist boolean If ``false``, no waiting list will be shown for this
|
||||
product when it is sold out.
|
||||
issue_giftcard boolean If ``true``, buying this product will yield a gift card.
|
||||
show_quota_left boolean Publicly show how many tickets are still available.
|
||||
If this is ``null``, the event default is used.
|
||||
has_variations boolean Shows whether or not this item has variations.
|
||||
variations list of objects A list with one object for each variation of this item.
|
||||
Can be empty. Only writable during creation,
|
||||
use separate endpoint to modify this later.
|
||||
├ id integer Internal ID of the variation
|
||||
├ value multi-lingual string The "name" of the variation
|
||||
├ default_price money (string) The price set directly for this variation or ``null``
|
||||
├ price money (string) The price used for this variation. This is either the
|
||||
same as ``default_price`` if that value is set or equal
|
||||
to the item's ``default_price``.
|
||||
├ original_price money (string) An original price, shown for comparison, not used
|
||||
for price calculations (or ``null``).
|
||||
├ active boolean If ``false``, this variation will not be sold or shown.
|
||||
├ description multi-lingual string A public description of the variation. May contain
|
||||
├ checkin_attention boolean If ``true``, the check-in app should show a warning
|
||||
that this ticket requires special attention if such
|
||||
a variation is being scanned.
|
||||
├ require_approval boolean If ``true``, orders with this variation will need to be
|
||||
approved by the event organizer before they can be
|
||||
paid.
|
||||
├ require_membership boolean If ``true``, booking this variation requires an active membership.
|
||||
├ require_membership_hidden boolean If ``true`` and ``require_membership`` is set, this variation will
|
||||
be hidden from users without a valid membership.
|
||||
├ require_membership_types list of integers Internal IDs of membership types valid if ``require_membership`` is ``true``
|
||||
Markdown syntax or can be ``null``.
|
||||
├ sales_channels list of strings Sales channels this variation is available on, such as
|
||||
``"web"`` or ``"resellers"``. Defaults to all existing sales channels.
|
||||
The item-level list takes precedence, i.e. a sales
|
||||
channel needs to be on both lists for the item to be
|
||||
available.
|
||||
├ available_from datetime The first date time at which this variation can be bought
|
||||
(or ``null``).
|
||||
├ available_until datetime The last date time at which this variation can be bought
|
||||
(or ``null``).
|
||||
├ hide_without_voucher boolean If ``true``, this variation is only shown during the voucher
|
||||
redemption process, but not in the normal shop
|
||||
frontend.
|
||||
├ meta_data object Values set for event-specific meta data parameters.
|
||||
└ position integer An integer, used for sorting
|
||||
addons list of objects Definition of add-ons that can be chosen for this item.
|
||||
Only writable during creation,
|
||||
use separate endpoint to modify this later.
|
||||
├ addon_category integer Internal ID of the item category the add-on can be
|
||||
chosen from.
|
||||
├ min_count integer The minimal number of add-ons that need to be chosen.
|
||||
├ max_count integer The maximal number of add-ons that can be chosen.
|
||||
├ position integer An integer, used for sorting
|
||||
├ multi_allowed boolean Adding the same item multiple times is allowed
|
||||
└ price_included boolean Adding this add-on to the item is free
|
||||
bundles list of objects Definition of bundles that are included in this item.
|
||||
Only writable during creation,
|
||||
use separate endpoint to modify this later.
|
||||
├ bundled_item integer Internal ID of the item that is included.
|
||||
├ bundled_variation integer Internal ID of the variation of the item (or ``null``).
|
||||
├ count integer Number of items included
|
||||
└ designated_price money (string) Designated price of the bundled product. This will be
|
||||
used to split the price of the base item e.g. for mixed
|
||||
taxation. This is not added to the price.
|
||||
meta_data object Values set for event-specific meta data parameters.
|
||||
======================================= ========================== =======================================================
|
||||
|
||||
.. versionchanged:: 4.0
|
||||
|
||||
@@ -158,7 +182,12 @@ meta_data object Values set for
|
||||
|
||||
.. versionchanged:: 4.16
|
||||
|
||||
The ``variations[x].meta_data`` attribute has been added.
|
||||
The ``variations[x].meta_data`` and ``variations[x].checkin_attention`` attributes have been added.
|
||||
The ``personalized`` attribute has been added.
|
||||
|
||||
.. versionchanged:: 4.17
|
||||
|
||||
The ``validity_*`` attributes have been added.
|
||||
|
||||
Notes
|
||||
-----
|
||||
@@ -213,6 +242,7 @@ Endpoints
|
||||
"tax_rate": "0.00",
|
||||
"tax_rule": 1,
|
||||
"admission": false,
|
||||
"personalized": false,
|
||||
"issue_giftcard": false,
|
||||
"meta_data": {},
|
||||
"position": 0,
|
||||
@@ -238,6 +268,14 @@ Endpoints
|
||||
"grant_membership_duration_like_event": true,
|
||||
"grant_membership_duration_days": 0,
|
||||
"grant_membership_duration_months": 0,
|
||||
"validity_fixed_from": null,
|
||||
"validity_fixed_until": null,
|
||||
"validity_dynamic_duration_minutes": null,
|
||||
"validity_dynamic_duration_hours": null,
|
||||
"validity_dynamic_duration_days": null,
|
||||
"validity_dynamic_duration_months": null,
|
||||
"validity_dynamic_start_choice": false,
|
||||
"validity_dynamic_start_choice_day_limit": null,
|
||||
"variations": [
|
||||
{
|
||||
"value": {"en": "Student"},
|
||||
@@ -245,6 +283,8 @@ Endpoints
|
||||
"price": "10.00",
|
||||
"original_price": null,
|
||||
"active": true,
|
||||
"checkin_attention": false,
|
||||
"require_approval": false,
|
||||
"require_membership": false,
|
||||
"require_membership_types": [],
|
||||
"sales_channels": ["web"],
|
||||
@@ -261,6 +301,8 @@ Endpoints
|
||||
"price": "23.00",
|
||||
"original_price": null,
|
||||
"active": true,
|
||||
"checkin_attention": false,
|
||||
"require_approval": false,
|
||||
"require_membership": false,
|
||||
"require_membership_types": [],
|
||||
"sales_channels": ["web"],
|
||||
@@ -329,6 +371,7 @@ Endpoints
|
||||
"tax_rate": "0.00",
|
||||
"tax_rule": 1,
|
||||
"admission": false,
|
||||
"personalized": false,
|
||||
"issue_giftcard": false,
|
||||
"meta_data": {},
|
||||
"position": 0,
|
||||
@@ -354,6 +397,14 @@ Endpoints
|
||||
"grant_membership_duration_like_event": true,
|
||||
"grant_membership_duration_days": 0,
|
||||
"grant_membership_duration_months": 0,
|
||||
"validity_fixed_from": null,
|
||||
"validity_fixed_until": null,
|
||||
"validity_dynamic_duration_minutes": null,
|
||||
"validity_dynamic_duration_hours": null,
|
||||
"validity_dynamic_duration_days": null,
|
||||
"validity_dynamic_duration_months": null,
|
||||
"validity_dynamic_start_choice": false,
|
||||
"validity_dynamic_start_choice_day_limit": null,
|
||||
"variations": [
|
||||
{
|
||||
"value": {"en": "Student"},
|
||||
@@ -361,6 +412,8 @@ Endpoints
|
||||
"price": "10.00",
|
||||
"original_price": null,
|
||||
"active": true,
|
||||
"checkin_attention": false,
|
||||
"require_approval": false,
|
||||
"require_membership": false,
|
||||
"require_membership_types": [],
|
||||
"description": null,
|
||||
@@ -377,6 +430,8 @@ Endpoints
|
||||
"price": "23.00",
|
||||
"original_price": null,
|
||||
"active": true,
|
||||
"checkin_attention": false,
|
||||
"require_approval": false,
|
||||
"require_membership": false,
|
||||
"require_membership_types": [],
|
||||
"sales_channels": ["web"],
|
||||
@@ -426,6 +481,7 @@ Endpoints
|
||||
"tax_rate": "0.00",
|
||||
"tax_rule": 1,
|
||||
"admission": false,
|
||||
"personalized": false,
|
||||
"issue_giftcard": false,
|
||||
"meta_data": {},
|
||||
"position": 0,
|
||||
@@ -450,6 +506,14 @@ Endpoints
|
||||
"grant_membership_duration_like_event": true,
|
||||
"grant_membership_duration_days": 0,
|
||||
"grant_membership_duration_months": 0,
|
||||
"validity_fixed_from": null,
|
||||
"validity_fixed_until": null,
|
||||
"validity_dynamic_duration_minutes": null,
|
||||
"validity_dynamic_duration_hours": null,
|
||||
"validity_dynamic_duration_days": null,
|
||||
"validity_dynamic_duration_months": null,
|
||||
"validity_dynamic_start_choice": false,
|
||||
"validity_dynamic_start_choice_day_limit": null,
|
||||
"variations": [
|
||||
{
|
||||
"value": {"en": "Student"},
|
||||
@@ -457,6 +521,8 @@ Endpoints
|
||||
"price": "10.00",
|
||||
"original_price": null,
|
||||
"active": true,
|
||||
"checkin_attention": false,
|
||||
"require_approval": false,
|
||||
"require_membership": false,
|
||||
"require_membership_types": [],
|
||||
"sales_channels": ["web"],
|
||||
@@ -473,6 +539,8 @@ Endpoints
|
||||
"price": "23.00",
|
||||
"original_price": null,
|
||||
"active": true,
|
||||
"checkin_attention": false,
|
||||
"require_approval": false,
|
||||
"require_membership": false,
|
||||
"require_membership_types": [],
|
||||
"sales_channels": ["web"],
|
||||
@@ -510,6 +578,7 @@ Endpoints
|
||||
"tax_rate": "0.00",
|
||||
"tax_rule": 1,
|
||||
"admission": false,
|
||||
"personalized": false,
|
||||
"issue_giftcard": false,
|
||||
"meta_data": {},
|
||||
"position": 0,
|
||||
@@ -535,6 +604,14 @@ Endpoints
|
||||
"grant_membership_duration_like_event": true,
|
||||
"grant_membership_duration_days": 0,
|
||||
"grant_membership_duration_months": 0,
|
||||
"validity_fixed_from": null,
|
||||
"validity_fixed_until": null,
|
||||
"validity_dynamic_duration_minutes": null,
|
||||
"validity_dynamic_duration_hours": null,
|
||||
"validity_dynamic_duration_days": null,
|
||||
"validity_dynamic_duration_months": null,
|
||||
"validity_dynamic_start_choice": false,
|
||||
"validity_dynamic_start_choice_day_limit": null,
|
||||
"variations": [
|
||||
{
|
||||
"value": {"en": "Student"},
|
||||
@@ -542,6 +619,8 @@ Endpoints
|
||||
"price": "10.00",
|
||||
"original_price": null,
|
||||
"active": true,
|
||||
"checkin_attention": false,
|
||||
"require_approval": false,
|
||||
"require_membership": false,
|
||||
"require_membership_types": [],
|
||||
"sales_channels": ["web"],
|
||||
@@ -558,6 +637,8 @@ Endpoints
|
||||
"price": "23.00",
|
||||
"original_price": null,
|
||||
"active": true,
|
||||
"checkin_attention": false,
|
||||
"require_approval": false,
|
||||
"require_membership": false,
|
||||
"require_membership_types": [],
|
||||
"sales_channels": ["web"],
|
||||
@@ -626,6 +707,7 @@ Endpoints
|
||||
"tax_rate": "0.00",
|
||||
"tax_rule": 1,
|
||||
"admission": false,
|
||||
"personalized": false,
|
||||
"issue_giftcard": false,
|
||||
"meta_data": {},
|
||||
"position": 0,
|
||||
@@ -651,6 +733,14 @@ Endpoints
|
||||
"grant_membership_duration_like_event": true,
|
||||
"grant_membership_duration_days": 0,
|
||||
"grant_membership_duration_months": 0,
|
||||
"validity_fixed_from": null,
|
||||
"validity_fixed_until": null,
|
||||
"validity_dynamic_duration_minutes": null,
|
||||
"validity_dynamic_duration_hours": null,
|
||||
"validity_dynamic_duration_days": null,
|
||||
"validity_dynamic_duration_months": null,
|
||||
"validity_dynamic_start_choice": false,
|
||||
"validity_dynamic_start_choice_day_limit": null,
|
||||
"variations": [
|
||||
{
|
||||
"value": {"en": "Student"},
|
||||
@@ -658,6 +748,8 @@ Endpoints
|
||||
"price": "10.00",
|
||||
"original_price": null,
|
||||
"active": true,
|
||||
"checkin_attention": false,
|
||||
"require_approval": false,
|
||||
"require_membership": false,
|
||||
"require_membership_types": [],
|
||||
"sales_channels": ["web"],
|
||||
@@ -674,6 +766,8 @@ Endpoints
|
||||
"price": "23.00",
|
||||
"original_price": null,
|
||||
"active": true,
|
||||
"checkin_attention": false,
|
||||
"require_approval": false,
|
||||
"require_membership": false,
|
||||
"require_membership_types": [],
|
||||
"sales_channels": ["web"],
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.. spelling::
|
||||
.. spelling:word-list::
|
||||
|
||||
checkins
|
||||
pdf
|
||||
@@ -91,6 +91,10 @@ require_approval boolean If ``true`` and
|
||||
needs approval by an organizer before it can
|
||||
continue. If ``true`` and the order is canceled,
|
||||
this order has been denied by the event organizer.
|
||||
valid_if_pending boolean If ``true`` and the order is pending, this order
|
||||
is still treated like a paid order for most purposes,
|
||||
such as check-in. This may be used e.g. for trusted
|
||||
customers who only need to pay after the event.
|
||||
url string The full URL to the order confirmation page
|
||||
payments list of objects List of payment processes (see below)
|
||||
refunds list of objects List of refund processes (see below)
|
||||
@@ -122,6 +126,10 @@ last_modified datetime Last modificati
|
||||
|
||||
The ``include`` query parameter has been added.
|
||||
|
||||
.. versionchanged:: 4.16
|
||||
|
||||
The ``valid_if_pending`` attribute has been added.
|
||||
|
||||
|
||||
.. _order-position-resource:
|
||||
|
||||
@@ -159,6 +167,9 @@ secret string Secret code pri
|
||||
addon_to integer Internal ID of the position this position is an add-on for (or ``null``)
|
||||
subevent integer ID of the date inside an event series this position belongs to (or ``null``).
|
||||
discount integer ID of a discount that has been used during the creation of this position in some way (or ``null``).
|
||||
blocked list of strings A list of strings, or ``null``. Whenever not ``null``, the ticket may not be used (e.g. for check-in).
|
||||
valid_from datetime The ticket will not be valid before this time. Can be ``null``.
|
||||
valid_until datetime The ticket will not be valid after this time. Can be ``null``.
|
||||
pseudonymization_id string A random ID, e.g. for use in lead scanning apps
|
||||
checkins list of objects List of **successful** check-ins with this ticket
|
||||
├ id integer Internal ID of the check-in event
|
||||
@@ -186,6 +197,10 @@ pdf_data object Data object req
|
||||
``pdf_data=true`` query parameter to your request.
|
||||
===================================== ========================== =======================================================
|
||||
|
||||
.. versionchanged:: 4.16
|
||||
|
||||
The attributes ``blocked``, ``valid_from`` and ``valid_until`` have been added.
|
||||
|
||||
.. _order-payment-resource:
|
||||
|
||||
Order payment resource
|
||||
@@ -294,6 +309,7 @@ List of all orders
|
||||
"custom_followup_at": null,
|
||||
"checkin_attention": false,
|
||||
"require_approval": false,
|
||||
"valid_if_pending": false,
|
||||
"invoice_address": {
|
||||
"last_modified": "2017-12-01T10:00:00Z",
|
||||
"is_business": true,
|
||||
@@ -336,6 +352,9 @@ List of all orders
|
||||
"secret": "z3fsn8jyufm5kpk768q69gkbyr5f4h6w",
|
||||
"addon_to": null,
|
||||
"subevent": null,
|
||||
"valid_from": null,
|
||||
"valid_until": null,
|
||||
"blocked": null,
|
||||
"discount": null,
|
||||
"pseudonymization_id": "MQLJvANO3B",
|
||||
"seat": null,
|
||||
@@ -467,6 +486,7 @@ Fetching individual orders
|
||||
"custom_followup_at": null,
|
||||
"checkin_attention": false,
|
||||
"require_approval": false,
|
||||
"valid_if_pending": false,
|
||||
"invoice_address": {
|
||||
"last_modified": "2017-12-01T10:00:00Z",
|
||||
"company": "Sample company",
|
||||
@@ -509,6 +529,9 @@ Fetching individual orders
|
||||
"secret": "z3fsn8jyufm5kpk768q69gkbyr5f4h6w",
|
||||
"addon_to": null,
|
||||
"subevent": null,
|
||||
"valid_from": null,
|
||||
"valid_until": null,
|
||||
"blocked": null,
|
||||
"discount": null,
|
||||
"pseudonymization_id": "MQLJvANO3B",
|
||||
"seat": null,
|
||||
@@ -640,6 +663,8 @@ Updating order fields
|
||||
|
||||
* ``invoice_address`` (you always need to supply the full object, or ``null`` to delete the current address)
|
||||
|
||||
* ``valid_if_pending``
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
@@ -815,6 +840,7 @@ Creating orders
|
||||
|
||||
* does not support or validate memberships
|
||||
|
||||
|
||||
You can supply the following fields of the resource:
|
||||
|
||||
* ``code`` (optional) – Only ``A-Z`` and ``0-9``, but without ``O`` and ``1``.
|
||||
@@ -845,6 +871,7 @@ Creating orders
|
||||
* ``custom_followup_at`` (optional)
|
||||
* ``checkin_attention`` (optional)
|
||||
* ``require_approval`` (optional)
|
||||
* ``valid_if_pending`` (optional)
|
||||
* ``invoice_address`` (optional)
|
||||
|
||||
* ``company``
|
||||
@@ -880,6 +907,9 @@ Creating orders
|
||||
* ``secret`` (optional)
|
||||
* ``addon_to`` (optional, see below)
|
||||
* ``subevent`` (optional)
|
||||
* ``valid_from`` (optional, if both ``valid_from`` and ``valid_until`` are **missing** (not ``null``) the availability will be computed from the given product)
|
||||
* ``valid_until`` (optional, if both ``valid_from`` and ``valid_until`` are **missing** (not ``null``) the availability will be computed from the given product)
|
||||
* ``requested_valid_from`` (optional, can be set **instead** of ``valid_from`` and ``valid_until`` to signal a user choice for the start time that may or may not be respected)
|
||||
* ``answers``
|
||||
|
||||
* ``question``
|
||||
@@ -950,7 +980,7 @@ Creating orders
|
||||
"street": "Sesam Street 12",
|
||||
"zipcode": "12345",
|
||||
"city": "Sample City",
|
||||
"country": "UK",
|
||||
"country": "GB",
|
||||
"state": "",
|
||||
"internal_reference": "",
|
||||
"vat_id": ""
|
||||
@@ -1448,6 +1478,9 @@ List of all order positions
|
||||
"seat": null,
|
||||
"addon_to": null,
|
||||
"subevent": null,
|
||||
"valid_from": null,
|
||||
"valid_until": null,
|
||||
"blocked": null,
|
||||
"checkins": [
|
||||
{
|
||||
"list": 44,
|
||||
@@ -1554,6 +1587,9 @@ Fetching individual positions
|
||||
"secret": "z3fsn8jyufm5kpk768q69gkbyr5f4h6w",
|
||||
"addon_to": null,
|
||||
"subevent": null,
|
||||
"valid_from": null,
|
||||
"valid_until": null,
|
||||
"blocked": null,
|
||||
"discount": null,
|
||||
"pseudonymization_id": "MQLJvANO3B",
|
||||
"seat": null,
|
||||
@@ -1659,6 +1695,10 @@ Manipulating individual positions
|
||||
The ``PATCH`` method now supports changing items, variations, subevents, seats, prices, and tax rules.
|
||||
The ``POST`` endpoint to add individual positions has been added.
|
||||
|
||||
.. versionadded:: 4.16
|
||||
|
||||
The endpoints to manage blocks have been added.
|
||||
|
||||
.. http:patch:: /api/v1/organizers/(organizer)/events/(event)/orderpositions/(id)/
|
||||
|
||||
Updates specific fields on an order position. Currently, only the following fields are supported:
|
||||
@@ -1697,6 +1737,10 @@ Manipulating individual positions
|
||||
|
||||
* ``tax_rule``
|
||||
|
||||
* ``valid_from``
|
||||
|
||||
* ``valid_until``
|
||||
|
||||
Changing parameters such as ``item`` or ``price`` will **not** automatically trigger creation of a new invoice,
|
||||
you need to take care of that yourself.
|
||||
|
||||
@@ -1771,6 +1815,10 @@ Manipulating individual positions
|
||||
and ``option_identifiers`` will be ignored. As a special case, you can submit the magic value
|
||||
``"file:keep"`` as the answer to a file question to keep the current value without re-uploading it.
|
||||
|
||||
* ``valid_from``
|
||||
|
||||
* ``valid_until``
|
||||
|
||||
This will **not** automatically trigger creation of a new invoice, you need to take care of that yourself.
|
||||
|
||||
**Example request**:
|
||||
@@ -1834,6 +1882,82 @@ Manipulating individual positions
|
||||
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
|
||||
:statuscode 404: The requested order position does not exist.
|
||||
|
||||
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/orderpositions/(id)/add_block/
|
||||
|
||||
Blocks an order position from being used. The block name either needs to be ``"admin"`` or start with ``"api:"``. It
|
||||
may only contain letters, numbers, dots and underscores. ``"admin"`` represents the regular block that can be set
|
||||
in the backend user interface.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /api/v1/organizers/bigevents/events/sampleconf/orderpositions/23/add_block/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"name": "api:block1"
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
(Full order position resource, see above.)
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer of the event
|
||||
:param event: The ``slug`` field of the event
|
||||
:param code: The ``id`` field of the order position to update
|
||||
|
||||
:statuscode 200: no error
|
||||
:statuscode 400: The order position could not be updated due to invalid submitted data.
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to update this order position.
|
||||
|
||||
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/orderpositions/(id)/remove_block/
|
||||
|
||||
Unblocks an order position from being used. The block name either needs to be ``"admin"`` or start with ``"api:"``. It
|
||||
may only contain letters, numbers, dots and underscores. ``"admin"`` represents the regular block that can be set
|
||||
in the backend user interface. Blocks set by plugins cannot be lifted through this API.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /api/v1/organizers/bigevents/events/sampleconf/orderpositions/23/remove_block/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"name": "api:block1"
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
(Full order position resource, see above.)
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer of the event
|
||||
:param event: The ``slug`` field of the event
|
||||
:param code: The ``id`` field of the order position to update
|
||||
|
||||
:statuscode 200: no error
|
||||
:statuscode 400: The order position could not be updated due to invalid submitted data.
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to update this order position.
|
||||
|
||||
Changing order contents
|
||||
-----------------------
|
||||
|
||||
@@ -1852,7 +1976,7 @@ otherwise, such as splitting an order or changing fees.
|
||||
|
||||
* ``patch_positions``: A list of objects with the two keys ``position`` specifying an order position ID and
|
||||
``body`` specifying the desired changed values of the position (``item``, ``variation``, ``subevent``, ``seat``,
|
||||
``price``, ``tax_rule``).
|
||||
``price``, ``tax_rule``, ``valid_from``, ``valid_until``).
|
||||
|
||||
* ``cancel_positions``: A list of objects with the single key ``position`` specifying an order position ID.
|
||||
|
||||
@@ -2541,3 +2665,57 @@ With some non-default ticket secret generation methods, a list of revoked ticket
|
||||
:statuscode 200: no error
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
|
||||
|
||||
Blocked ticket secrets
|
||||
----------------------
|
||||
|
||||
With some non-default ticket secret generation methods, a list of blocked ticket secrets is required for proper validation.
|
||||
This endpoint returns all secrets that are currently blocked **or have been blocked before and are now unblocked**, so
|
||||
be sure to check the ``blocked`` attribute for its actual value. The list is currently always ordered with the most
|
||||
recently updated ones first.
|
||||
|
||||
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/blockedsecrets/
|
||||
|
||||
Returns a list of all blocked or historically blocked secrets within a given event.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /api/v1/organizers/bigevents/events/sampleconf/blockedsecrets/ 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
|
||||
X-Page-Generated: 2017-12-01T10:00:00Z
|
||||
|
||||
{
|
||||
"count": 1,
|
||||
"next": null,
|
||||
"previous": null,
|
||||
"results": [
|
||||
{
|
||||
"id": 1234,
|
||||
"secret": "k24fiuwvu8kxz3y1",
|
||||
"blocked": true,
|
||||
"updated": "2017-12-01T10:00:00Z",
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
:query integer page: The page number in case of a multi-page result set, default is 1
|
||||
:query datetime updated_since: Only return records that have been updated since the given date.
|
||||
:query boolean blocked: Only return blocked / non-blocked records.
|
||||
:param organizer: The ``slug`` field of the organizer to fetch
|
||||
:param event: The ``slug`` field of the event to fetch
|
||||
:resheader X-Page-Generated: The server time at the beginning of the operation. If you're using this API to fetch
|
||||
differences, this is the value you want to use as ``updated_since`` in your next call.
|
||||
:statuscode 200: no error
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
|
||||
|
||||
@@ -17,12 +17,18 @@ Field Type Description
|
||||
name string The organizer's full name, i.e. the name of an
|
||||
organization or company.
|
||||
slug string A short form of the name, used e.g. in URLs.
|
||||
public_url string The public, customer-facing URL of the organizer, where
|
||||
the list of all events can be found (read-only).
|
||||
===================================== ========================== =======================================================
|
||||
|
||||
|
||||
Endpoints
|
||||
---------
|
||||
|
||||
.. versionchanged:: 4.17
|
||||
|
||||
The ``public_url`` field has been added.
|
||||
|
||||
.. http:get:: /api/v1/organizers/
|
||||
|
||||
Returns a list of all organizers the authenticated user/token has access to.
|
||||
@@ -51,6 +57,7 @@ Endpoints
|
||||
{
|
||||
"name": "Big Events LLC",
|
||||
"slug": "Big Events",
|
||||
"public_url": "https://pretix.eu/bigevents/"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -84,6 +91,7 @@ Endpoints
|
||||
{
|
||||
"name": "Big Events LLC",
|
||||
"slug": "Big Events",
|
||||
"public_url": "https://pretix.eu/bigevents/"
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to fetch
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.. spelling::
|
||||
.. spelling:word-list::
|
||||
|
||||
checkin
|
||||
datetime
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.. spelling:: checkin
|
||||
.. spelling:word-list:: checkin
|
||||
|
||||
Data shredders
|
||||
==============
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.. spelling::
|
||||
.. spelling:word-list::
|
||||
|
||||
geo
|
||||
lat
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.. spelling:: fullname checkin
|
||||
.. spelling:word-list:: fullname checkin
|
||||
|
||||
.. _`rest-teams`:
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ limit_events list of strings If ``all_events
|
||||
action_types list of strings A list of action type filters that limit the
|
||||
notifications sent to this webhook. See below for
|
||||
valid values
|
||||
comment string Internal comment on this webhook, default ``null``
|
||||
===================================== ========================== =======================================================
|
||||
|
||||
The following values for ``action_types`` are valid with pretix core:
|
||||
@@ -56,6 +57,7 @@ The following values for ``action_types`` are valid with pretix core:
|
||||
* ``pretix.subevent.added``
|
||||
* ``pretix.subevent.changed``
|
||||
* ``pretix.subevent.deleted``
|
||||
* ``pretix.event.item.*``
|
||||
* ``pretix.event.live.activated``
|
||||
* ``pretix.event.live.deactivated``
|
||||
* ``pretix.event.testmode.activated``
|
||||
@@ -98,7 +100,8 @@ Endpoints
|
||||
"target_url": "https://httpstat.us/200",
|
||||
"all_events": false,
|
||||
"limit_events": ["democon"],
|
||||
"action_types": ["pretix.event.order.modified", "pretix.event.order.changed.*"]
|
||||
"action_types": ["pretix.event.order.modified", "pretix.event.order.changed.*"],
|
||||
"comment": null
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -135,7 +138,8 @@ Endpoints
|
||||
"target_url": "https://httpstat.us/200",
|
||||
"all_events": false,
|
||||
"limit_events": ["democon"],
|
||||
"action_types": ["pretix.event.order.modified", "pretix.event.order.changed.*"]
|
||||
"action_types": ["pretix.event.order.modified", "pretix.event.order.changed.*"],
|
||||
"comment": null
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to fetch
|
||||
@@ -162,7 +166,8 @@ Endpoints
|
||||
"target_url": "https://httpstat.us/200",
|
||||
"all_events": false,
|
||||
"limit_events": ["democon"],
|
||||
"action_types": ["pretix.event.order.modified", "pretix.event.order.changed.*"]
|
||||
"action_types": ["pretix.event.order.modified", "pretix.event.order.changed.*"],
|
||||
"comment": "Called for changes"
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
@@ -179,7 +184,8 @@ Endpoints
|
||||
"target_url": "https://httpstat.us/200",
|
||||
"all_events": false,
|
||||
"limit_events": ["democon"],
|
||||
"action_types": ["pretix.event.order.modified", "pretix.event.order.changed.*"]
|
||||
"action_types": ["pretix.event.order.modified", "pretix.event.order.changed.*"],
|
||||
"comment": "Called for changes"
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to create a webhook for
|
||||
@@ -224,7 +230,8 @@ Endpoints
|
||||
"target_url": "https://httpstat.us/200",
|
||||
"all_events": false,
|
||||
"limit_events": ["democon"],
|
||||
"action_types": ["pretix.event.order.modified", "pretix.event.order.changed.*"]
|
||||
"action_types": ["pretix.event.order.modified", "pretix.event.order.changed.*"],
|
||||
"comment": null
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to modify
|
||||
|
||||
109
doc/conf.py
@@ -13,10 +13,6 @@
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
from docutils.parsers.rst.directives.admonitions import BaseAdmonition
|
||||
from sphinx.util import compat
|
||||
compat.make_admonition = BaseAdmonition # See https://github.com/spinus/sphinxcontrib-images/issues/41
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
@@ -28,12 +24,13 @@ from datetime import date
|
||||
sys.path.insert(0, os.path.abspath('../src'))
|
||||
|
||||
import django
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "pretix.testutils.settings")
|
||||
django.setup()
|
||||
|
||||
|
||||
try:
|
||||
import enchant
|
||||
import enchant # noqa
|
||||
|
||||
HAS_PYENCHANT = True
|
||||
except:
|
||||
HAS_PYENCHANT = False
|
||||
@@ -41,7 +38,7 @@ except:
|
||||
# -- General configuration ------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
# needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
@@ -52,6 +49,7 @@ extensions = [
|
||||
'sphinx.ext.coverage',
|
||||
'sphinxcontrib.httpdomain',
|
||||
'sphinxcontrib.images',
|
||||
'sphinxcontrib.jquery',
|
||||
'sphinxemoji.sphinxemoji',
|
||||
]
|
||||
if HAS_PYENCHANT:
|
||||
@@ -64,7 +62,7 @@ templates_path = ['_templates']
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
# source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
@@ -79,19 +77,20 @@ copyright = '2014-{}, Raphael Michel'.format(date.today().year)
|
||||
#
|
||||
# The short X.Y version.
|
||||
from pretix import __version__
|
||||
|
||||
version = '.'.join(__version__.split('.')[:2])
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = __version__
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#language = None
|
||||
# language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
# today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
@@ -99,34 +98,34 @@ exclude_patterns = ['_build']
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all
|
||||
# documents.
|
||||
#default_role = None
|
||||
# default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
# add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#add_module_names = True
|
||||
# add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
# show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
# modindex_common_prefix = []
|
||||
|
||||
# If true, keep warnings as "system message" paragraphs in the built documents.
|
||||
#keep_warnings = False
|
||||
# keep_warnings = False
|
||||
|
||||
|
||||
# -- Options for HTML output ----------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
#html_theme = ""
|
||||
# html_theme = ""
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
@@ -136,14 +135,14 @@ html_theme_options = {
|
||||
}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
#html_theme_path = []
|
||||
# html_theme_path = []
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#html_title = None
|
||||
# html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
# html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
@@ -152,7 +151,7 @@ html_logo = 'images/logo-white.svg'
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
# html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
@@ -166,18 +165,18 @@ html_static_path = [
|
||||
# Add any extra paths that contain custom files (such as robots.txt or
|
||||
# .htaccess) here, relative to this directory. These files are copied
|
||||
# directly to the root of the documentation.
|
||||
#html_extra_path = []
|
||||
# html_extra_path = []
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
# html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
# html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
# html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
@@ -192,24 +191,24 @@ html_domain_indices = False
|
||||
html_use_index = False
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
# html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
html_show_sourcelink = True
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
#html_show_sphinx = True
|
||||
# html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#html_show_copyright = True
|
||||
# html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
# html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = None
|
||||
# html_file_suffix = None
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'pretixdoc'
|
||||
@@ -217,47 +216,46 @@ htmlhelp_basename = 'pretixdoc'
|
||||
html_theme = 'pretix_theme'
|
||||
html_theme_path = [os.path.abspath('_themes')]
|
||||
|
||||
|
||||
# -- Options for LaTeX output ---------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
'papersize': 'a4paper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
# 'preamble': '',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
('index', 'pretix.tex', 'pretix Documentation',
|
||||
'Raphael Michel', 'manual'),
|
||||
('index', 'pretix.tex', 'pretix Documentation',
|
||||
'Raphael Michel', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#latex_logo = None
|
||||
# latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
# latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
#latex_show_pagerefs = False
|
||||
# latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#latex_show_urls = False
|
||||
# latex_show_urls = False
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
# latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_domain_indices = True
|
||||
# latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output ---------------------------------------
|
||||
@@ -270,7 +268,7 @@ man_pages = [
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#man_show_urls = False
|
||||
# man_show_urls = False
|
||||
|
||||
|
||||
# -- Options for Texinfo output -------------------------------------------
|
||||
@@ -279,22 +277,22 @@ man_pages = [
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
('index', 'pretix', 'pretix Documentation',
|
||||
'Raphael Michel', 'pretix', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
('index', 'pretix', 'pretix Documentation',
|
||||
'Raphael Michel', 'pretix', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#texinfo_appendices = []
|
||||
# texinfo_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#texinfo_domain_indices = True
|
||||
# texinfo_domain_indices = True
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
#texinfo_show_urls = 'footnote'
|
||||
# texinfo_show_urls = 'footnote'
|
||||
|
||||
# If true, do not generate a @detailmenu in the "Top" node's menu.
|
||||
#texinfo_no_detailmenu = False
|
||||
# texinfo_no_detailmenu = False
|
||||
|
||||
|
||||
images_config = {
|
||||
@@ -314,12 +312,13 @@ if HAS_PYENCHANT:
|
||||
# String specifying a file containing a list of words known to be spelled
|
||||
# correctly but that do not appear in the language dictionary selected by
|
||||
# spelling_lang. The file should contain one word per line.
|
||||
spelling_word_list_filename='spelling_wordlist.txt'
|
||||
spelling_word_list_filename = 'spelling_wordlist.txt'
|
||||
|
||||
# Boolean controlling whether suggestions for misspelled words are printed.
|
||||
# Defaults to False.
|
||||
spelling_show_suggestions=True
|
||||
spelling_show_suggestions = True
|
||||
|
||||
# List of filter classes to be added to the tokenizer that produces words to be checked.
|
||||
from checkin_filter import CheckinFilter
|
||||
spelling_filters=[CheckinFilter]
|
||||
|
||||
spelling_filters = [CheckinFilter]
|
||||
|
||||
@@ -76,6 +76,10 @@ The exporter class
|
||||
|
||||
This is an abstract attribute, you **must** override this!
|
||||
|
||||
.. autoattribute:: description
|
||||
|
||||
.. autoattribute:: category
|
||||
|
||||
.. autoattribute:: export_form_fields
|
||||
|
||||
.. automethod:: render
|
||||
|
||||
@@ -61,7 +61,7 @@ Backend
|
||||
item_formsets, order_search_filter_q, order_search_forms
|
||||
|
||||
.. automodule:: pretix.base.signals
|
||||
:members: logentry_display, logentry_object_link, requiredaction_display, timeline_events
|
||||
:members: logentry_display, logentry_object_link, requiredaction_display, timeline_events, orderposition_blocked_display
|
||||
|
||||
Vouchers
|
||||
""""""""
|
||||
|
||||
@@ -102,6 +102,8 @@ The provider class
|
||||
|
||||
.. automethod:: render_invoice_text
|
||||
|
||||
.. automethod:: render_invoice_stamp
|
||||
|
||||
.. automethod:: order_change_allowed
|
||||
|
||||
.. automethod:: payment_prepare
|
||||
@@ -120,6 +122,8 @@ The provider class
|
||||
|
||||
.. automethod:: refund_control_render
|
||||
|
||||
.. automethod:: refund_control_render_short
|
||||
|
||||
.. automethod:: new_refund_control_form_render
|
||||
|
||||
.. automethod:: new_refund_control_form_process
|
||||
@@ -130,6 +134,8 @@ The provider class
|
||||
|
||||
.. automethod:: matching_id
|
||||
|
||||
.. automethod:: refund_matching_id
|
||||
|
||||
.. automethod:: shred_payment_info
|
||||
|
||||
.. automethod:: cancel_payment
|
||||
|
||||
@@ -55,7 +55,6 @@ visible boolean (optional) ``True`` by default, can hide a plugin s
|
||||
restricted boolean (optional) ``False`` by default, restricts a plugin such that it can only be enabled
|
||||
for an event by system administrators / superusers.
|
||||
experimental boolean (optional) ``False`` by default, marks a plugin as an experimental feature in the plugins list.
|
||||
picture string (optional) Path to a picture resolvable through the static file system.
|
||||
compatibility string Specifier for compatible pretix versions.
|
||||
================== ==================== ===========================================================
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.. spelling:: Rebase rebasing
|
||||
.. spelling:word-list:: Rebase rebasing
|
||||
|
||||
Coding style and quality
|
||||
========================
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
.. highlight:: python
|
||||
:linenothreshold: 5
|
||||
|
||||
.. spelling:: answ contrib
|
||||
.. spelling:word-list:: answ contrib
|
||||
|
||||
Data model
|
||||
==========
|
||||
|
||||
|
Before Width: | Height: | Size: 274 KiB After Width: | Height: | Size: 278 KiB |
@@ -23,30 +23,50 @@ partition "data-based check" {
|
||||
"Is the order in status PAID or PENDING\nand is the position not canceled?" --> if "" then
|
||||
-right->[no] "Return error CANCELED"
|
||||
else
|
||||
-down->[yes] "Is the product part of the check-in list?"
|
||||
-down->[yes] "Is one or more block set on the ticket?"
|
||||
--> if "" then
|
||||
-right->[no] "Return error PRODUCT"
|
||||
-right->[no] "Return error BLOCKED"
|
||||
else
|
||||
-down->[yes] "Is the subevent part of the check-in list?"
|
||||
-down->[yes] "If this is not an exit, is the valid_from/valid_until\nconstraint on the ticket fulfilled?"
|
||||
--> if "" then
|
||||
-right->[no] "Return error INVALID"
|
||||
note bottom: TODO\ninconsistent\nwith online\ncheck
|
||||
-right->[no] "Return error INVALID_TIME"
|
||||
else
|
||||
-down->[yes] "Is the order in status PAID?"
|
||||
-down->[yes] "Is the product part of the check-in list?"
|
||||
--> if "" then
|
||||
-right->[no] "Does the check-in list include pending orders?"
|
||||
-right->[no] "Return error PRODUCT"
|
||||
else
|
||||
-down->[yes] "Is the subevent part of the check-in list?"
|
||||
--> if "" then
|
||||
-right->[no] "Return error UNPAID "
|
||||
-right->[no] "Return error INVALID"
|
||||
note bottom: TODO\ninconsistent\nwith online\ncheck
|
||||
else
|
||||
-down->[yes] "Is ignore_unpaid set?\n(Has the operator confirmed\nthe checkin?)"
|
||||
-down->[yes] "Is the order in status PAID?"
|
||||
--> if "" then
|
||||
-right->[no] "Return error UNPAID "
|
||||
-right->[no] "Is Order.require_approval set?"
|
||||
--> if "" then
|
||||
-->[yes] "Return error UNPAID "
|
||||
else
|
||||
-right->[no] "Is Order.valid_if_pending set?"
|
||||
--> if "" then
|
||||
-->[yes] "Is this an entry or exit?"
|
||||
else
|
||||
-->[no] "Does the check-in list include pending orders?"
|
||||
--> if "" then
|
||||
-->[no] "Return error UNPAID "
|
||||
else
|
||||
-->[yes] "Is ignore_unpaid set on the request?\n(Has the operator confirmed\nthe checkin?)"
|
||||
--> if "" then
|
||||
-->[no] "Return error UNPAID "
|
||||
else
|
||||
-->[yes] "Is this an entry or exit?"
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
else
|
||||
-down->[yes] "Is this an entry or exit?"
|
||||
endif
|
||||
endif
|
||||
else
|
||||
-down->[yes] "Is this an entry or exit?"
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
@@ -98,16 +118,26 @@ partition "dataless check" {
|
||||
--> if "" then
|
||||
-right->[yes] "Return error REVOKED"
|
||||
else
|
||||
-down->[no] "Is the product part of the check-in list? "
|
||||
-down->[yes] "Is the ticket secret on the block list?"
|
||||
--> if "" then
|
||||
-right->[no] "Return error PRODUCT "
|
||||
-right->[yes] "Return error BLOCKED "
|
||||
else
|
||||
-down->[yes] "Is the subevent part of the check-in list? "
|
||||
-down->[yes] "If this is not an exit, is the valid_from/valid_until\nconstraint on the ticket fulfilled? "
|
||||
--> if "" then
|
||||
-right->[no] "Return error INVALID "
|
||||
note bottom: TODO\ninconsistent\nwith online\ncheck
|
||||
-right->[no] "Return error INVALID_TIME "
|
||||
else
|
||||
--> "Is this an entry or exit? "
|
||||
-down->[no] "Is the product part of the check-in list? "
|
||||
--> if "" then
|
||||
-right->[no] "Return error PRODUCT "
|
||||
else
|
||||
-down->[yes] "Is the subevent part of the check-in list? "
|
||||
--> if "" then
|
||||
-right->[no] "Return error INVALID "
|
||||
note bottom: TODO\ninconsistent\nwith online\ncheck
|
||||
else
|
||||
--> "Is this an entry or exit? "
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
|
Before Width: | Height: | Size: 175 KiB After Width: | Height: | Size: 177 KiB |
@@ -40,29 +40,49 @@ endif
|
||||
"Is the order in status PAID or PENDING\nand is the position not canceled?" --> if "" then
|
||||
-right->[no] "Return error CANCELED"
|
||||
else
|
||||
-down->[yes] "Is the product part of the check-in list?"
|
||||
-down->[yes] "Is one or more block set on the ticket?"
|
||||
--> if "" then
|
||||
-right->[no] "Return error PRODUCT"
|
||||
-right->[no] "Return error BLOCKED"
|
||||
else
|
||||
-down->[yes] "Is the subevent part of the check-in list?"
|
||||
-down->[yes] "If this is not an exit, is the valid_from/valid_until\nconstraint on the ticket fulfilled?"
|
||||
--> if "" then
|
||||
-right->[no] "Return error PRODUCT "
|
||||
-right->[no] "Return error INVALID_TIME"
|
||||
else
|
||||
-down->[yes] "Is the order in status PAID\nor is this a forced upload?"
|
||||
-down->[yes] "Is the product part of the check-in list?"
|
||||
--> if "" then
|
||||
-right->[no] "Does the check-in list include pending orders?"
|
||||
-right->[no] "Return error PRODUCT"
|
||||
else
|
||||
-down->[yes] "Is the subevent part of the check-in list?"
|
||||
--> if "" then
|
||||
-right->[no] "Return error UNPAID "
|
||||
-right->[no] "Return error PRODUCT "
|
||||
else
|
||||
-down->[yes] "Is ignore_unpaid set?\n(Has the operator confirmed\nthe checkin?)"
|
||||
-down->[yes] "Is the order in status PAID\nor is this a forced upload?"
|
||||
--> if "" then
|
||||
-right->[no] "Return error UNPAID "
|
||||
-right->[no] "Is Order.require_approval set?"
|
||||
--> if "" then
|
||||
-->[no] "Is Order.valid_if_pending set?"
|
||||
--> if "" then
|
||||
-down->[yes] "Is this an entry or exit?\nIs the upload forced?"
|
||||
else
|
||||
-right->[no] "Does the check-in list include pending orders?"
|
||||
--> if "" then
|
||||
-right->[no] "Return error UNPAID "
|
||||
else
|
||||
-down->[yes] "Is ignore_unpaid set on the request?\n(Has the operator confirmed\nthe checkin?)"
|
||||
--> if "" then
|
||||
-right->[no] "Return error UNPAID "
|
||||
else
|
||||
-down->[yes] "Is this an entry or exit?\nIs the upload forced?"
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
else
|
||||
-->[yes] "Return error UNPAID "
|
||||
endif
|
||||
else
|
||||
-down->[yes] "Is this an entry or exit?\nIs the upload forced?"
|
||||
endif
|
||||
endif
|
||||
else
|
||||
-down->[yes] "Is this an entry or exit?\nIs the upload forced?"
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
@@ -1,69 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="254.15625"
|
||||
height="109.59375"
|
||||
viewBox="0 0 254.15625 109.59375"
|
||||
version="1.1"
|
||||
id="svg5"
|
||||
sodipodi:docname="logo-white.svg"
|
||||
inkscape:version="0.92.1 r"><metadata
|
||||
id="metadata9">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1364"
|
||||
inkscape:window-height="676"
|
||||
id="namedview7"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:zoom="1"
|
||||
inkscape:cx="56.462442"
|
||||
inkscape:cy="54.796875"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="72"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg5" />
|
||||
|
||||
id="svg2"
|
||||
version="1.1">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<g
|
||||
id="layer1"
|
||||
transform="translate(-277.78125,-568.75)">
|
||||
<path
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;marker:none;enable-background:accumulate"
|
||||
d="m 20,20 v 34.09375 c 11.43679,0 20.71875,9.28196 20.71875,20.71875 C 40.71875,86.24928 31.43679,95.5 20,95.5 v 34.09375 h 146.6875 v -9.5 h 3 v 9.5 H 274.15625 V 95.5 c -0.0105,2e-5 -0.0208,0 -0.0312,0 -11.43678,0 -20.71875,-9.25072 -20.71875,-20.6875 0,-11.43679 9.28197,-20.71875 20.71875,-20.71875 0.0105,0 0.0208,-2e-5 0.0312,0 V 20 H 169.6875 v 9.09375 h -3 V 20 Z m 146.6875,16.09375 h 3 v 14 h -3 z m 41.44141,12.833984 c 2.79067,0 5.02343,1.92774 5.02343,4.3125 0,2.38476 -2.23276,4.363282 -5.02343,4.363282 -2.73994,0 -4.97266,-1.978522 -4.97266,-4.363282 0,-2.38476 2.23272,-4.3125 4.97266,-4.3125 z m -13.22852,4.210938 v 8.017578 h 3.95899 v 6.291016 h -3.95899 v 12.279296 c 0,2.02959 0.71015,2.791016 2.13086,2.791016 0.71035,0 1.06703,-0.10181 1.82813,-0.40625 v 5.935547 c -0.71036,0.40591 -2.38661,0.964844 -4.61915,0.964844 -6.13949,0 -8.98046,-3.753876 -8.98046,-8.472657 V 67.447266 h -2.8418 V 61.15625 h 2.8418 V 55.574219 Z M 166.6875,57.09375 h 3 v 14 h -3 z m -74.568359,3.554688 c 8.473509,0 14.207029,4.515688 14.207029,14.105468 0,8.62573 -5.02336,14.105469 -12.07617,14.105469 -1.72514,0 -3.147072,-0.20329 -3.857422,-0.40625 V 99.414062 H 80.751953 V 62.728516 c 2.58772,-1.21775 6.090268,-2.080081 11.367188,-2.080078 z m 49.863279,0 c 8.57499,0 12.63436,5.935363 12.12696,15.220703 l -15.93165,2.234375 c 0.60888,2.94289 2.18061,4.414062 5.68165,4.414062 3.24732,0 5.78445,-0.711556 7.30664,-1.472656 l 2.13086,5.886719 c -2.38476,1.16701 -5.58034,2.080078 -10.6543,2.080078 -8.93017,0 -13.64844,-6.037993 -13.64844,-14.257813 0,-8.21981 4.41329,-14.105468 12.98828,-14.105468 z m -17.92187,0.0059 c 0.8928,0.01358 1.82795,0.04496 2.80468,0.0957 l -1.67578,6.697266 c -1.77589,-0.86257 -3.50104,-0.913692 -4.76953,-0.457032 v 21.513672 h -9.64062 v -25.77539 c 2.79702,-1.376314 7.03166,-2.16926 13.28125,-2.074219 z m 79.24804,0.501953 h 9.64063 v 27.347656 h -9.64063 z m 13.23438,0 h 10.04687 l 3.29883,6.849609 h 0.10156 l 3.60157,-6.849609 h 8.98047 l -7.96485,12.632812 8.72656,14.714844 H 232.67969 L 229.17773,80.9434 h -0.10156 l -3.65234,7.560547 h -9.74219 l 8.57422,-14.105468 z m -74.9668,5.023438 c -2.84142,0 -4.41381,2.585948 -4.10937,7.355468 l 7.76367,-1.166015 c 0,-4.16064 -1.2188,-6.189454 -3.6543,-6.189453 z m -49.507811,0.09961 c -0.71035,0 -1.219131,0.101686 -1.675781,0.253906 v 16.439453 c 0.35517,0.15221 0.863828,0.253906 1.523438,0.253906 3.4503,0 4.871093,-2.840514 4.871093,-8.421874 0,-5.733571 -1.21772,-8.525391 -4.71875,-8.525391 z M 166.6875,78.09375 h 3 v 14 h -3 z m 0,21 h 3 v 14 h -3 z"
|
||||
transform="translate(257.78125,548.75)"
|
||||
id="rect3888"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="254.156" height="109.594" version="1.1"><g transform="scale(1.9856)"><path d="M36.29 23.21c-.35 0-.61.06-.83.13v8.29c.19.06.45.13.77.13 1.73 0 2.46-1.44 2.46-4.26s-.64-4.29-2.4-4.29z" fill="#f8f8f8"/><path d="M61.22 23.15c-1.44 0-2.21 1.31-2.08 3.71l3.9-.58c.03-2.11-.58-3.14-1.82-3.14z" fill="#f8f8f8"/><path d="M127.39 17.1c.35-.03.61-.29.61-.64V.64c0-.35-.29-.64-.64-.64H75.94v2.48c0 .62-.5 1.12-1.12 1.12-.62 0-1.12-.5-1.12-1.12V0H.64C.29 0 0 .29 0 .64v15.82c0 .35.26.61.61.64 5.47.32 9.82 4.86 9.82 10.43 0 5.57-4.35 10.08-9.82 10.37-.35.03-.61.29-.61.64v15.82c0 .35.29.64.64.64h73.22-.16v-2.48c0-.63.49-1.12 1.12-1.12.63 0 1.12.5 1.12 1.12V55h-.16 51.58c.35 0 .64-.29.64-.64V38.54c0-.35-.26-.61-.61-.64-5.47-.32-9.82-4.83-9.82-10.4s4.35-10.11 9.82-10.4zM37.41 34.57c-.86 0-1.6-.1-1.95-.19v5.54H30.6v-18.5c1.31-.61 3.07-1.06 5.73-1.06 4.26 0 7.17 2.27 7.17 7.1 0 4.35-2.53 7.1-6.08 7.1zm15.58-10.78c-.9-.45-1.76-.45-2.4-.22v10.85h-4.86V21.43c1.41-.7 3.55-1.09 6.69-1.06.45 0 .93.03 1.41.06L53 23.79Zm14.56 4.26-8.03 1.12c.32 1.47 1.09 2.21 2.85 2.21 1.63 0 2.91-.35 3.68-.74l1.09 2.98c-1.22.58-2.82 1.06-5.38 1.06-4.51 0-6.88-3.04-6.88-7.17s2.21-7.1 6.53-7.1c4.35-.03 6.4 2.98 6.14 7.65zm8.38 18.56c0 .62-.5 1.12-1.12 1.12-.62 0-1.12-.5-1.12-1.12v-5.12c0-.62.5-1.12 1.12-1.12.62 0 1.12.52 1.12 1.12zm0-11.07c0 .6-.52 1.12-1.12 1.12-.6 0-1.12-.52-1.12-1.12v-5.12c0-.63.49-1.12 1.12-1.12.63 0 1.12.5 1.12 1.12zm0-11.01c0 .62-.5 1.12-1.12 1.12-.62 0-1.12-.5-1.12-1.12v-5.12c0-.62.5-1.12 1.12-1.12.62 0 1.12.52 1.12 1.12zm0-11.07c0 .6-.52 1.12-1.12 1.12-.6 0-1.12-.52-1.12-1.12V8.34c0-.63.49-1.12 1.12-1.12.63 0 1.12.5 1.12 1.12zM90.11 23.8h-2.02v6.18c0 1.02.35 1.41 1.09 1.41.35 0 .54-.06.93-.19v2.98c-.35.19-1.22.48-2.34.48-3.1 0-4.51-1.89-4.51-4.26v-6.59h-1.44v-3.17h1.44v-2.82l4.86-1.22v4.03h1.98v3.17zm7.07 10.62h-4.86V20.66h4.86zm-2.43-15.58c-1.38 0-2.5-.99-2.5-2.21s1.12-2.18 2.5-2.18 2.53.96 2.53 2.18c0 1.22-1.12 2.21-2.53 2.21zm12.35 15.58-1.76-3.81h-.06l-1.82 3.81h-4.9l4.32-7.1-3.87-6.66h5.06l1.66 3.46h.06l1.82-3.46h4.51l-4 6.37 4.38 7.42-5.41-.03z" fill="#f8f8f8"/></g></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 2.1 KiB |
@@ -1,4 +1,4 @@
|
||||
.. spelling::
|
||||
.. spelling:word-list::
|
||||
|
||||
AGPL
|
||||
AGPLv3
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.. spelling::
|
||||
.. spelling:word-list::
|
||||
Analytics
|
||||
|
||||
List of plugins
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.. highlight:: ini
|
||||
.. spelling::
|
||||
.. spelling:word-list::
|
||||
|
||||
IdP
|
||||
skIDentity
|
||||
|
||||
@@ -17,9 +17,13 @@ Field Type Description
|
||||
id integer Internal layout ID
|
||||
name string Internal layout description
|
||||
default boolean ``true`` if this is the default layout
|
||||
layout object Layout specification for libpretixprint
|
||||
layout list Dynamic layout specification. Each list element
|
||||
corresponds to one dynamic element of the layout.
|
||||
The current version of the schema in use can be found
|
||||
`here`_.
|
||||
Submitting invalid content can lead to application errors.
|
||||
background URL Background PDF file
|
||||
item_assignments list of objects Products this layout is assigned to
|
||||
item_assignments list of objects Products this layout is assigned to (currently read-only)
|
||||
├ sales_channel string Sales channel (defaults to ``web``).
|
||||
└ item integer Item ID
|
||||
===================================== ========================== =======================================================
|
||||
@@ -58,7 +62,7 @@ Endpoints
|
||||
"name": "Default layout",
|
||||
"default": true,
|
||||
"layout": {…},
|
||||
"background": {},
|
||||
"background": null,
|
||||
"item_assignments": []
|
||||
}
|
||||
]
|
||||
@@ -96,7 +100,7 @@ Endpoints
|
||||
"name": "Default layout",
|
||||
"default": true,
|
||||
"layout": {…},
|
||||
"background": {},
|
||||
"background": null,
|
||||
"item_assignments": []
|
||||
}
|
||||
|
||||
@@ -147,3 +151,122 @@ Endpoints
|
||||
:statuscode 200: no error
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer does not exist **or** you have no permission to view it.
|
||||
|
||||
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/ticketlayouts/
|
||||
|
||||
Creates a new ticket layout
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /api/v1/organizers/bigevents/events/sampleconf/ticketlayouts/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"name": "Default layout",
|
||||
"default": true,
|
||||
"layout": […],
|
||||
"background": null,
|
||||
"item_assignments": []
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 201 Created
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Default layout",
|
||||
"default": true,
|
||||
"layout": […],
|
||||
"background": null,
|
||||
"item_assignments": []
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer of the event to create a layout for
|
||||
:param event: The ``slug`` field of the event to create a layout for
|
||||
:statuscode 201: no error
|
||||
:statuscode 400: The layout could not be created due to invalid submitted data.
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to create this resource.
|
||||
|
||||
.. http:patch:: /api/v1/organizers/(organizer)/events/(event)/ticketlayouts/(id)/
|
||||
|
||||
Update a layout. You can also use ``PUT`` instead of ``PATCH``. With ``PUT``, you have to provide all fields of
|
||||
the resource, other fields will be reset to default. With ``PATCH``, you only need to provide the fields that you
|
||||
want to change.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
PATCH /api/v1/organizers/bigevents/events/sampleconf/ticketlayouts/1/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
Content-Type: application/json
|
||||
Content-Length: 94
|
||||
|
||||
{
|
||||
"name": "Default layout"
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Default layout",
|
||||
"default": true,
|
||||
"layout": […],
|
||||
"background": null,
|
||||
"item_assignments": []
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to modify
|
||||
:param event: The ``slug`` field of the event to modify
|
||||
:param id: The ``id`` field of the layout to modify
|
||||
:statuscode 200: no error
|
||||
:statuscode 400: The layout could not be modified due to invalid submitted data
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to change this resource.
|
||||
|
||||
.. http:delete:: /api/v1/organizers/(organizer)/events/(event)/ticketlayouts/(id)/
|
||||
|
||||
Delete a layout.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
DELETE /api/v1/organizers/bigevents/events/sampleconf/ticketlayouts/1/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 204 No Content
|
||||
Vary: Accept
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to modify
|
||||
:param event: The ``slug`` field of the event to modify
|
||||
:param id: The ``id`` field of the layout to delete
|
||||
:statuscode 204: no error
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to delete this resource.
|
||||
|
||||
|
||||
.. _here: https://github.com/pretix/pretix/blob/master/src/pretix/static/schema/pdf-layout.schema.json
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
sphinx==2.3.*
|
||||
jinja2==3.0.*
|
||||
sphinx==6.1.*
|
||||
jinja2==3.1.*
|
||||
sphinx-rtd-theme
|
||||
sphinxcontrib-httpdomain
|
||||
sphinxcontrib-images
|
||||
sphinxcontrib-spelling==4.*
|
||||
sphinxcontrib-jquery
|
||||
sphinxcontrib-spelling==7.*
|
||||
sphinxemoji
|
||||
pygments-markdown-lexer
|
||||
# See https://github.com/rfk/pyenchant/pull/130
|
||||
git+https://github.com/raphaelm/pyenchant.git@patch-1#egg=pyenchant
|
||||
pyenchant==3.2.*
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
-e ../src/
|
||||
sphinx==2.3.*
|
||||
jinja2==3.0.*
|
||||
sphinx==6.1.*
|
||||
jinja2==3.1.*
|
||||
sphinx-rtd-theme
|
||||
sphinxcontrib-httpdomain
|
||||
sphinxcontrib-images
|
||||
sphinxcontrib-spelling==4.*
|
||||
sphinxcontrib-jquery
|
||||
sphinxcontrib-spelling==7.*
|
||||
sphinxemoji
|
||||
pygments-markdown-lexer
|
||||
# See https://github.com/rfk/pyenchant/pull/130
|
||||
git+https://github.com/raphaelm/pyenchant.git@patch-1#egg=pyenchant
|
||||
pyenchant==3.2.*
|
||||
|
||||
@@ -97,6 +97,7 @@ overpayment
|
||||
param
|
||||
passphrase
|
||||
percental
|
||||
personalization
|
||||
pluggable
|
||||
positionid
|
||||
pre
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.. spelling::
|
||||
.. spelling:word-list::
|
||||
|
||||
Warengutschein
|
||||
Wertgutschein
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Invoice settings
|
||||
================
|
||||
|
||||
.. spelling:: Inv
|
||||
.. spelling:word-list:: Inv
|
||||
|
||||
The settings at "Settings" → "Invoice" allow you to specify if and how pretix should generate invoices for your orders.
|
||||
|
||||
|
||||
@@ -153,9 +153,9 @@ If you want to include all your public events, you can just reference your organ
|
||||
There is an optional ``style`` parameter that let's you choose between a monthly calendar view, a week view and a list
|
||||
view. If you do not set it, the choice will be taken from your organizer settings::
|
||||
|
||||
<pretix-widget event="https://pretix.eu/demo/series/" style="list"></pretix-widget>
|
||||
<pretix-widget event="https://pretix.eu/demo/series/" style="calendar"></pretix-widget>
|
||||
<pretix-widget event="https://pretix.eu/demo/series/" style="week"></pretix-widget>
|
||||
<pretix-widget event="https://pretix.eu/demo/series/" list-type="list"></pretix-widget>
|
||||
<pretix-widget event="https://pretix.eu/demo/series/" list-type="calendar"></pretix-widget>
|
||||
<pretix-widget event="https://pretix.eu/demo/series/" list-type="week"></pretix-widget>
|
||||
|
||||
If you have more than 100 events, the system might refuse to show a list view and always show a calendar for performance
|
||||
reasons instead.
|
||||
@@ -164,7 +164,7 @@ You can see an example here:
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<pretix-widget event="https://pretix.eu/demo/series/" style="calendar"></pretix-widget>
|
||||
<pretix-widget event="https://pretix.eu/demo/series/" list-type="calendar"></pretix-widget>
|
||||
<noscript>
|
||||
<div class="pretix-widget">
|
||||
<div class="pretix-widget-info-message">
|
||||
@@ -176,7 +176,7 @@ You can see an example here:
|
||||
You can filter events by meta data attributes. You can create those attributes in your order profile and set their values in both event and series date
|
||||
settings. For example, if you set up a meta data property called "Promoted" that you set to "Yes" on some events, you can pass a filter like this::
|
||||
|
||||
<pretix-widget event="https://pretix.eu/demo/series/" style="list" filter="attr[Promoted]=Yes"></pretix-widget>
|
||||
<pretix-widget event="https://pretix.eu/demo/series/" list-type="list" filter="attr[Promoted]=Yes"></pretix-widget>
|
||||
|
||||
pretix Button
|
||||
-------------
|
||||
|
||||
BIN
res/icon.png
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 5.7 KiB |
37
res/icon.svg
@@ -1,36 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 149.59399 149.59399"
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
height="159.56693"
|
||||
width="159.56693">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
transform="translate(-257.78125,-548.74975)"
|
||||
id="layer1">
|
||||
<path
|
||||
id="rect3888"
|
||||
transform="matrix(0.93749999,0,0,0.93749999,257.78125,548.74975)"
|
||||
d="M 21.333984 21.333984 L 21.333984 57.699219 C 33.470966 57.699219 43.320312 67.601506 43.320312 79.800781 C 43.320312 92.000035 33.470966 101.86719 21.333984 101.86719 L 21.333984 138.23438 L 94.226562 138.23438 L 94.226562 128.09961 L 97.410156 128.09961 L 97.410156 138.23438 L 138.23438 138.23438 L 138.23438 101.86719 C 138.22328 101.86721 138.21216 101.86719 138.20117 101.86719 C 126.0642 101.86719 116.21289 92.000035 116.21289 79.800781 C 116.21289 67.601506 126.0642 57.699219 138.20117 57.699219 C 138.21237 57.699219 138.22339 57.699197 138.23438 57.699219 L 138.23438 21.333984 L 97.410156 21.333984 L 97.410156 31.033203 L 94.226562 31.033203 L 94.226562 21.333984 L 21.333984 21.333984 z M 94.226562 38.5 L 97.410156 38.5 L 97.410156 53.433594 L 94.226562 53.433594 L 94.226562 38.5 z M 94.226562 60.900391 L 97.410156 60.900391 L 97.410156 75.833984 L 94.226562 75.833984 L 94.226562 60.900391 z M 67.044922 64.027344 C 76.359333 64.027344 82.662109 68.991742 82.662109 79.533203 C 82.662109 89.014942 77.139434 95.039062 69.386719 95.039062 C 67.490377 95.039062 65.927327 94.814901 65.146484 94.591797 L 65.146484 106.64062 L 54.550781 106.64062 L 54.550781 66.314453 C 57.395304 64.97585 61.244324 64.027344 67.044922 64.027344 z M 66.990234 70.216797 C 66.209392 70.216797 65.648458 70.328766 65.146484 70.496094 L 65.146484 88.568359 C 65.536906 88.735677 66.097199 88.845703 66.822266 88.845703 C 70.61497 88.845703 72.175781 85.725087 72.175781 79.589844 C 72.175781 73.287273 70.838704 70.216797 66.990234 70.216797 z M 94.226562 83.300781 L 97.410156 83.300781 L 97.410156 98.234375 L 94.226562 98.234375 L 94.226562 83.300781 z M 94.226562 105.69922 L 97.410156 105.69922 L 97.410156 120.63281 L 94.226562 120.63281 L 94.226562 105.69922 z "
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#3b1c4a;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.06666672;marker:none;enable-background:accumulate" />
|
||||
</g>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="159.567" height="159.567" version="1.1" viewBox="0 0 149.594 149.594"><g transform="matrix(.9375 0 0 .9375 20.413 20.413)"><path d="M45.52 48.98c-.74 0-1.28.13-1.75.27v17.43c.4.13.94.27 1.61.27 3.63 0 5.18-3.03 5.18-8.95s-1.35-9.02-5.05-9.02z" fill="#3b1c4a"/><path d="M114.72 36.13c.74-.07 1.28-.61 1.28-1.35V1.35c0-.74-.61-1.35-1.35-1.35H75.99v5.38c0 1.15-.94 2.09-2.09 2.09s-2.09-.94-2.09-2.09V0H1.35C.61 0 0 .61 0 1.35v33.44c0 .74.54 1.28 1.28 1.35 11.51.67 20.66 10.23 20.66 21.94s-9.15 21.2-20.66 21.8c-.74.07-1.28.61-1.28 1.35v33.44c0 .74.61 1.35 1.35 1.35h70.48v-5.38c0-1.19.9-2.09 2.09-2.09s2.09.94 2.09 2.09v5.38h38.66c.74 0 1.35-.61 1.35-1.35V81.23c0-.74-.54-1.28-1.28-1.35-11.51-.67-20.66-10.16-20.66-21.87s9.15-21.26 20.66-21.87zM47.87 72.87c-1.82 0-3.36-.2-4.1-.4v11.64H33.54V45.22C36.3 43.94 40 43 45.58 43c8.95 0 15.07 4.78 15.07 14.94 0 9.15-5.32 14.94-12.78 14.94zm28.12 25.3c0 1.15-.94 2.09-2.09 2.09s-2.09-.94-2.09-2.09V87.4c0-1.15.94-2.09 2.09-2.09s2.09.97 2.09 2.09zm0-23.28c0 1.11-.97 2.09-2.09 2.09-1.12 0-2.09-.97-2.09-2.09V64.12c0-1.19.9-2.09 2.09-2.09s2.09.94 2.09 2.09zm0-23.15c0 1.15-.94 2.09-2.09 2.09s-2.09-.94-2.09-2.09V40.97c0-1.15.94-2.09 2.09-2.09s2.09.97 2.09 2.09zm0-23.28c0 1.11-.97 2.09-2.09 2.09-1.12 0-2.09-.97-2.09-2.09V17.69c0-1.19.9-2.09 2.09-2.09s2.09.94 2.09 2.09z" fill="#3b1c4a"/></g></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 1.4 KiB |
BIN
res/logo.png
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.8 KiB |
73
res/logo.svg
@@ -1,72 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="600"
|
||||
height="400"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.92.1 r"
|
||||
sodipodi:docname="logo.svg"
|
||||
inkscape:export-filename="/tmp/LOGO.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96"
|
||||
viewBox="0 0 562.50001 375.00002">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.9899495"
|
||||
inkscape:cx="133.36756"
|
||||
inkscape:cy="276.10571"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1916"
|
||||
inkscape:window-height="1041"
|
||||
inkscape:window-x="1920"
|
||||
inkscape:window-y="18"
|
||||
inkscape:window-maximized="0"
|
||||
fit-margin-top="20"
|
||||
fit-margin-left="20"
|
||||
fit-margin-right="20"
|
||||
fit-margin-bottom="20" />
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Ebene 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-259.03125,-322.09374)">
|
||||
<path
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#3b1c4a;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.91138947;marker:none;enable-background:accumulate"
|
||||
d="m 297.38548,404.85558 v 65.16643 c 21.86016,0 39.6016,17.74144 39.6016,39.60159 0,21.86015 -17.74144,39.54187 -39.6016,39.54187 v 65.16644 h 280.37693 v -18.1582 h 5.73417 v 18.1582 h 199.68046 v -65.16644 c -0.02,4e-5 -0.0397,0 -0.0596,0 -21.86015,0 -39.6016,-17.68172 -39.6016,-39.54187 0,-21.86015 17.74145,-39.60159 39.6016,-39.60159 0.02,0 0.0397,-4e-5 0.0596,0 V 404.85558 H 583.49658 v 17.38169 h -5.73417 V 404.85558 Z M 577.76241,435.617 h 5.73417 v 26.75945 h -5.73417 z m 79.21068,24.53074 c 5.33405,0 9.60172,3.68466 9.60172,8.24287 0,4.5582 -4.26767,8.33993 -9.60172,8.33993 -5.2371,0 -9.50469,-3.78173 -9.50469,-8.33993 0,-4.55821 4.26759,-8.24287 9.50469,-8.24287 z m -25.28486,8.04874 v 15.32472 h 7.56717 v 12.02458 h -7.56717 v 23.47051 c 0,3.87934 1.35737,5.33473 4.0729,5.33473 1.35775,0 2.03951,-0.19461 3.49427,-0.77651 v 11.34514 c -1.35777,0.77585 -4.56174,1.84419 -8.829,1.84419 -11.73495,0 -17.16515,-7.17511 -17.16515,-16.19455 v -25.02351 h -5.43179 V 483.5212 h 5.43179 v -10.66944 z m -53.92582,7.5597 h 5.73417 v 26.75945 h -5.73417 z m -142.52917,6.79439 c 16.19618,0 27.15517,8.63124 27.15517,26.96104 0,16.48713 -9.6016,26.96105 -23.08227,26.96105 -3.29741,0 -6.01528,-0.38857 -7.37304,-0.77651 v 20.95062 H 413.50612 V 486.5264 c 4.94614,-2.32759 11.64087,-3.97583 21.72712,-3.97583 z m 95.30815,0 c 16.39014,0 24.14917,11.34479 23.17933,29.09269 l -30.45158,4.27076 c 1.1638,5.62501 4.16799,8.437 10.85985,8.437 6.20689,0 11.05633,-1.36007 13.96583,-2.81482 l 4.0729,11.2518 c -4.5582,2.23062 -10.6662,3.97584 -20.36451,3.97584 -17.06903,0 -26.08749,-11.54096 -26.08749,-27.25223 0,-15.71126 8.43552,-26.96104 24.82567,-26.96104 z m -34.25568,0.0113 c 1.70649,0.026 3.49392,0.0859 5.36084,0.18292 l -3.20307,12.80108 c -3.39442,-1.6487 -6.69185,-1.74642 -9.11643,-0.87356 v 41.121 h -18.42698 v -49.2668 c 5.3462,-2.63068 13.44024,-4.1463 25.38564,-3.96465 z m 151.47387,0.95943 h 18.42699 v 52.27202 h -18.42699 z m 25.29605,0 h 19.20348 l 6.30535,13.09227 h 0.19412 l 6.884,-13.09227 h 17.16517 l -15.22393,24.14622 16.67986,28.1258 h -20.3645 l -6.69361,-14.45115 h -0.19412 l -6.98104,14.45115 h -18.62112 l 16.38867,-26.96104 z m -143.29075,9.60175 c -5.43106,0 -8.43651,4.94275 -7.85461,14.05916 l 14.8394,-2.22871 c 0,-7.9526 -2.3296,-11.83045 -6.98479,-11.83045 z m -94.6287,0.19038 c -1.35776,0 -2.33024,0.19437 -3.20308,0.48532 v 31.4222 c 0.67888,0.29093 1.65112,0.48531 2.91189,0.48531 6.59487,0 9.31055,-5.42933 9.31055,-16.09748 0,-10.95908 -2.32753,-16.29535 -9.01936,-16.29535 z m 142.62623,22.58194 h 5.73417 v 26.75946 h -5.73417 z m 0,40.13918 h 5.73417 v 26.75946 h -5.73417 z"
|
||||
id="rect3888"
|
||||
inkscape:connector-curvature="0"
|
||||
inkscape:export-filename="/tmp/LOGO.png"
|
||||
inkscape:export-xdpi="88"
|
||||
inkscape:export-ydpi="88" />
|
||||
</g>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="600" height="400" version="1.1" viewBox="0 0 562.5 375"><g transform="translate(-259.031 -322.094)"><g transform="matrix(3.79525 0 0 3.79525 297.317 405.22)"><path d="M36.29 23.21c-.35 0-.61.06-.83.13v8.29c.19.06.45.13.77.13 1.73 0 2.46-1.44 2.46-4.26s-.64-4.29-2.4-4.29z" fill="#3b1c4a"/><path d="M61.22 23.15c-1.44 0-2.21 1.31-2.08 3.71l3.9-.58c.03-2.11-.58-3.14-1.82-3.14z" fill="#3b1c4a"/><path d="M127.39 17.1c.35-.03.61-.29.61-.64V.64c0-.35-.29-.64-.64-.64H75.81v2.48c0 .55-.45.99-.99.99s-.99-.45-.99-.99V0H.64C.29 0 0 .29 0 .64v15.82c0 .35.26.61.61.64 5.47.32 9.82 4.86 9.82 10.43 0 5.57-4.35 10.08-9.82 10.37-.35.03-.61.29-.61.64v15.82c0 .35.29.64.64.64h73.22-.03v-2.48c0-.57.43-.99.99-.99s.99.45.99.99V55h-.03 51.58c.35 0 .64-.29.64-.64V38.54c0-.35-.26-.61-.61-.64-5.47-.32-9.82-4.83-9.82-10.4s4.35-10.11 9.82-10.4zM37.41 34.57c-.86 0-1.6-.1-1.95-.19v5.54H30.6v-18.5c1.31-.61 3.07-1.06 5.73-1.06 4.26 0 7.17 2.27 7.17 7.1 0 4.35-2.53 7.1-6.08 7.1zm15.58-10.78c-.9-.45-1.76-.45-2.4-.22v10.85h-4.86V21.43c1.41-.7 3.55-1.09 6.69-1.06.45 0 .93.03 1.41.06L53 23.79Zm14.56 4.26-8.03 1.12c.32 1.47 1.09 2.21 2.85 2.21 1.63 0 2.91-.35 3.68-.74l1.09 2.98c-1.22.58-2.82 1.06-5.38 1.06-4.51 0-6.88-3.04-6.88-7.17s2.21-7.1 6.53-7.1c4.35-.03 6.4 2.98 6.14 7.65zm8.26 18.56c0 .55-.45.99-.99.99s-.99-.45-.99-.99v-5.12c0-.55.45-.99.99-.99s.99.46.99.99zm0-11.07c0 .53-.46.99-.99.99s-.99-.46-.99-.99v-5.12c0-.57.43-.99.99-.99s.99.45.99.99zm0-11.01c0 .55-.45.99-.99.99s-.99-.45-.99-.99v-5.12c0-.55.45-.99.99-.99s.99.46.99.99zm0-11.07c0 .53-.46.99-.99.99s-.99-.46-.99-.99V8.34c0-.57.43-.99.99-.99s.99.45.99.99zm14.3 10.34h-2.02v6.18c0 1.02.35 1.41 1.09 1.41.35 0 .54-.06.93-.19v2.98c-.35.19-1.22.48-2.34.48-3.1 0-4.51-1.89-4.51-4.26v-6.59h-1.44v-3.17h1.44v-2.82l4.86-1.22v4.03h1.98v3.17zm7.07 10.62h-4.86V20.66h4.86zm-2.43-15.58c-1.38 0-2.5-.99-2.5-2.21s1.12-2.18 2.5-2.18 2.53.96 2.53 2.18c0 1.22-1.12 2.21-2.53 2.21zm12.35 15.58-1.76-3.81h-.06l-1.82 3.81h-4.9l4.32-7.1-3.87-6.66h5.06l1.66 3.46h.06l1.82-3.46h4.51l-4 6.37 4.38 7.42-5.41-.03z" fill="#3b1c4a"/></g></g></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 2.1 KiB |
@@ -1,68 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="294.15625"
|
||||
height="149.59375"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="logo_draft_white.svg"
|
||||
inkscape:export-filename="/home/raphael/proj/pretix/pretix/logo_draft.png"
|
||||
inkscape:export-xdpi="88.529999"
|
||||
inkscape:export-ydpi="88.529999">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.9899495"
|
||||
inkscape:cx="121.06383"
|
||||
inkscape:cy="277.43904"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="956"
|
||||
inkscape:window-height="1041"
|
||||
inkscape:window-x="2880"
|
||||
inkscape:window-y="18"
|
||||
inkscape:window-maximized="0"
|
||||
fit-margin-top="20"
|
||||
fit-margin-left="20"
|
||||
fit-margin-right="20"
|
||||
fit-margin-bottom="20" />
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Ebene 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-257.78125,-548.75)">
|
||||
<path
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;marker:none;enable-background:accumulate"
|
||||
d="M 20 20 L 20 54.09375 C 31.43679 54.09375 40.71875 63.37571 40.71875 74.8125 C 40.71875 86.24928 31.43679 95.5 20 95.5 L 20 129.59375 L 166.6875 129.59375 L 166.6875 120.09375 L 169.6875 120.09375 L 169.6875 129.59375 L 274.15625 129.59375 L 274.15625 95.5 C 274.14575 95.50002 274.1354 95.5 274.125 95.5 C 262.68822 95.5 253.40625 86.24928 253.40625 74.8125 C 253.40625 63.37571 262.68822 54.09375 274.125 54.09375 C 274.1355 54.09375 274.14585 54.09373 274.15625 54.09375 L 274.15625 20 L 169.6875 20 L 169.6875 29.09375 L 166.6875 29.09375 L 166.6875 20 L 20 20 z M 166.6875 36.09375 L 169.6875 36.09375 L 169.6875 50.09375 L 166.6875 50.09375 L 166.6875 36.09375 z M 208.12891 48.927734 C 210.91958 48.927734 213.15234 50.855474 213.15234 53.240234 C 213.15234 55.624994 210.91958 57.603516 208.12891 57.603516 C 205.38897 57.603516 203.15625 55.624994 203.15625 53.240234 C 203.15625 50.855474 205.38897 48.927734 208.12891 48.927734 z M 194.90039 53.138672 L 194.90039 61.15625 L 198.85938 61.15625 L 198.85938 67.447266 L 194.90039 67.447266 L 194.90039 79.726562 C 194.90039 81.756152 195.61054 82.517578 197.03125 82.517578 C 197.7416 82.517578 198.09828 82.415768 198.85938 82.111328 L 198.85938 88.046875 C 198.14902 88.452785 196.47277 89.011719 194.24023 89.011719 C 188.10074 89.011719 185.25977 85.257843 185.25977 80.539062 L 185.25977 67.447266 L 182.41797 67.447266 L 182.41797 61.15625 L 185.25977 61.15625 L 185.25977 55.574219 L 194.90039 53.138672 z M 166.6875 57.09375 L 169.6875 57.09375 L 169.6875 71.09375 L 166.6875 71.09375 L 166.6875 57.09375 z M 92.119141 60.648438 C 100.59265 60.648438 106.32617 65.164126 106.32617 74.753906 C 106.32617 83.379636 101.30281 88.859375 94.25 88.859375 C 92.52486 88.859375 91.102928 88.656085 90.392578 88.453125 L 90.392578 99.414062 L 80.751953 99.414062 L 80.751953 62.728516 C 83.339673 61.510766 86.842221 60.648435 92.119141 60.648438 z M 141.98242 60.648438 C 150.55741 60.648438 154.61678 66.583801 154.10938 75.869141 L 138.17773 78.103516 C 138.78661 81.046406 140.35834 82.517578 143.85938 82.517578 C 147.1067 82.517578 149.64383 81.806022 151.16602 81.044922 L 153.29688 86.931641 C 150.91212 88.098651 147.71654 89.011719 142.64258 89.011719 C 133.71241 89.011719 128.99414 82.973726 128.99414 74.753906 C 128.99414 66.534096 133.40743 60.648438 141.98242 60.648438 z M 124.06055 60.654297 C 124.95335 60.667874 125.8885 60.69926 126.86523 60.75 L 125.18945 67.447266 C 123.41356 66.584696 121.68841 66.533574 120.41992 66.990234 L 120.41992 88.503906 L 110.7793 88.503906 L 110.7793 62.728516 C 113.57632 61.352202 117.81096 60.559256 124.06055 60.654297 z M 203.30859 61.15625 L 212.94922 61.15625 L 212.94922 88.503906 L 203.30859 88.503906 L 203.30859 61.15625 z M 216.54297 61.15625 L 226.58984 61.15625 L 229.88867 68.005859 L 229.99023 68.005859 L 233.5918 61.15625 L 242.57227 61.15625 L 234.60742 73.789062 L 243.33398 88.503906 L 232.67969 88.503906 L 229.17773 80.943359 L 229.07617 80.943359 L 225.42383 88.503906 L 215.68164 88.503906 L 224.25586 74.398438 L 216.54297 61.15625 z M 141.57617 66.179688 C 138.73475 66.179688 137.16236 68.765636 137.4668 73.535156 L 145.23047 72.369141 C 145.23047 68.208501 144.01167 66.179687 141.57617 66.179688 z M 92.068359 66.279297 C 91.358009 66.279297 90.849228 66.380983 90.392578 66.533203 L 90.392578 82.972656 C 90.747748 83.124866 91.256406 83.226562 91.916016 83.226562 C 95.366316 83.226562 96.787109 80.386048 96.787109 74.804688 C 96.787109 69.071117 95.569389 66.279297 92.068359 66.279297 z M 166.6875 78.09375 L 169.6875 78.09375 L 169.6875 92.09375 L 166.6875 92.09375 L 166.6875 78.09375 z M 166.6875 99.09375 L 169.6875 99.09375 L 169.6875 113.09375 L 166.6875 113.09375 L 166.6875 99.09375 z "
|
||||
transform="translate(257.78125,548.75)"
|
||||
id="rect3888" />
|
||||
</g>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="294.156" height="149.594" version="1.1"><g transform="translate(-257.781 -548.75)"><g transform="matrix(1.9856 0 0 1.9856 277.781 568.75)"><path d="M36.29 23.21c-.35 0-.61.06-.83.13v8.29c.19.06.45.13.77.13 1.73 0 2.46-1.44 2.46-4.26s-.64-4.29-2.4-4.29z" fill="#f8f8f8"/><path d="M61.22 23.15c-1.44 0-2.21 1.31-2.08 3.71l3.9-.58c.03-2.11-.58-3.14-1.82-3.14z" fill="#f8f8f8"/><path d="M127.39 17.1c.35-.03.61-.29.61-.64V.64c0-.35-.29-.64-.64-.64H75.94v2.48c0 .62-.5 1.12-1.12 1.12-.62 0-1.12-.5-1.12-1.12V0H.64C.29 0 0 .29 0 .64v15.82c0 .35.26.61.61.64 5.47.32 9.82 4.86 9.82 10.43 0 5.57-4.35 10.08-9.82 10.37-.35.03-.61.29-.61.64v15.82c0 .35.29.64.64.64h73.22-.16v-2.48c0-.63.49-1.12 1.12-1.12.63 0 1.12.5 1.12 1.12V55h-.16 51.58c.35 0 .64-.29.64-.64V38.54c0-.35-.26-.61-.61-.64-5.47-.32-9.82-4.83-9.82-10.4s4.35-10.11 9.82-10.4zM37.41 34.57c-.86 0-1.6-.1-1.95-.19v5.54H30.6v-18.5c1.31-.61 3.07-1.06 5.73-1.06 4.26 0 7.17 2.27 7.17 7.1 0 4.35-2.53 7.1-6.08 7.1zm15.58-10.78c-.9-.45-1.76-.45-2.4-.22v10.85h-4.86V21.43c1.41-.7 3.55-1.09 6.69-1.06.45 0 .93.03 1.41.06L53 23.79Zm14.56 4.26-8.03 1.12c.32 1.47 1.09 2.21 2.85 2.21 1.63 0 2.91-.35 3.68-.74l1.09 2.98c-1.22.58-2.82 1.06-5.38 1.06-4.51 0-6.88-3.04-6.88-7.17s2.21-7.1 6.53-7.1c4.35-.03 6.4 2.98 6.14 7.65zm8.38 18.56c0 .62-.5 1.12-1.12 1.12-.62 0-1.12-.5-1.12-1.12v-5.12c0-.62.5-1.12 1.12-1.12.62 0 1.12.52 1.12 1.12zm0-11.07c0 .6-.52 1.12-1.12 1.12-.6 0-1.12-.52-1.12-1.12v-5.12c0-.63.49-1.12 1.12-1.12.63 0 1.12.5 1.12 1.12zm0-11.01c0 .62-.5 1.12-1.12 1.12-.62 0-1.12-.5-1.12-1.12v-5.12c0-.62.5-1.12 1.12-1.12.62 0 1.12.52 1.12 1.12zm0-11.07c0 .6-.52 1.12-1.12 1.12-.6 0-1.12-.52-1.12-1.12V8.34c0-.63.49-1.12 1.12-1.12.63 0 1.12.5 1.12 1.12zM90.11 23.8h-2.02v6.18c0 1.02.35 1.41 1.09 1.41.35 0 .54-.06.93-.19v2.98c-.35.19-1.22.48-2.34.48-3.1 0-4.51-1.89-4.51-4.26v-6.59h-1.44v-3.17h1.44v-2.82l4.86-1.22v4.03h1.98v3.17zm7.07 10.62h-4.86V20.66h4.86zm-2.43-15.58c-1.38 0-2.5-.99-2.5-2.21s1.12-2.18 2.5-2.18 2.53.96 2.53 2.18c0 1.22-1.12 2.21-2.53 2.21zm12.35 15.58-1.76-3.81h-.06l-1.82 3.81h-4.9l4.32-7.1-3.87-6.66h5.06l1.66 3.46h.06l1.82-3.46h4.51l-4 6.37 4.38 7.42-5.41-.03z" fill="#f8f8f8"/></g></g></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 2.2 KiB |
@@ -19,4 +19,4 @@
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
__version__ = "4.16.0.dev0"
|
||||
__version__ = "4.18.0.dev0"
|
||||
|
||||
@@ -19,9 +19,12 @@
|
||||
# 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 logging
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FullAccessSecurityProfile:
|
||||
identifier = 'full'
|
||||
@@ -36,7 +39,13 @@ class AllowListSecurityProfile:
|
||||
|
||||
def is_allowed(self, request):
|
||||
key = (request.method, f"{request.resolver_match.namespace}:{request.resolver_match.url_name}")
|
||||
return key in self.allowlist
|
||||
if key in self.allowlist:
|
||||
return True
|
||||
else:
|
||||
logger.info(
|
||||
f'Request {key} not allowed in profile {self.identifier}'
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
class PretixScanSecurityProfile(AllowListSecurityProfile):
|
||||
@@ -65,6 +74,7 @@ class PretixScanSecurityProfile(AllowListSecurityProfile):
|
||||
('GET', 'api-v1:checkinlistpos-list'),
|
||||
('POST', 'api-v1:checkinlistpos-redeem'),
|
||||
('GET', 'api-v1:revokedsecrets-list'),
|
||||
('GET', 'api-v1:blockedsecrets-list'),
|
||||
('GET', 'api-v1:order-list'),
|
||||
('GET', 'api-v1:orderposition-pdf_image'),
|
||||
('GET', 'api-v1:event.settings'),
|
||||
@@ -99,6 +109,7 @@ class PretixScanNoSyncNoSearchSecurityProfile(AllowListSecurityProfile):
|
||||
('POST', 'api-v1:checkinlist-failed_checkins'),
|
||||
('POST', 'api-v1:checkinlistpos-redeem'),
|
||||
('GET', 'api-v1:revokedsecrets-list'),
|
||||
('GET', 'api-v1:blockedsecrets-list'),
|
||||
('GET', 'api-v1:orderposition-pdf_image'),
|
||||
('GET', 'api-v1:event.settings'),
|
||||
('POST', 'api-v1:upload'),
|
||||
@@ -133,6 +144,7 @@ class PretixScanNoSyncSecurityProfile(AllowListSecurityProfile):
|
||||
('GET', 'api-v1:checkinlistpos-list'),
|
||||
('POST', 'api-v1:checkinlistpos-redeem'),
|
||||
('GET', 'api-v1:revokedsecrets-list'),
|
||||
('GET', 'api-v1:blockedsecrets-list'),
|
||||
('GET', 'api-v1:orderposition-pdf_image'),
|
||||
('GET', 'api-v1:event.settings'),
|
||||
('POST', 'api-v1:upload'),
|
||||
@@ -199,6 +211,7 @@ class PretixPosSecurityProfile(AllowListSecurityProfile):
|
||||
('POST', 'plugins:pretix_posbackend:stripeterminal.paymentintent'),
|
||||
('PUT', 'plugins:pretix_posbackend:file.upload'),
|
||||
('GET', 'api-v1:revokedsecrets-list'),
|
||||
('GET', 'api-v1:blockedsecrets-list'),
|
||||
('GET', 'api-v1:event.settings'),
|
||||
('GET', 'plugins:pretix_seating:event.event'),
|
||||
('GET', 'plugins:pretix_seating:event.event.subevent'),
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
import json
|
||||
import logging
|
||||
from hashlib import sha1
|
||||
|
||||
from django.conf import settings
|
||||
@@ -32,6 +33,9 @@ from rest_framework import status
|
||||
|
||||
from pretix.api.models import ApiCall
|
||||
from pretix.base.models import Organizer
|
||||
from pretix.helpers import OF_SELF
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class IdempotencyMiddleware:
|
||||
@@ -56,7 +60,7 @@ class IdempotencyMiddleware:
|
||||
idempotency_key = request.headers.get('X-Idempotency-Key', '')
|
||||
|
||||
with transaction.atomic():
|
||||
call, created = ApiCall.objects.select_for_update().get_or_create(
|
||||
call, created = ApiCall.objects.select_for_update(of=OF_SELF).get_or_create(
|
||||
auth_hash=auth_hash,
|
||||
idempotency_key=idempotency_key,
|
||||
defaults={
|
||||
@@ -96,6 +100,9 @@ class IdempotencyMiddleware:
|
||||
return resp
|
||||
else:
|
||||
if call.locked:
|
||||
logger.info(
|
||||
f'Concurrent request with idempotency key {idempotency_key} blocked.'
|
||||
)
|
||||
r = JsonResponse(
|
||||
{'detail': 'Concurrent request with idempotency key.'},
|
||||
status=status.HTTP_409_CONFLICT,
|
||||
@@ -110,6 +117,7 @@ class IdempotencyMiddleware:
|
||||
content=content,
|
||||
status=call.response_code,
|
||||
)
|
||||
logger.info(f'API response replayed from idempotency store for key {idempotency_key} [{call.response_code}]')
|
||||
for k, v in json.loads(call.response_headers).values():
|
||||
r[k] = v
|
||||
return r
|
||||
|
||||
77
src/pretix/api/migrations/0009_auto_20221217_1847.py
Normal file
@@ -0,0 +1,77 @@
|
||||
# Generated by Django 3.2.16 on 2022-12-17 18:47
|
||||
|
||||
import uuid
|
||||
|
||||
import django.db.models.deletion
|
||||
import oauth2_provider.generators
|
||||
import oauth2_provider.models
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('pretixbase', '0226_itemvariationmetavalue'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('pretixapi', '0008_webhookcallretry'),
|
||||
]
|
||||
run_before = [
|
||||
('oauth2_provider', '0002_auto_20190406_1805'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='oauthapplication',
|
||||
name='algorithm',
|
||||
field=models.CharField(default='', max_length=5),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='oauthgrant',
|
||||
name='claims',
|
||||
field=models.TextField(default=''),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='oauthgrant',
|
||||
name='code_challenge',
|
||||
field=models.CharField(default='', max_length=128),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='oauthgrant',
|
||||
name='code_challenge_method',
|
||||
field=models.CharField(default='', max_length=10),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='oauthgrant',
|
||||
name='nonce',
|
||||
field=models.CharField(default='', max_length=255),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='oauthapplication',
|
||||
name='client_secret',
|
||||
field=oauth2_provider.models.ClientSecretField(db_index=True, default=oauth2_provider.generators.generate_client_secret, max_length=255),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='OAuthIDToken',
|
||||
fields=[
|
||||
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
||||
('jti', models.UUIDField(default=uuid.uuid4, unique=True)),
|
||||
('expires', models.DateTimeField()),
|
||||
('scope', models.TextField()),
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
('updated', models.DateTimeField(auto_now=True)),
|
||||
('application', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.OAUTH2_PROVIDER_APPLICATION_MODEL)),
|
||||
('organizers', models.ManyToManyField(to='pretixbase.Organizer')),
|
||||
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='pretixapi_oauthidtoken', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='oauthaccesstoken',
|
||||
name='id_token',
|
||||
field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='access_token', to='pretixapi.oauthidtoken'),
|
||||
),
|
||||
]
|
||||
18
src/pretix/api/migrations/0010_webhook_comment.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 3.2.17 on 2023-02-07 12:18
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('pretixapi', '0009_auto_20221217_1847'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='webhook',
|
||||
name='comment',
|
||||
field=models.CharField(max_length=255, null=True),
|
||||
),
|
||||
]
|
||||
@@ -29,8 +29,8 @@ from oauth2_provider.generators import (
|
||||
generate_client_id, generate_client_secret,
|
||||
)
|
||||
from oauth2_provider.models import (
|
||||
AbstractAccessToken, AbstractApplication, AbstractGrant,
|
||||
AbstractRefreshToken,
|
||||
AbstractAccessToken, AbstractApplication, AbstractGrant, AbstractIDToken,
|
||||
AbstractRefreshToken, ClientSecretField,
|
||||
)
|
||||
from oauth2_provider.validators import URIValidator
|
||||
|
||||
@@ -46,7 +46,7 @@ class OAuthApplication(AbstractApplication):
|
||||
verbose_name=_("Client ID"),
|
||||
max_length=100, unique=True, default=generate_client_id, db_index=True
|
||||
)
|
||||
client_secret = models.CharField(
|
||||
client_secret = ClientSecretField(
|
||||
verbose_name=_("Client secret"),
|
||||
max_length=255, blank=False, default=generate_client_secret, db_index=True
|
||||
)
|
||||
@@ -67,12 +67,26 @@ class OAuthGrant(AbstractGrant):
|
||||
redirect_uri = models.CharField(max_length=2500) # Only 255 in AbstractGrant, which caused problems
|
||||
|
||||
|
||||
class OAuthIDToken(AbstractIDToken):
|
||||
application = models.ForeignKey(
|
||||
OAuthApplication, on_delete=models.CASCADE,
|
||||
)
|
||||
organizers = models.ManyToManyField('pretixbase.Organizer')
|
||||
|
||||
|
||||
class OAuthAccessToken(AbstractAccessToken):
|
||||
source_refresh_token = models.OneToOneField(
|
||||
# unique=True implied by the OneToOneField
|
||||
'OAuthRefreshToken', on_delete=models.SET_NULL, blank=True, null=True,
|
||||
related_name="refreshed_access_token"
|
||||
)
|
||||
id_token = models.OneToOneField(
|
||||
OAuthIDToken,
|
||||
on_delete=models.CASCADE,
|
||||
blank=True,
|
||||
null=True,
|
||||
related_name="access_token",
|
||||
)
|
||||
application = models.ForeignKey(
|
||||
OAuthApplication, on_delete=models.CASCADE, blank=True, null=True,
|
||||
)
|
||||
@@ -98,6 +112,7 @@ class WebHook(models.Model):
|
||||
target_url = models.URLField(verbose_name=_("Target URL"), max_length=255)
|
||||
all_events = models.BooleanField(default=True, verbose_name=_("All events (including newly created ones)"))
|
||||
limit_events = models.ManyToManyField('pretixbase.Event', verbose_name=_("Limit to events"), blank=True)
|
||||
comment = models.CharField(verbose_name=_("Comment"), max_length=255, null=True, blank=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ('id',)
|
||||
|
||||
@@ -19,9 +19,19 @@
|
||||
# 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 rest_framework.filters import OrderingFilter
|
||||
from rest_framework.pagination import PageNumberPagination
|
||||
|
||||
from pretix.helpers import get_deterministic_ordering
|
||||
|
||||
|
||||
class Pagination(PageNumberPagination):
|
||||
page_size_query_param = 'page_size'
|
||||
max_page_size = 50
|
||||
|
||||
|
||||
class TotalOrderingFilter(OrderingFilter):
|
||||
def get_ordering(self, request, queryset, view):
|
||||
o = super().get_ordering(request, queryset, view)
|
||||
o = get_deterministic_ordering(queryset.model, o)
|
||||
return o
|
||||
|
||||
@@ -59,6 +59,7 @@ from pretix.base.settings import (
|
||||
LazyI18nStringList, validate_event_settings,
|
||||
)
|
||||
from pretix.base.signals import api_event_settings_fields
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -164,6 +165,10 @@ class EventSerializer(I18nAwareModelSerializer):
|
||||
timezone = TimeZoneField(required=False, choices=[(a, a) for a in common_timezones])
|
||||
valid_keys = ValidKeysField(source='*', read_only=True)
|
||||
best_availability_state = serializers.IntegerField(allow_null=True, read_only=True)
|
||||
public_url = serializers.SerializerMethodField('get_event_url', read_only=True)
|
||||
|
||||
def get_event_url(self, event):
|
||||
return build_absolute_uri(event, 'presale:event.index')
|
||||
|
||||
class Meta:
|
||||
model = Event
|
||||
@@ -171,7 +176,7 @@ class EventSerializer(I18nAwareModelSerializer):
|
||||
'date_to', 'date_admission', 'is_public', 'presale_start',
|
||||
'presale_end', 'location', 'geo_lat', 'geo_lon', 'has_subevents', 'meta_data', 'seating_plan',
|
||||
'plugins', 'seat_category_mapping', 'timezone', 'item_meta_properties', 'valid_keys',
|
||||
'sales_channels', 'best_availability_state')
|
||||
'sales_channels', 'best_availability_state', 'public_url')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
@@ -662,6 +667,7 @@ class EventSettingsSerializer(SettingsSerializer):
|
||||
'show_times',
|
||||
'show_items_outside_presale_period',
|
||||
'display_net_prices',
|
||||
'hide_prices_from_attendees',
|
||||
'presale_start_show_date',
|
||||
'locales',
|
||||
'locale',
|
||||
|
||||
@@ -22,8 +22,10 @@
|
||||
from django import forms
|
||||
from django.http import QueryDict
|
||||
from rest_framework import serializers
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
from pretix.base.exporter import OrganizerLevelExportMixin
|
||||
from pretix.base.timeframes import DateFrameField, SerializerDateFrameField
|
||||
|
||||
|
||||
class FormFieldWrapperField(serializers.Field):
|
||||
@@ -142,6 +144,12 @@ class JobRunSerializer(serializers.Serializer):
|
||||
allow_null=not v.required,
|
||||
validators=v.validators,
|
||||
)
|
||||
elif isinstance(v, DateFrameField):
|
||||
self.fields[k] = SerializerDateFrameField(
|
||||
required=v.required,
|
||||
allow_null=not v.required,
|
||||
validators=v.validators,
|
||||
)
|
||||
else:
|
||||
self.fields[k] = FormFieldWrapperField(form_field=v, required=v.required, allow_null=not v.required)
|
||||
|
||||
@@ -151,5 +159,40 @@ class JobRunSerializer(serializers.Serializer):
|
||||
for k, v in self.fields.items():
|
||||
if isinstance(v, serializers.ManyRelatedField) and k not in data:
|
||||
data[k] = []
|
||||
|
||||
for fk in self.fields.keys():
|
||||
# Backwards compatibility for exports that used to take e.g. (date_from, date_to) or (event_date_from, event_date_to)
|
||||
# and now only take date_range.
|
||||
if fk.endswith("_range") and isinstance(self.fields[fk], SerializerDateFrameField) and fk not in data:
|
||||
if fk.replace("_range", "_from") in data:
|
||||
d_from = data.pop(fk.replace("_range", "_from"))
|
||||
if d_from:
|
||||
d_from = serializers.DateField().to_internal_value(d_from)
|
||||
else:
|
||||
d_from = None
|
||||
if fk.replace("_range", "_to") in data:
|
||||
d_to = data.pop(fk.replace("_range", "_to"))
|
||||
if d_to:
|
||||
d_to = serializers.DateField().to_internal_value(d_to)
|
||||
else:
|
||||
d_to = None
|
||||
data[fk] = f'{d_from.isoformat() if d_from else ""}/{d_to.isoformat() if d_to else ""}'
|
||||
|
||||
data = super().to_internal_value(data)
|
||||
return data
|
||||
|
||||
def is_valid(self, raise_exception=False):
|
||||
super().is_valid(raise_exception=raise_exception)
|
||||
|
||||
fields_keys = set(self.fields.keys())
|
||||
input_keys = set(self.initial_data.keys())
|
||||
|
||||
additional_fields = input_keys - fields_keys
|
||||
|
||||
if bool(additional_fields):
|
||||
self._errors['fields'] = ['Additional fields not allowed: {}.'.format(list(additional_fields))]
|
||||
|
||||
if self._errors and raise_exception:
|
||||
raise ValidationError(self.errors)
|
||||
|
||||
return not bool(self._errors)
|
||||
|
||||
@@ -60,8 +60,8 @@ class InlineItemVariationSerializer(I18nAwareModelSerializer):
|
||||
model = ItemVariation
|
||||
fields = ('id', 'value', 'active', 'description',
|
||||
'position', 'default_price', 'price', 'original_price', 'require_approval',
|
||||
'require_membership', 'require_membership_types',
|
||||
'require_membership_hidden', 'available_from', 'available_until',
|
||||
'require_membership', 'require_membership_types', 'require_membership_hidden',
|
||||
'checkin_attention', 'available_from', 'available_until',
|
||||
'sales_channels', 'hide_without_voucher', 'meta_data')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -84,8 +84,8 @@ class ItemVariationSerializer(I18nAwareModelSerializer):
|
||||
model = ItemVariation
|
||||
fields = ('id', 'value', 'active', 'description',
|
||||
'position', 'default_price', 'price', 'original_price', 'require_approval',
|
||||
'require_membership', 'require_membership_types',
|
||||
'require_membership_hidden', 'available_from', 'available_until',
|
||||
'require_membership', 'require_membership_types', 'require_membership_hidden',
|
||||
'checkin_attention', 'available_from', 'available_until',
|
||||
'sales_channels', 'hide_without_voucher', 'meta_data')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -95,8 +95,12 @@ class ItemVariationSerializer(I18nAwareModelSerializer):
|
||||
@transaction.atomic
|
||||
def create(self, validated_data):
|
||||
meta_data = validated_data.pop('meta_data', None)
|
||||
require_membership_types = validated_data.pop('require_membership_types', [])
|
||||
variation = ItemVariation.objects.create(**validated_data)
|
||||
|
||||
if require_membership_types:
|
||||
variation.require_membership_types.add(*require_membership_types)
|
||||
|
||||
# Meta data
|
||||
if meta_data is not None:
|
||||
for key, value in meta_data.items():
|
||||
@@ -230,7 +234,7 @@ class ItemSerializer(I18nAwareModelSerializer):
|
||||
class Meta:
|
||||
model = Item
|
||||
fields = ('id', 'category', 'name', 'internal_name', 'active', 'sales_channels', 'description',
|
||||
'default_price', 'free_price', 'tax_rate', 'tax_rule', 'admission',
|
||||
'default_price', 'free_price', 'tax_rate', 'tax_rule', 'admission', 'personalized',
|
||||
'position', 'picture', 'available_from', 'available_until',
|
||||
'require_voucher', 'hide_without_voucher', 'allow_cancel', 'require_bundling',
|
||||
'min_per_order', 'max_per_order', 'checkin_attention', 'has_variations', 'variations',
|
||||
@@ -238,7 +242,9 @@ class ItemSerializer(I18nAwareModelSerializer):
|
||||
'show_quota_left', 'hidden_if_available', 'allow_waitinglist', 'issue_giftcard', 'meta_data',
|
||||
'require_membership', 'require_membership_types', 'require_membership_hidden', 'grant_membership_type',
|
||||
'grant_membership_duration_like_event', 'grant_membership_duration_days',
|
||||
'grant_membership_duration_months')
|
||||
'grant_membership_duration_months', 'validity_mode', 'validity_fixed_from', 'validity_fixed_until',
|
||||
'validity_dynamic_duration_minutes', 'validity_dynamic_duration_hours', 'validity_dynamic_duration_days',
|
||||
'validity_dynamic_duration_months', 'validity_dynamic_start_choice', 'validity_dynamic_start_choice_day_limit')
|
||||
read_only_fields = ('has_variations',)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -258,6 +264,15 @@ class ItemSerializer(I18nAwareModelSerializer):
|
||||
Item.clean_per_order(data.get('min_per_order'), data.get('max_per_order'))
|
||||
Item.clean_available(data.get('available_from'), data.get('available_until'))
|
||||
|
||||
if data.get('personalized') and not data.get('admission'):
|
||||
raise ValidationError(_('Only admission products can currently be personalized.'))
|
||||
|
||||
if data.get('admission') and 'personalized' not in data and not self.instance:
|
||||
# Backwards compatibility
|
||||
data['personalized'] = True
|
||||
elif 'admission' in data and not data['admission']:
|
||||
data['personalized'] = False
|
||||
|
||||
if data.get('issue_giftcard'):
|
||||
if data.get('tax_rule') and data.get('tax_rule').rate > 0:
|
||||
raise ValidationError(
|
||||
|
||||
@@ -52,7 +52,8 @@ from pretix.base.models import (
|
||||
SubEvent, TaxRule, Voucher,
|
||||
)
|
||||
from pretix.base.models.orders import (
|
||||
CartPosition, OrderFee, OrderPayment, OrderRefund, RevokedTicketSecret,
|
||||
BlockedTicketSecret, CartPosition, OrderFee, OrderPayment, OrderRefund,
|
||||
RevokedTicketSecret,
|
||||
)
|
||||
from pretix.base.pdf import get_images, get_variables
|
||||
from pretix.base.services.cart import error_messages
|
||||
@@ -118,6 +119,10 @@ class InvoiceAddressSerializer(I18nAwareModelSerializer):
|
||||
raise ValidationError(
|
||||
{'name': ['Do not specify name if you specified name_parts.']}
|
||||
)
|
||||
|
||||
if data.get('name_parts') and not isinstance(data.get('name_parts'), dict):
|
||||
raise ValidationError({'name_parts': ['Invalid data type']})
|
||||
|
||||
if data.get('name_parts') and '_scheme' not in data.get('name_parts'):
|
||||
data['name_parts']['_scheme'] = self.context['request'].event.settings.name_scheme
|
||||
|
||||
@@ -296,7 +301,9 @@ class FailedCheckinSerializer(I18nAwareModelSerializer):
|
||||
class OrderDownloadsField(serializers.Field):
|
||||
def to_representation(self, instance: Order):
|
||||
if instance.status != Order.STATUS_PAID:
|
||||
if instance.status != Order.STATUS_PENDING or instance.require_approval or not instance.event.settings.ticket_download_pending:
|
||||
if instance.status != Order.STATUS_PENDING or instance.require_approval or (
|
||||
not instance.valid_if_pending and not instance.event.settings.ticket_download_pending
|
||||
):
|
||||
return []
|
||||
|
||||
request = self.context['request']
|
||||
@@ -320,7 +327,9 @@ class OrderDownloadsField(serializers.Field):
|
||||
class PositionDownloadsField(serializers.Field):
|
||||
def to_representation(self, instance: OrderPosition):
|
||||
if instance.order.status != Order.STATUS_PAID:
|
||||
if instance.order.status != Order.STATUS_PENDING or instance.order.require_approval or not instance.order.event.settings.ticket_download_pending:
|
||||
if instance.order.status != Order.STATUS_PENDING or instance.order.require_approval or (
|
||||
not instance.order.valid_if_pending and not instance.order.event.settings.ticket_download_pending
|
||||
):
|
||||
return []
|
||||
if not instance.generate_ticket:
|
||||
return []
|
||||
@@ -433,11 +442,12 @@ class OrderPositionSerializer(I18nAwareModelSerializer):
|
||||
fields = ('id', 'order', 'positionid', 'item', 'variation', 'price', 'attendee_name', 'attendee_name_parts',
|
||||
'company', 'street', 'zipcode', 'city', 'country', 'state', 'discount',
|
||||
'attendee_email', 'voucher', 'tax_rate', 'tax_value', 'secret', 'addon_to', 'subevent', 'checkins',
|
||||
'downloads', 'answers', 'tax_rule', 'pseudonymization_id', 'pdf_data', 'seat', 'canceled')
|
||||
'downloads', 'answers', 'tax_rule', 'pseudonymization_id', 'pdf_data', 'seat', 'canceled',
|
||||
'valid_from', 'valid_until', 'blocked')
|
||||
read_only_fields = (
|
||||
'id', 'order', 'positionid', 'item', 'variation', 'price', 'voucher', 'tax_rate', 'tax_value', 'secret',
|
||||
'addon_to', 'subevent', 'checkins', 'downloads', 'answers', 'tax_rule', 'pseudonymization_id', 'pdf_data',
|
||||
'seat', 'canceled', 'discount',
|
||||
'seat', 'canceled', 'discount', 'valid_from', 'valid_until', 'blocked'
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -459,7 +469,7 @@ class OrderPositionSerializer(I18nAwareModelSerializer):
|
||||
|
||||
class RequireAttentionField(serializers.Field):
|
||||
def to_representation(self, instance: OrderPosition):
|
||||
return instance.order.checkin_attention or instance.item.checkin_attention
|
||||
return instance.require_checkin_attention
|
||||
|
||||
|
||||
class AttendeeNameField(serializers.Field):
|
||||
@@ -504,7 +514,7 @@ class CheckinListOrderPositionSerializer(OrderPositionSerializer):
|
||||
'company', 'street', 'zipcode', 'city', 'country', 'state',
|
||||
'attendee_email', 'voucher', 'tax_rate', 'tax_value', 'secret', 'addon_to', 'subevent', 'checkins',
|
||||
'downloads', 'answers', 'tax_rule', 'pseudonymization_id', 'pdf_data', 'seat', 'require_attention',
|
||||
'order__status')
|
||||
'order__status', 'valid_from', 'valid_until', 'blocked')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
@@ -618,7 +628,7 @@ class OrderSerializer(I18nAwareModelSerializer):
|
||||
'code', 'status', 'testmode', 'secret', 'email', 'phone', 'locale', 'datetime', 'expires', 'payment_date',
|
||||
'payment_provider', 'fees', 'total', 'comment', 'custom_followup_at', 'invoice_address', 'positions', 'downloads',
|
||||
'checkin_attention', 'last_modified', 'payments', 'refunds', 'require_approval', 'sales_channel',
|
||||
'url', 'customer'
|
||||
'url', 'customer', 'valid_if_pending'
|
||||
)
|
||||
read_only_fields = (
|
||||
'code', 'status', 'testmode', 'secret', 'datetime', 'expires', 'payment_date',
|
||||
@@ -636,7 +646,7 @@ class OrderSerializer(I18nAwareModelSerializer):
|
||||
for fname, field in list(self.fields.items()):
|
||||
if fname in includes:
|
||||
continue
|
||||
elif hasattr(field, 'child'):
|
||||
elif hasattr(field, 'child'): # Nested list serializers
|
||||
found_any = False
|
||||
for childfname, childfield in list(field.child.fields.items()):
|
||||
if f'{fname}.{childfname}' not in includes:
|
||||
@@ -645,6 +655,15 @@ class OrderSerializer(I18nAwareModelSerializer):
|
||||
found_any = True
|
||||
if not found_any:
|
||||
self.fields.pop(fname)
|
||||
elif isinstance(field, serializers.Serializer): # Nested serializers
|
||||
found_any = False
|
||||
for childfname, childfield in list(field.fields.items()):
|
||||
if f'{fname}.{childfname}' not in includes:
|
||||
field.fields.pop(childfname)
|
||||
else:
|
||||
found_any = True
|
||||
if not found_any:
|
||||
self.fields.pop(fname)
|
||||
else:
|
||||
self.fields.pop(fname)
|
||||
|
||||
@@ -664,7 +683,8 @@ class OrderSerializer(I18nAwareModelSerializer):
|
||||
def update(self, instance, validated_data):
|
||||
# Even though all fields that shouldn't be edited are marked as read_only in the serializer
|
||||
# (hopefully), we'll be extra careful here and be explicit about the model fields we update.
|
||||
update_fields = ['comment', 'custom_followup_at', 'checkin_attention', 'email', 'locale', 'phone']
|
||||
update_fields = ['comment', 'custom_followup_at', 'checkin_attention', 'email', 'locale', 'phone',
|
||||
'valid_if_pending']
|
||||
|
||||
if 'invoice_address' in validated_data:
|
||||
iadata = validated_data.pop('invoice_address')
|
||||
@@ -765,12 +785,14 @@ class OrderPositionCreateSerializer(I18nAwareModelSerializer):
|
||||
voucher = serializers.SlugRelatedField(slug_field='code', queryset=Voucher.objects.none(),
|
||||
required=False, allow_null=True)
|
||||
country = CompatibleCountryField(source='*')
|
||||
requested_valid_from = serializers.DateTimeField(required=False, allow_null=True)
|
||||
|
||||
class Meta:
|
||||
model = OrderPosition
|
||||
fields = ('positionid', 'item', 'variation', 'price', 'attendee_name', 'attendee_name_parts', 'attendee_email',
|
||||
'company', 'street', 'zipcode', 'city', 'country', 'state', 'is_bundled',
|
||||
'secret', 'addon_to', 'subevent', 'answers', 'seat', 'voucher')
|
||||
'secret', 'addon_to', 'subevent', 'answers', 'seat', 'voucher', 'valid_from', 'valid_until',
|
||||
'requested_valid_from')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
@@ -832,6 +854,10 @@ class OrderPositionCreateSerializer(I18nAwareModelSerializer):
|
||||
raise ValidationError(
|
||||
{'attendee_name': ['Do not specify attendee_name if you specified attendee_name_parts.']}
|
||||
)
|
||||
|
||||
if data.get('attendee_name_parts') and not isinstance(data.get('attendee_name_parts'), dict):
|
||||
raise ValidationError({'attendee_name_parts': ['Invalid data type']})
|
||||
|
||||
if data.get('attendee_name_parts') and '_scheme' not in data.get('attendee_name_parts'):
|
||||
data['attendee_name_parts']['_scheme'] = self.context['request'].event.settings.name_scheme
|
||||
|
||||
@@ -924,7 +950,8 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
|
||||
model = Order
|
||||
fields = ('code', 'status', 'testmode', 'email', 'phone', 'locale', 'payment_provider', 'fees', 'comment', 'sales_channel',
|
||||
'invoice_address', 'positions', 'checkin_attention', 'payment_info', 'payment_date', 'consume_carts',
|
||||
'force', 'send_email', 'simulate', 'customer', 'custom_followup_at', 'require_approval')
|
||||
'force', 'send_email', 'simulate', 'customer', 'custom_followup_at', 'require_approval',
|
||||
'valid_if_pending')
|
||||
|
||||
def validate_payment_provider(self, pp):
|
||||
if pp is None:
|
||||
@@ -1152,6 +1179,20 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
|
||||
elif seated:
|
||||
errs[i]['seat'] = ['The specified product requires to choose a seat.']
|
||||
|
||||
requested_valid_from = pos_data.pop('requested_valid_from', None)
|
||||
if 'valid_from' not in pos_data and 'valid_until' not in pos_data:
|
||||
valid_from, valid_until = pos_data['item'].compute_validity(
|
||||
requested_start=(
|
||||
max(requested_valid_from, now())
|
||||
if requested_valid_from and pos_data['item'].validity_dynamic_start_choice
|
||||
else now()
|
||||
),
|
||||
enforce_start_limit=True,
|
||||
override_tz=self.context['event'].timezone,
|
||||
)
|
||||
pos_data['valid_from'] = valid_from
|
||||
pos_data['valid_until'] = valid_until
|
||||
|
||||
if not force:
|
||||
for i, pos_data in enumerate(positions_data):
|
||||
if pos_data.get('voucher'):
|
||||
@@ -1467,9 +1508,9 @@ class InvoiceSerializer(I18nAwareModelSerializer):
|
||||
'invoice_to', 'invoice_to_company', 'invoice_to_name', 'invoice_to_street', 'invoice_to_zipcode',
|
||||
'invoice_to_city', 'invoice_to_state', 'invoice_to_country', 'invoice_to_vat_id', 'invoice_to_beneficiary',
|
||||
'custom_field', 'date', 'refers', 'locale',
|
||||
'introductory_text', 'additional_text', 'payment_provider_text', 'footer_text', 'lines',
|
||||
'foreign_currency_display', 'foreign_currency_rate', 'foreign_currency_rate_date',
|
||||
'internal_reference')
|
||||
'introductory_text', 'additional_text', 'payment_provider_text', 'payment_provider_stamp',
|
||||
'footer_text', 'lines', 'foreign_currency_display', 'foreign_currency_rate',
|
||||
'foreign_currency_rate_date', 'internal_reference')
|
||||
|
||||
|
||||
class OrderPaymentCreateSerializer(I18nAwareModelSerializer):
|
||||
@@ -1515,3 +1556,10 @@ class RevokedTicketSecretSerializer(I18nAwareModelSerializer):
|
||||
class Meta:
|
||||
model = RevokedTicketSecret
|
||||
fields = ('id', 'secret', 'created')
|
||||
|
||||
|
||||
class BlockedTicketSecretSerializer(I18nAwareModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = BlockedTicketSecret
|
||||
fields = ('id', 'secret', 'updated', 'blocked')
|
||||
|
||||
@@ -24,6 +24,7 @@ import os
|
||||
|
||||
import pycountry
|
||||
from django.core.files import File
|
||||
from django.core.validators import RegexValidator
|
||||
from rest_framework import serializers
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
@@ -53,7 +54,7 @@ class OrderPositionCreateForExistingOrderSerializer(OrderPositionCreateSerialize
|
||||
model = OrderPosition
|
||||
fields = ('order', 'item', 'variation', 'price', 'attendee_name', 'attendee_name_parts', 'attendee_email',
|
||||
'company', 'street', 'zipcode', 'city', 'country', 'state',
|
||||
'secret', 'addon_to', 'subevent', 'answers', 'seat')
|
||||
'secret', 'addon_to', 'subevent', 'answers', 'seat', 'valid_from', 'valid_until')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
@@ -89,6 +90,8 @@ class OrderPositionCreateForExistingOrderSerializer(OrderPositionCreateSerialize
|
||||
addon_to=validated_data.get('addon_to'),
|
||||
subevent=validated_data.get('subevent'),
|
||||
seat=validated_data.get('seat'),
|
||||
valid_from=validated_data.get('valid_from'),
|
||||
valid_until=validated_data.get('valid_until'),
|
||||
)
|
||||
if self.context.get('commit', True):
|
||||
ocm.commit()
|
||||
@@ -158,12 +161,14 @@ class OrderPositionInfoPatchSerializer(serializers.ModelSerializer):
|
||||
a.question_id: a for a in instance.answers.all()
|
||||
}
|
||||
for answ_data in answers_data:
|
||||
if not answ_data.get('answer'):
|
||||
continue
|
||||
options = answ_data.pop('options', [])
|
||||
if answ_data['question'].pk in qs_seen:
|
||||
raise ValidationError(f'Question {answ_data["question"]} was sent twice.')
|
||||
if answ_data['question'].pk in answercache:
|
||||
a = answercache[answ_data['question'].pk]
|
||||
if isinstance(answ_data['answer'], File):
|
||||
if isinstance(answ_data.get('answer'), File):
|
||||
a.file.save(answ_data['answer'].name, answ_data['answer'], save=False)
|
||||
a.answer = 'file://' + a.file.name
|
||||
elif a.answer.startswith('file://') and answ_data['answer'] == "file:keep":
|
||||
@@ -173,7 +178,7 @@ class OrderPositionInfoPatchSerializer(serializers.ModelSerializer):
|
||||
setattr(a, attr, value)
|
||||
a.save()
|
||||
else:
|
||||
if isinstance(answ_data['answer'], File):
|
||||
if isinstance(answ_data.get('answer'), File):
|
||||
an = answ_data.pop('answer')
|
||||
a = instance.answers.create(**answ_data, answer='')
|
||||
a.file.save(os.path.basename(an.name), an, save=False)
|
||||
@@ -196,7 +201,7 @@ class OrderPositionChangeSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = OrderPosition
|
||||
fields = (
|
||||
'item', 'variation', 'subevent', 'seat', 'price', 'tax_rule',
|
||||
'item', 'variation', 'subevent', 'seat', 'price', 'tax_rule', 'valid_from', 'valid_until'
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -262,6 +267,8 @@ class OrderPositionChangeSerializer(serializers.ModelSerializer):
|
||||
price = validated_data.get('price', instance.price)
|
||||
seat = validated_data.get('seat', current_seat)
|
||||
tax_rule = validated_data.get('tax_rule', instance.tax_rule)
|
||||
valid_from = validated_data.get('valid_from', instance.valid_from)
|
||||
valid_until = validated_data.get('valid_until', instance.valid_until)
|
||||
|
||||
change_item = None
|
||||
if item != instance.item or variation != instance.variation:
|
||||
@@ -288,6 +295,12 @@ class OrderPositionChangeSerializer(serializers.ModelSerializer):
|
||||
if tax_rule != instance.tax_rule:
|
||||
ocm.change_tax_rule(instance, tax_rule)
|
||||
|
||||
if valid_from != instance.valid_from:
|
||||
ocm.change_valid_from(instance, valid_from)
|
||||
|
||||
if valid_until != instance.valid_until:
|
||||
ocm.change_valid_until(instance, valid_until)
|
||||
|
||||
if self.context.get('commit', True):
|
||||
ocm.commit()
|
||||
instance.refresh_from_db()
|
||||
@@ -421,3 +434,7 @@ class OrderChangeOperationSerializer(serializers.Serializer):
|
||||
seen_positions.add(d['fee'])
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class BlockNameSerializer(serializers.Serializer):
|
||||
name = serializers.CharField(validators=[RegexValidator('^(admin|api:[a-zA-Z0-9._]+)$')])
|
||||
|
||||
@@ -41,15 +41,21 @@ from pretix.base.models import (
|
||||
from pretix.base.models.seating import SeatingPlanLayoutValidator
|
||||
from pretix.base.services.mail import SendMailException, mail
|
||||
from pretix.base.settings import validate_organizer_settings
|
||||
from pretix.helpers.urls import build_absolute_uri
|
||||
from pretix.helpers.urls import build_absolute_uri as build_global_uri
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class OrganizerSerializer(I18nAwareModelSerializer):
|
||||
public_url = serializers.SerializerMethodField('get_organizer_url', read_only=True)
|
||||
|
||||
def get_organizer_url(self, organizer):
|
||||
return build_absolute_uri(organizer, 'presale:organizer.index')
|
||||
|
||||
class Meta:
|
||||
model = Organizer
|
||||
fields = ('name', 'slug')
|
||||
fields = ('name', 'slug', 'public_url')
|
||||
|
||||
|
||||
class SeatingPlanSerializer(I18nAwareModelSerializer):
|
||||
@@ -79,6 +85,13 @@ class CustomerSerializer(I18nAwareModelSerializer):
|
||||
validated_data['external_identifier'] = instance.external_identifier
|
||||
return super().update(instance, validated_data)
|
||||
|
||||
def validate(self, data):
|
||||
if data.get('name_parts') and not isinstance(data.get('name_parts'), dict):
|
||||
raise ValidationError({'name_parts': ['Invalid data type']})
|
||||
if data.get('name_parts') and '_scheme' not in data.get('name_parts'):
|
||||
data['name_parts']['_scheme'] = self.context['request'].organizer.settings.name_scheme
|
||||
return data
|
||||
|
||||
|
||||
class CustomerCreateSerializer(CustomerSerializer):
|
||||
send_email = serializers.BooleanField(default=False, required=False, allow_null=True)
|
||||
@@ -220,7 +233,7 @@ class TeamInviteSerializer(serializers.ModelSerializer):
|
||||
'user': self,
|
||||
'organizer': self.context['organizer'].name,
|
||||
'team': instance.team.name,
|
||||
'url': build_absolute_uri('control:auth.invite', kwargs={
|
||||
'url': build_global_uri('control:auth.invite', kwargs={
|
||||
'token': instance.token
|
||||
})
|
||||
},
|
||||
|
||||
@@ -55,7 +55,7 @@ class WebHookSerializer(I18nAwareModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = WebHook
|
||||
fields = ('id', 'enabled', 'target_url', 'all_events', 'limit_events', 'action_types')
|
||||
fields = ('id', 'enabled', 'target_url', 'all_events', 'limit_events', 'action_types', 'comment')
|
||||
|
||||
def validate(self, data):
|
||||
data = super().validate(data)
|
||||
|
||||
@@ -35,7 +35,8 @@
|
||||
import importlib
|
||||
|
||||
from django.apps import apps
|
||||
from django.conf.urls import include, re_path
|
||||
from django.conf.urls import re_path
|
||||
from django.urls import include
|
||||
from rest_framework import routers
|
||||
|
||||
from pretix.api.views import cart
|
||||
@@ -80,6 +81,7 @@ event_router.register(r'orders', order.OrderViewSet)
|
||||
event_router.register(r'orderpositions', order.OrderPositionViewSet)
|
||||
event_router.register(r'invoices', order.InvoiceViewSet)
|
||||
event_router.register(r'revokedsecrets', order.RevokedSecretViewSet, basename='revokedsecrets')
|
||||
event_router.register(r'blockedsecrets', order.BlockedSecretViewSet, basename='blockedsecrets')
|
||||
event_router.register(r'taxrules', event.TaxRuleViewSet)
|
||||
event_router.register(r'waitinglistentries', waitinglist.WaitingListViewSet)
|
||||
event_router.register(r'checkinlists', checkin.CheckinListViewSet)
|
||||
|
||||
@@ -24,10 +24,11 @@ from calendar import timegm
|
||||
from django.db.models import Max
|
||||
from django.http import HttpResponse
|
||||
from django.utils.http import http_date, parse_http_date_safe
|
||||
from rest_framework.filters import OrderingFilter
|
||||
|
||||
from pretix.api.pagination import TotalOrderingFilter
|
||||
|
||||
|
||||
class RichOrderingFilter(OrderingFilter):
|
||||
class RichOrderingFilter(TotalOrderingFilter):
|
||||
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
ordering = self.get_ordering(request, queryset, view)
|
||||
|
||||
@@ -29,11 +29,11 @@ from django.utils.translation import gettext as _
|
||||
from rest_framework import status, viewsets
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.filters import OrderingFilter
|
||||
from rest_framework.mixins import CreateModelMixin, DestroyModelMixin
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.serializers import as_serializer_error
|
||||
|
||||
from pretix.api.pagination import TotalOrderingFilter
|
||||
from pretix.api.serializers.cart import (
|
||||
CartPositionCreateSerializer, CartPositionSerializer,
|
||||
)
|
||||
@@ -47,7 +47,7 @@ from pretix.base.services.locking import NoLockManager
|
||||
class CartPositionViewSet(CreateModelMixin, DestroyModelMixin, viewsets.ReadOnlyModelViewSet):
|
||||
serializer_class = CartPositionSerializer
|
||||
queryset = CartPosition.objects.none()
|
||||
filter_backends = (OrderingFilter,)
|
||||
filter_backends = (TotalOrderingFilter,)
|
||||
ordering = ('datetime',)
|
||||
ordering_fields = ('datetime', 'cart_id')
|
||||
lookup_field = 'id'
|
||||
|
||||
@@ -93,8 +93,10 @@ with scopes_disabled():
|
||||
class CheckinListViewSet(viewsets.ModelViewSet):
|
||||
serializer_class = CheckinListSerializer
|
||||
queryset = CheckinList.objects.none()
|
||||
filter_backends = (DjangoFilterBackend,)
|
||||
filter_backends = (DjangoFilterBackend, RichOrderingFilter)
|
||||
filterset_class = CheckinListFilter
|
||||
ordering = ('subevent__date_from', 'name', 'id')
|
||||
ordering_fields = ('subevent__date_from', 'id', 'name',)
|
||||
|
||||
def _get_permission_name(self, request):
|
||||
if request.path.endswith('/failed_checkins/'):
|
||||
@@ -271,7 +273,13 @@ with scopes_disabled():
|
||||
def check_rules_qs(self, queryset, name, value):
|
||||
if not self.checkinlist.rules:
|
||||
return queryset
|
||||
return queryset.filter(SQLLogic(self.checkinlist).apply(self.checkinlist.rules))
|
||||
return queryset.filter(
|
||||
SQLLogic(self.checkinlist).apply(self.checkinlist.rules)
|
||||
).filter(
|
||||
Q(valid_from__isnull=True) | Q(valid_from__lte=now()),
|
||||
Q(valid_until__isnull=True) | Q(valid_until__gte=now()),
|
||||
blocked__isnull=True,
|
||||
)
|
||||
|
||||
|
||||
def _handle_file_upload(data, user, auth):
|
||||
@@ -323,7 +331,13 @@ def _checkin_list_position_queryset(checkinlists, ignore_status=False, ignore_pr
|
||||
if checkinlist.subevent:
|
||||
list_q &= Q(subevent=checkinlist.subevent)
|
||||
if not ignore_status:
|
||||
list_q &= Q(order__status__in=[Order.STATUS_PAID, Order.STATUS_PENDING] if checkinlist.include_pending else [Order.STATUS_PAID])
|
||||
if checkinlist.include_pending:
|
||||
list_q &= Q(order__status__in=[Order.STATUS_PAID, Order.STATUS_PENDING])
|
||||
else:
|
||||
list_q &= Q(
|
||||
Q(order__status=Order.STATUS_PAID) |
|
||||
Q(order__status=Order.STATUS_PENDING, order__valid_if_pending=True)
|
||||
)
|
||||
if not checkinlist.all_products and not ignore_products:
|
||||
list_q &= Q(item__in=checkinlist.limit_products.values_list('id', flat=True))
|
||||
lists_qs.append(list_q)
|
||||
@@ -580,7 +594,7 @@ def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force,
|
||||
'status': 'error',
|
||||
'reason': Checkin.REASON_AMBIGUOUS,
|
||||
'reason_explanation': None,
|
||||
'require_attention': op.item.checkin_attention or op.order.checkin_attention,
|
||||
'require_attention': op.require_checkin_attention,
|
||||
'position': CheckinListOrderPositionSerializer(op, context=_make_context(context, op.order.event)).data,
|
||||
'list': MiniCheckinListSerializer(list_by_event[op.order.event_id]).data,
|
||||
}, status=400)
|
||||
@@ -625,7 +639,7 @@ def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force,
|
||||
except RequiredQuestionsError as e:
|
||||
return Response({
|
||||
'status': 'incomplete',
|
||||
'require_attention': op.item.checkin_attention or op.order.checkin_attention,
|
||||
'require_attention': op.require_checkin_attention,
|
||||
'position': CheckinListOrderPositionSerializer(op, context=_make_context(context, op.order.event)).data,
|
||||
'questions': [
|
||||
QuestionSerializer(q).data for q in e.questions
|
||||
@@ -654,14 +668,14 @@ def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force,
|
||||
'status': 'error',
|
||||
'reason': e.code,
|
||||
'reason_explanation': e.reason,
|
||||
'require_attention': op.item.checkin_attention or op.order.checkin_attention,
|
||||
'require_attention': op.require_checkin_attention,
|
||||
'position': CheckinListOrderPositionSerializer(op, context=_make_context(context, op.order.event)).data,
|
||||
'list': MiniCheckinListSerializer(list_by_event[op.order.event_id]).data,
|
||||
}, status=400)
|
||||
else:
|
||||
return Response({
|
||||
'status': 'ok',
|
||||
'require_attention': op.item.checkin_attention or op.order.checkin_attention,
|
||||
'require_attention': op.require_checkin_attention,
|
||||
'position': CheckinListOrderPositionSerializer(op, context=_make_context(context, op.order.event)).data,
|
||||
'list': MiniCheckinListSerializer(list_by_event[op.order.event_id]).data,
|
||||
}, status=201)
|
||||
@@ -682,7 +696,7 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
serializer_class = CheckinListOrderPositionSerializer
|
||||
queryset = OrderPosition.all.none()
|
||||
filter_backends = (ExtendedBackend, RichOrderingFilter)
|
||||
ordering = (F('attendee_name_cached').asc(nulls_last=True), 'positionid')
|
||||
ordering = (F('attendee_name_cached').asc(nulls_last=True), 'pk')
|
||||
ordering_fields = (
|
||||
'order__code', 'order__datetime', 'positionid', 'attendee_name',
|
||||
'last_checked_in', 'order__email',
|
||||
|
||||
@@ -36,8 +36,8 @@ from django_filters.rest_framework import DjangoFilterBackend, FilterSet
|
||||
from django_scopes import scopes_disabled
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
from rest_framework.filters import OrderingFilter
|
||||
|
||||
from pretix.api.pagination import TotalOrderingFilter
|
||||
from pretix.api.serializers.discount import DiscountSerializer
|
||||
from pretix.api.views import ConditionalListView
|
||||
from pretix.base.models import CartPosition, Discount
|
||||
@@ -52,7 +52,7 @@ with scopes_disabled():
|
||||
class DiscountViewSet(ConditionalListView, viewsets.ModelViewSet):
|
||||
serializer_class = DiscountSerializer
|
||||
queryset = Discount.objects.none()
|
||||
filter_backends = (DjangoFilterBackend, OrderingFilter)
|
||||
filter_backends = (DjangoFilterBackend, TotalOrderingFilter)
|
||||
filterset_class = DiscountFilter
|
||||
ordering_fields = ('id', 'position')
|
||||
ordering = ('position', 'id')
|
||||
|
||||
@@ -39,11 +39,12 @@ from django.db.models import Prefetch, ProtectedError, Q
|
||||
from django.utils.timezone import now
|
||||
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
|
||||
from django_scopes import scopes_disabled
|
||||
from rest_framework import filters, serializers, views, viewsets
|
||||
from rest_framework import serializers, views, viewsets
|
||||
from rest_framework.exceptions import PermissionDenied, ValidationError
|
||||
from rest_framework.response import Response
|
||||
|
||||
from pretix.api.auth.permission import EventCRUDPermission
|
||||
from pretix.api.pagination import TotalOrderingFilter
|
||||
from pretix.api.serializers.event import (
|
||||
CloneEventSerializer, DeviceEventSettingsSerializer, EventSerializer,
|
||||
EventSettingsSerializer, SubEventSerializer, TaxRuleSerializer,
|
||||
@@ -127,7 +128,7 @@ class EventViewSet(viewsets.ModelViewSet):
|
||||
lookup_url_kwarg = 'event'
|
||||
lookup_value_regex = '[^/]+'
|
||||
permission_classes = (EventCRUDPermission,)
|
||||
filter_backends = (DjangoFilterBackend, filters.OrderingFilter)
|
||||
filter_backends = (DjangoFilterBackend, TotalOrderingFilter)
|
||||
ordering = ('slug',)
|
||||
ordering_fields = ('date_from', 'slug')
|
||||
filterset_class = EventFilter
|
||||
@@ -379,7 +380,7 @@ class SubEventViewSet(ConditionalListView, viewsets.ModelViewSet):
|
||||
serializer_class = SubEventSerializer
|
||||
queryset = SubEvent.objects.none()
|
||||
write_permission = 'can_change_event_settings'
|
||||
filter_backends = (DjangoFilterBackend, filters.OrderingFilter)
|
||||
filter_backends = (DjangoFilterBackend, TotalOrderingFilter)
|
||||
filterset_class = SubEventFilter
|
||||
ordering = ('date_from',)
|
||||
ordering_fields = ('id', 'date_from', 'last_modified')
|
||||
|
||||
@@ -41,9 +41,9 @@ from django_scopes import scopes_disabled
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
from rest_framework.filters import OrderingFilter
|
||||
from rest_framework.response import Response
|
||||
|
||||
from pretix.api.pagination import TotalOrderingFilter
|
||||
from pretix.api.serializers.item import (
|
||||
ItemAddOnSerializer, ItemBundleSerializer, ItemCategorySerializer,
|
||||
ItemSerializer, ItemVariationSerializer, QuestionOptionSerializer,
|
||||
@@ -75,7 +75,7 @@ with scopes_disabled():
|
||||
class ItemViewSet(ConditionalListView, viewsets.ModelViewSet):
|
||||
serializer_class = ItemSerializer
|
||||
queryset = Item.objects.none()
|
||||
filter_backends = (DjangoFilterBackend, OrderingFilter)
|
||||
filter_backends = (DjangoFilterBackend, TotalOrderingFilter)
|
||||
ordering_fields = ('id', 'position')
|
||||
ordering = ('position', 'id')
|
||||
filterset_class = ItemFilter
|
||||
@@ -138,7 +138,7 @@ class ItemViewSet(ConditionalListView, viewsets.ModelViewSet):
|
||||
class ItemVariationViewSet(viewsets.ModelViewSet):
|
||||
serializer_class = ItemVariationSerializer
|
||||
queryset = ItemVariation.objects.none()
|
||||
filter_backends = (DjangoFilterBackend, OrderingFilter,)
|
||||
filter_backends = (DjangoFilterBackend, TotalOrderingFilter,)
|
||||
ordering_fields = ('id', 'position')
|
||||
ordering = ('id',)
|
||||
permission = None
|
||||
@@ -208,7 +208,7 @@ class ItemVariationViewSet(viewsets.ModelViewSet):
|
||||
class ItemBundleViewSet(viewsets.ModelViewSet):
|
||||
serializer_class = ItemBundleSerializer
|
||||
queryset = ItemBundle.objects.none()
|
||||
filter_backends = (DjangoFilterBackend, OrderingFilter,)
|
||||
filter_backends = (DjangoFilterBackend, TotalOrderingFilter,)
|
||||
ordering_fields = ('id',)
|
||||
ordering = ('id',)
|
||||
permission = None
|
||||
@@ -260,7 +260,7 @@ class ItemBundleViewSet(viewsets.ModelViewSet):
|
||||
class ItemAddOnViewSet(viewsets.ModelViewSet):
|
||||
serializer_class = ItemAddOnSerializer
|
||||
queryset = ItemAddOn.objects.none()
|
||||
filter_backends = (DjangoFilterBackend, OrderingFilter,)
|
||||
filter_backends = (DjangoFilterBackend, TotalOrderingFilter,)
|
||||
ordering_fields = ('id', 'position')
|
||||
ordering = ('id',)
|
||||
permission = None
|
||||
@@ -318,7 +318,7 @@ class ItemCategoryFilter(FilterSet):
|
||||
class ItemCategoryViewSet(ConditionalListView, viewsets.ModelViewSet):
|
||||
serializer_class = ItemCategorySerializer
|
||||
queryset = ItemCategory.objects.none()
|
||||
filter_backends = (DjangoFilterBackend, OrderingFilter)
|
||||
filter_backends = (DjangoFilterBackend, TotalOrderingFilter)
|
||||
filterset_class = ItemCategoryFilter
|
||||
ordering_fields = ('id', 'position')
|
||||
ordering = ('position', 'id')
|
||||
@@ -373,7 +373,7 @@ with scopes_disabled():
|
||||
class QuestionViewSet(ConditionalListView, viewsets.ModelViewSet):
|
||||
serializer_class = QuestionSerializer
|
||||
queryset = Question.objects.none()
|
||||
filter_backends = (DjangoFilterBackend, OrderingFilter)
|
||||
filter_backends = (DjangoFilterBackend, TotalOrderingFilter)
|
||||
filterset_class = QuestionFilter
|
||||
ordering_fields = ('id', 'position')
|
||||
ordering = ('position', 'id')
|
||||
@@ -418,7 +418,7 @@ class QuestionViewSet(ConditionalListView, viewsets.ModelViewSet):
|
||||
class QuestionOptionViewSet(viewsets.ModelViewSet):
|
||||
serializer_class = QuestionOptionSerializer
|
||||
queryset = QuestionOption.objects.none()
|
||||
filter_backends = (DjangoFilterBackend, OrderingFilter,)
|
||||
filter_backends = (DjangoFilterBackend, TotalOrderingFilter,)
|
||||
ordering_fields = ('id', 'position')
|
||||
ordering = ('position',)
|
||||
permission = None
|
||||
@@ -475,7 +475,7 @@ with scopes_disabled():
|
||||
class QuotaViewSet(ConditionalListView, viewsets.ModelViewSet):
|
||||
serializer_class = QuotaSerializer
|
||||
queryset = Quota.objects.none()
|
||||
filter_backends = (DjangoFilterBackend, OrderingFilter,)
|
||||
filter_backends = (DjangoFilterBackend, TotalOrderingFilter,)
|
||||
filterset_class = QuotaFilter
|
||||
ordering_fields = ('id', 'size')
|
||||
ordering = ('id',)
|
||||
|
||||
@@ -43,21 +43,21 @@ from rest_framework.decorators import action
|
||||
from rest_framework.exceptions import (
|
||||
APIException, NotFound, PermissionDenied, ValidationError,
|
||||
)
|
||||
from rest_framework.filters import OrderingFilter
|
||||
from rest_framework.mixins import CreateModelMixin
|
||||
from rest_framework.response import Response
|
||||
|
||||
from pretix.api.models import OAuthAccessToken
|
||||
from pretix.api.pagination import TotalOrderingFilter
|
||||
from pretix.api.serializers.order import (
|
||||
InvoiceSerializer, OrderCreateSerializer, OrderPaymentCreateSerializer,
|
||||
OrderPaymentSerializer, OrderPositionSerializer,
|
||||
OrderRefundCreateSerializer, OrderRefundSerializer, OrderSerializer,
|
||||
PriceCalcSerializer, RevokedTicketSecretSerializer,
|
||||
SimulatedOrderSerializer,
|
||||
BlockedTicketSecretSerializer, InvoiceSerializer, OrderCreateSerializer,
|
||||
OrderPaymentCreateSerializer, OrderPaymentSerializer,
|
||||
OrderPositionSerializer, OrderRefundCreateSerializer,
|
||||
OrderRefundSerializer, OrderSerializer, PriceCalcSerializer,
|
||||
RevokedTicketSecretSerializer, SimulatedOrderSerializer,
|
||||
)
|
||||
from pretix.api.serializers.orderchange import (
|
||||
OrderChangeOperationSerializer, OrderFeeChangeSerializer,
|
||||
OrderPositionChangeSerializer,
|
||||
BlockNameSerializer, OrderChangeOperationSerializer,
|
||||
OrderFeeChangeSerializer, OrderPositionChangeSerializer,
|
||||
OrderPositionCreateForExistingOrderSerializer,
|
||||
OrderPositionInfoPatchSerializer,
|
||||
)
|
||||
@@ -70,7 +70,9 @@ from pretix.base.models import (
|
||||
OrderRefund, Quota, SubEvent, SubEventMetaValue, TaxRule, TeamAPIToken,
|
||||
generate_secret,
|
||||
)
|
||||
from pretix.base.models.orders import QuestionAnswer, RevokedTicketSecret
|
||||
from pretix.base.models.orders import (
|
||||
BlockedTicketSecret, QuestionAnswer, RevokedTicketSecret,
|
||||
)
|
||||
from pretix.base.payment import PaymentException
|
||||
from pretix.base.pdf import get_images
|
||||
from pretix.base.secrets import assign_ticket_secret
|
||||
@@ -103,9 +105,9 @@ with scopes_disabled():
|
||||
subevent_after = django_filters.IsoDateTimeFilter(method='subevent_after_qs')
|
||||
subevent_before = django_filters.IsoDateTimeFilter(method='subevent_before_qs')
|
||||
search = django_filters.CharFilter(method='search_qs')
|
||||
item = django_filters.CharFilter(field_name='all_positions', lookup_expr='item_id')
|
||||
variation = django_filters.CharFilter(field_name='all_positions', lookup_expr='variation_id')
|
||||
subevent = django_filters.CharFilter(field_name='all_positions', lookup_expr='subevent_id')
|
||||
item = django_filters.CharFilter(field_name='all_positions', lookup_expr='item_id', distinct=True)
|
||||
variation = django_filters.CharFilter(field_name='all_positions', lookup_expr='variation_id', distinct=True)
|
||||
subevent = django_filters.CharFilter(field_name='all_positions', lookup_expr='subevent_id', distinct=True)
|
||||
|
||||
class Meta:
|
||||
model = Order
|
||||
@@ -179,7 +181,7 @@ with scopes_disabled():
|
||||
class OrderViewSet(viewsets.ModelViewSet):
|
||||
serializer_class = OrderSerializer
|
||||
queryset = Order.objects.none()
|
||||
filter_backends = (DjangoFilterBackend, OrderingFilter)
|
||||
filter_backends = (DjangoFilterBackend, TotalOrderingFilter)
|
||||
ordering = ('datetime',)
|
||||
ordering_fields = ('datetime', 'code', 'status', 'last_modified')
|
||||
filterset_class = OrderFilter
|
||||
@@ -287,7 +289,7 @@ class OrderViewSet(viewsets.ModelViewSet):
|
||||
if order.status in (Order.STATUS_CANCELED, Order.STATUS_EXPIRED):
|
||||
raise PermissionDenied("Downloads are not available for canceled or expired orders.")
|
||||
|
||||
if order.status == Order.STATUS_PENDING and not request.event.settings.ticket_download_pending:
|
||||
if order.status == Order.STATUS_PENDING and not (order.valid_if_pending or request.event.settings.ticket_download_pending):
|
||||
raise PermissionDenied("Downloads are not available for pending orders.")
|
||||
|
||||
ct = CachedCombinedTicket.objects.filter(
|
||||
@@ -763,6 +765,16 @@ class OrderViewSet(viewsets.ModelViewSet):
|
||||
}
|
||||
)
|
||||
|
||||
if 'valid_if_pending' in self.request.data and serializer.instance.valid_if_pending != self.request.data.get('valid_if_pending'):
|
||||
serializer.instance.log_action(
|
||||
'pretix.event.order.valid_if_pending',
|
||||
user=self.request.user,
|
||||
auth=self.request.auth,
|
||||
data={
|
||||
'new_value': self.request.data.get('valid_if_pending')
|
||||
}
|
||||
)
|
||||
|
||||
if 'email' in self.request.data and serializer.instance.email != self.request.data.get('email'):
|
||||
serializer.instance.email_known_to_work = False
|
||||
serializer.instance.log_action(
|
||||
@@ -1183,7 +1195,7 @@ class OrderPositionViewSet(viewsets.ModelViewSet):
|
||||
if pos.order.status in (Order.STATUS_CANCELED, Order.STATUS_EXPIRED):
|
||||
raise PermissionDenied("Downloads are not available for canceled or expired orders.")
|
||||
|
||||
if pos.order.status == Order.STATUS_PENDING and not request.event.settings.ticket_download_pending:
|
||||
if pos.order.status == Order.STATUS_PENDING and not (pos.order.valid_if_pending or request.event.settings.ticket_download_pending):
|
||||
raise PermissionDenied("Downloads are not available for pending orders.")
|
||||
if not pos.generate_ticket:
|
||||
raise PermissionDenied("Downloads are not enabled for this product.")
|
||||
@@ -1225,6 +1237,54 @@ class OrderPositionViewSet(viewsets.ModelViewSet):
|
||||
raise ValidationError(str(e))
|
||||
return self.retrieve(request, [], **kwargs)
|
||||
|
||||
@action(detail=True, methods=['POST'])
|
||||
def add_block(self, request, **kwargs):
|
||||
serializer = BlockNameSerializer(
|
||||
data=request.data,
|
||||
context=self.get_serializer_context(),
|
||||
)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
instance = self.get_object()
|
||||
try:
|
||||
ocm = OrderChangeManager(
|
||||
instance.order,
|
||||
user=self.request.user if self.request.user.is_authenticated else None,
|
||||
auth=self.request.auth,
|
||||
notify=False,
|
||||
reissue_invoice=False,
|
||||
)
|
||||
ocm.add_block(instance, serializer.validated_data['name'])
|
||||
ocm.commit()
|
||||
except OrderError as e:
|
||||
raise ValidationError(str(e))
|
||||
except Quota.QuotaExceededException as e:
|
||||
raise ValidationError(str(e))
|
||||
return self.retrieve(request, [], **kwargs)
|
||||
|
||||
@action(detail=True, methods=['POST'])
|
||||
def remove_block(self, request, **kwargs):
|
||||
serializer = BlockNameSerializer(
|
||||
data=request.data,
|
||||
context=self.get_serializer_context(),
|
||||
)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
instance = self.get_object()
|
||||
try:
|
||||
ocm = OrderChangeManager(
|
||||
instance.order,
|
||||
user=self.request.user if self.request.user.is_authenticated else None,
|
||||
auth=self.request.auth,
|
||||
notify=False,
|
||||
reissue_invoice=False,
|
||||
)
|
||||
ocm.remove_block(instance, serializer.validated_data['name'])
|
||||
ocm.commit()
|
||||
except OrderError as e:
|
||||
raise ValidationError(str(e))
|
||||
except Quota.QuotaExceededException as e:
|
||||
raise ValidationError(str(e))
|
||||
return self.retrieve(request, [], **kwargs)
|
||||
|
||||
def perform_destroy(self, instance):
|
||||
try:
|
||||
ocm = OrderChangeManager(
|
||||
@@ -1479,21 +1539,27 @@ class PaymentViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
|
||||
source=OrderRefund.REFUND_SOURCE_ADMIN,
|
||||
state=OrderRefund.REFUND_STATE_CREATED,
|
||||
amount=amount,
|
||||
provider=payment.provider
|
||||
provider=payment.provider,
|
||||
info='{}',
|
||||
)
|
||||
payment.order.log_action('pretix.event.order.refund.created', {
|
||||
'local_id': r.local_id,
|
||||
'provider': r.provider,
|
||||
}, user=self.request.user if self.request.user.is_authenticated else None, auth=self.request.auth)
|
||||
|
||||
try:
|
||||
r.payment_provider.execute_refund(r)
|
||||
except PaymentException as e:
|
||||
r.state = OrderRefund.REFUND_STATE_FAILED
|
||||
r.save()
|
||||
payment.order.log_action('pretix.event.order.refund.failed', {
|
||||
'local_id': r.local_id,
|
||||
'provider': r.provider,
|
||||
'error': str(e)
|
||||
})
|
||||
return Response({'detail': 'External error: {}'.format(str(e))},
|
||||
status=status.HTTP_400_BAD_REQUEST)
|
||||
else:
|
||||
payment.order.log_action('pretix.event.order.refund.created', {
|
||||
'local_id': r.local_id,
|
||||
'provider': r.provider,
|
||||
}, user=self.request.user if self.request.user.is_authenticated else None, auth=self.request.auth)
|
||||
if payment.order.pending_sum > 0:
|
||||
if mark_refunded:
|
||||
mark_order_refunded(payment.order,
|
||||
@@ -1683,7 +1749,7 @@ class RetryException(APIException):
|
||||
class InvoiceViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
serializer_class = InvoiceSerializer
|
||||
queryset = Invoice.objects.none()
|
||||
filter_backends = (DjangoFilterBackend, OrderingFilter)
|
||||
filter_backends = (DjangoFilterBackend, TotalOrderingFilter)
|
||||
ordering = ('nr',)
|
||||
ordering_fields = ('nr', 'date')
|
||||
filterset_class = InvoiceFilter
|
||||
@@ -1776,7 +1842,7 @@ with scopes_disabled():
|
||||
class RevokedSecretViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
serializer_class = RevokedTicketSecretSerializer
|
||||
queryset = RevokedTicketSecret.objects.none()
|
||||
filter_backends = (DjangoFilterBackend, OrderingFilter)
|
||||
filter_backends = (DjangoFilterBackend, TotalOrderingFilter)
|
||||
ordering = ('-created',)
|
||||
ordering_fields = ('created', 'secret')
|
||||
filterset_class = RevokedSecretFilter
|
||||
@@ -1785,3 +1851,25 @@ class RevokedSecretViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
|
||||
def get_queryset(self):
|
||||
return RevokedTicketSecret.objects.filter(event=self.request.event)
|
||||
|
||||
|
||||
with scopes_disabled():
|
||||
class BlockedSecretFilter(FilterSet):
|
||||
updated_since = django_filters.IsoDateTimeFilter(field_name='updated', lookup_expr='gte')
|
||||
|
||||
class Meta:
|
||||
model = BlockedTicketSecret
|
||||
fields = ['blocked']
|
||||
|
||||
|
||||
class BlockedSecretViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
serializer_class = BlockedTicketSecretSerializer
|
||||
queryset = BlockedTicketSecret.objects.none()
|
||||
filter_backends = (DjangoFilterBackend, TotalOrderingFilter)
|
||||
ordering = ('-updated', '-pk')
|
||||
filterset_class = BlockedSecretFilter
|
||||
permission = 'can_view_orders'
|
||||
write_permission = 'can_change_orders'
|
||||
|
||||
def get_queryset(self):
|
||||
return BlockedTicketSecret.objects.filter(event=self.request.event)
|
||||
|
||||
@@ -28,9 +28,7 @@ from django.shortcuts import get_object_or_404
|
||||
from django.utils.functional import cached_property
|
||||
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
|
||||
from django_scopes import scopes_disabled
|
||||
from rest_framework import (
|
||||
filters, mixins, serializers, status, views, viewsets,
|
||||
)
|
||||
from rest_framework import mixins, serializers, status, views, viewsets
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.exceptions import MethodNotAllowed, PermissionDenied
|
||||
from rest_framework.mixins import CreateModelMixin, DestroyModelMixin
|
||||
@@ -38,6 +36,7 @@ from rest_framework.response import Response
|
||||
from rest_framework.viewsets import GenericViewSet
|
||||
|
||||
from pretix.api.models import OAuthAccessToken
|
||||
from pretix.api.pagination import TotalOrderingFilter
|
||||
from pretix.api.serializers.organizer import (
|
||||
CustomerCreateSerializer, CustomerSerializer, DeviceSerializer,
|
||||
GiftCardSerializer, GiftCardTransactionSerializer, MembershipSerializer,
|
||||
@@ -51,6 +50,7 @@ from pretix.base.models import (
|
||||
User,
|
||||
)
|
||||
from pretix.base.settings import SETTINGS_AFFECTING_CSS
|
||||
from pretix.helpers import OF_SELF
|
||||
from pretix.helpers.dicts import merge_dicts
|
||||
from pretix.presale.style import regenerate_organizer_css
|
||||
|
||||
@@ -61,7 +61,7 @@ class OrganizerViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
lookup_field = 'slug'
|
||||
lookup_url_kwarg = 'organizer'
|
||||
lookup_value_regex = '[^/]+'
|
||||
filter_backends = (filters.OrderingFilter,)
|
||||
filter_backends = (TotalOrderingFilter,)
|
||||
ordering = ('slug',)
|
||||
ordering_fields = ('name', 'slug')
|
||||
|
||||
@@ -178,7 +178,7 @@ class GiftCardViewSet(viewsets.ModelViewSet):
|
||||
def perform_update(self, serializer):
|
||||
if 'include_accepted' in self.request.GET:
|
||||
raise PermissionDenied("Accepted gift cards cannot be updated, use transact instead.")
|
||||
GiftCard.objects.select_for_update().get(pk=self.get_object().pk)
|
||||
GiftCard.objects.select_for_update(of=OF_SELF).get(pk=self.get_object().pk)
|
||||
old_value = serializer.instance.value
|
||||
value = serializer.validated_data.pop('value')
|
||||
inst = serializer.save(secret=serializer.instance.secret, currency=serializer.instance.currency,
|
||||
@@ -196,7 +196,7 @@ class GiftCardViewSet(viewsets.ModelViewSet):
|
||||
@action(detail=True, methods=["POST"])
|
||||
@transaction.atomic()
|
||||
def transact(self, request, **kwargs):
|
||||
gc = GiftCard.objects.select_for_update().get(pk=self.get_object().pk)
|
||||
gc = GiftCard.objects.select_for_update(of=OF_SELF).get(pk=self.get_object().pk)
|
||||
value = serializers.DecimalField(max_digits=10, decimal_places=2).to_internal_value(
|
||||
request.data.get('value')
|
||||
)
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#
|
||||
import datetime
|
||||
|
||||
from django.core.exceptions import ValidationError as DjangoValidationError
|
||||
from django.utils.timezone import now
|
||||
from oauth2_provider.contrib.rest_framework import OAuth2Authentication
|
||||
from rest_framework.authentication import SessionAuthentication
|
||||
@@ -33,6 +34,9 @@ from pretix.api.auth.device import DeviceTokenAuthentication
|
||||
from pretix.api.auth.permission import AnyAuthenticatedClientPermission
|
||||
from pretix.api.auth.token import TeamTokenAuthentication
|
||||
from pretix.base.models import CachedFile
|
||||
from pretix.helpers.images import (
|
||||
IMAGE_TYPES, validate_uploaded_file_for_valid_image,
|
||||
)
|
||||
|
||||
ALLOWED_TYPES = {
|
||||
'image/gif': {'.gif'},
|
||||
@@ -61,6 +65,13 @@ class UploadView(APIView):
|
||||
name=file_obj.name,
|
||||
type=content_type
|
||||
))
|
||||
|
||||
if content_type in IMAGE_TYPES:
|
||||
try:
|
||||
validate_uploaded_file_for_valid_image(file_obj)
|
||||
except DjangoValidationError as e:
|
||||
raise ValidationError(e.message)
|
||||
|
||||
cf = CachedFile.objects.create(
|
||||
expires=now() + datetime.timedelta(days=1),
|
||||
date=now(),
|
||||
|
||||
@@ -31,9 +31,9 @@ from django_scopes import scopes_disabled
|
||||
from rest_framework import status, viewsets
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
from rest_framework.filters import OrderingFilter
|
||||
from rest_framework.response import Response
|
||||
|
||||
from pretix.api.pagination import TotalOrderingFilter
|
||||
from pretix.api.serializers.voucher import VoucherSerializer
|
||||
from pretix.base.models import Voucher
|
||||
|
||||
@@ -59,7 +59,7 @@ with scopes_disabled():
|
||||
class VoucherViewSet(viewsets.ModelViewSet):
|
||||
serializer_class = VoucherSerializer
|
||||
queryset = Voucher.objects.none()
|
||||
filter_backends = (DjangoFilterBackend, OrderingFilter)
|
||||
filter_backends = (DjangoFilterBackend, TotalOrderingFilter)
|
||||
ordering = ('id',)
|
||||
ordering_fields = ('id', 'code', 'max_usages', 'valid_until', 'value')
|
||||
filterset_class = VoucherFilter
|
||||
|
||||
@@ -25,9 +25,9 @@ from django_scopes import scopes_disabled
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.exceptions import PermissionDenied, ValidationError
|
||||
from rest_framework.filters import OrderingFilter
|
||||
from rest_framework.response import Response
|
||||
|
||||
from pretix.api.pagination import TotalOrderingFilter
|
||||
from pretix.api.serializers.waitinglist import WaitingListSerializer
|
||||
from pretix.base.models import WaitingListEntry
|
||||
from pretix.base.models.waitinglist import WaitingListException
|
||||
@@ -47,8 +47,8 @@ with scopes_disabled():
|
||||
class WaitingListViewSet(viewsets.ModelViewSet):
|
||||
serializer_class = WaitingListSerializer
|
||||
queryset = WaitingListEntry.objects.none()
|
||||
filter_backends = (DjangoFilterBackend, OrderingFilter)
|
||||
ordering = ('created',)
|
||||
filter_backends = (DjangoFilterBackend, TotalOrderingFilter)
|
||||
ordering = ('created', 'pk',)
|
||||
ordering_fields = ('id', 'created', 'email', 'item')
|
||||
filterset_class = WaitingListFilter
|
||||
permission = 'can_view_orders'
|
||||
|
||||
@@ -42,6 +42,7 @@ from pretix.base.models import LogEntry
|
||||
from pretix.base.services.tasks import ProfiledTask, TransactionAwareTask
|
||||
from pretix.base.signals import periodic_task
|
||||
from pretix.celery_app import app
|
||||
from pretix.helpers import OF_SELF
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
_ALL_EVENTS = None
|
||||
@@ -95,7 +96,7 @@ def get_all_webhook_events():
|
||||
return types
|
||||
|
||||
|
||||
class ParametrizedOrderWebhookEvent(WebhookEvent):
|
||||
class ParametrizedWebhookEvent(WebhookEvent):
|
||||
def __init__(self, action_type, verbose_name):
|
||||
self._action_type = action_type
|
||||
self._verbose_name = verbose_name
|
||||
@@ -109,6 +110,8 @@ class ParametrizedOrderWebhookEvent(WebhookEvent):
|
||||
def verbose_name(self):
|
||||
return self._verbose_name
|
||||
|
||||
|
||||
class ParametrizedOrderWebhookEvent(ParametrizedWebhookEvent):
|
||||
def build_payload(self, logentry: LogEntry):
|
||||
order = logentry.content_object
|
||||
if not order:
|
||||
@@ -123,19 +126,7 @@ class ParametrizedOrderWebhookEvent(WebhookEvent):
|
||||
}
|
||||
|
||||
|
||||
class ParametrizedEventWebhookEvent(WebhookEvent):
|
||||
def __init__(self, action_type, verbose_name):
|
||||
self._action_type = action_type
|
||||
self._verbose_name = verbose_name
|
||||
super().__init__()
|
||||
|
||||
@property
|
||||
def action_type(self):
|
||||
return self._action_type
|
||||
|
||||
@property
|
||||
def verbose_name(self):
|
||||
return self._verbose_name
|
||||
class ParametrizedEventWebhookEvent(ParametrizedWebhookEvent):
|
||||
|
||||
def build_payload(self, logentry: LogEntry):
|
||||
if logentry.action_type == 'pretix.event.deleted':
|
||||
@@ -159,19 +150,7 @@ class ParametrizedEventWebhookEvent(WebhookEvent):
|
||||
}
|
||||
|
||||
|
||||
class ParametrizedSubEventWebhookEvent(WebhookEvent):
|
||||
def __init__(self, action_type, verbose_name):
|
||||
self._action_type = action_type
|
||||
self._verbose_name = verbose_name
|
||||
super().__init__()
|
||||
|
||||
@property
|
||||
def action_type(self):
|
||||
return self._action_type
|
||||
|
||||
@property
|
||||
def verbose_name(self):
|
||||
return self._verbose_name
|
||||
class ParametrizedSubEventWebhookEvent(ParametrizedWebhookEvent):
|
||||
|
||||
def build_payload(self, logentry: LogEntry):
|
||||
# do not use content_object, this is also called in deletion
|
||||
@@ -184,6 +163,19 @@ class ParametrizedSubEventWebhookEvent(WebhookEvent):
|
||||
}
|
||||
|
||||
|
||||
class ParametrizedItemWebhookEvent(ParametrizedWebhookEvent):
|
||||
|
||||
def build_payload(self, logentry: LogEntry):
|
||||
# do not use content_object, this is also called in deletion
|
||||
return {
|
||||
'notification_id': logentry.pk,
|
||||
'organizer': logentry.event.organizer.slug,
|
||||
'event': logentry.event.slug,
|
||||
'item': logentry.object_id,
|
||||
'action': logentry.action_type,
|
||||
}
|
||||
|
||||
|
||||
class ParametrizedOrderPositionWebhookEvent(ParametrizedOrderWebhookEvent):
|
||||
|
||||
def build_payload(self, logentry: LogEntry):
|
||||
@@ -304,6 +296,11 @@ def register_default_webhook_events(sender, **kwargs):
|
||||
'pretix.subevent.deleted',
|
||||
pgettext_lazy('subevent', 'Event series date deleted'),
|
||||
),
|
||||
ParametrizedItemWebhookEvent(
|
||||
'pretix.event.item.*',
|
||||
_('Product changed (including product added or deleted and including changes to nested objects like '
|
||||
'variations or bundles)'),
|
||||
),
|
||||
ParametrizedEventWebhookEvent(
|
||||
'pretix.event.live.activated',
|
||||
_('Shop taken live'),
|
||||
@@ -502,7 +499,8 @@ def manually_retry_all_calls(webhook_id: int):
|
||||
webhook = WebHook.objects.get(id=webhook_id)
|
||||
with scope(organizer=webhook.organizer), transaction.atomic():
|
||||
for whcr in webhook.retries.select_for_update(
|
||||
skip_locked=connection.features.has_select_for_update_skip_locked
|
||||
skip_locked=connection.features.has_select_for_update_skip_locked,
|
||||
of=OF_SELF
|
||||
):
|
||||
send_webhook.apply_async(
|
||||
args=(whcr.logentry_id, whcr.action_type, whcr.webhook_id, whcr.retry_count),
|
||||
@@ -515,7 +513,8 @@ def manually_retry_all_calls(webhook_id: int):
|
||||
def schedule_webhook_retries_on_celery(sender, **kwargs):
|
||||
with transaction.atomic():
|
||||
for whcr in WebHookCallRetry.objects.select_for_update(
|
||||
skip_locked=connection.features.has_select_for_update_skip_locked
|
||||
skip_locked=connection.features.has_select_for_update_skip_locked,
|
||||
of=OF_SELF
|
||||
).filter(retry_not_before__lt=now()):
|
||||
send_webhook.apply_async(
|
||||
args=(whcr.logentry_id, whcr.action_type, whcr.webhook_id, whcr.retry_count),
|
||||
|
||||
@@ -42,7 +42,6 @@ from localflavor.fr.forms import FRZipCodeField
|
||||
from localflavor.gb.forms import GBPostcodeField
|
||||
from localflavor.gr.forms import GRPostalCodeField
|
||||
from localflavor.hr.forms import HRPostalCodeField
|
||||
from localflavor.id_.forms import IDPostCodeField
|
||||
from localflavor.ie.forms import EircodeField
|
||||
from localflavor.il.forms import ILPostalCodeField
|
||||
from localflavor.in_.forms import INZipCodeField
|
||||
@@ -80,8 +79,8 @@ COUNTRIES_WITH_STREET_ZIPCODE_AND_CITY_REQUIRED = {
|
||||
# We don't presume this for countries we don't have knowledge about, there are countries in the
|
||||
# world e.g. without zipcodes
|
||||
'AR', 'AT', 'AU', 'BE', 'BR', 'CA', 'CH', 'CN', 'CU', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FI', 'FR',
|
||||
'GB', 'GR', 'HR', 'ID', 'IE', 'IL', 'IN', 'IR', 'IS', 'IT', 'JP', 'LT', 'LV', 'MA', 'MT', 'MX',
|
||||
'NL', 'NO', 'NZ', 'PK', 'PL', 'PT', 'RO', 'RU', 'SE', 'SG', 'SI', 'SK', 'TR', 'UA', 'US', 'ZA',
|
||||
'GB', 'GR', 'HR', 'IE', 'IL', 'IN', 'IR', 'IS', 'IT', 'JP', 'LT', 'LV', 'MA', 'MT', 'MX', 'NL',
|
||||
'NO', 'NZ', 'PK', 'PL', 'PT', 'RO', 'RU', 'SE', 'SG', 'SI', 'SK', 'TR', 'UA', 'US', 'ZA',
|
||||
}
|
||||
|
||||
|
||||
@@ -167,7 +166,6 @@ _zip_code_fields = {
|
||||
'GB': GBPostcodeField,
|
||||
'GR': GRPostalCodeField,
|
||||
'HR': HRPostalCodeField,
|
||||
'ID': IDPostCodeField,
|
||||
'IE': EircodeField,
|
||||
'IL': ILPostalCodeField,
|
||||
'IN': INZipCodeField,
|
||||
|
||||
@@ -46,7 +46,7 @@ class PretixBaseConfig(AppConfig):
|
||||
from . import invoice # NOQA
|
||||
from . import notifications # NOQA
|
||||
from . import email # NOQA
|
||||
from .services import auth, checkin, export, mail, tickets, cart, orderimport, orders, invoices, cleanup, update_check, quotas, notifications, vouchers # NOQA
|
||||
from .services import auth, checkin, currencies, export, mail, tickets, cart, orderimport, orders, invoices, cleanup, update_check, quotas, notifications, vouchers # NOQA
|
||||
from .models import _transactions # NOQA
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
@@ -381,11 +381,24 @@ def base_placeholders(sender, **kwargs):
|
||||
SimpleFunctionalMailTextPlaceholder(
|
||||
'currency', ['event'], lambda event: event.currency, lambda event: event.currency
|
||||
),
|
||||
SimpleFunctionalMailTextPlaceholder(
|
||||
'order_email', ['order'], lambda order: order.email, 'john@example.org'
|
||||
),
|
||||
SimpleFunctionalMailTextPlaceholder(
|
||||
'invoice_number', ['invoice'],
|
||||
lambda invoice: invoice.full_invoice_no,
|
||||
f'{sender.settings.invoice_numbers_prefix or (sender.slug.upper() + "-")}00000'
|
||||
),
|
||||
SimpleFunctionalMailTextPlaceholder(
|
||||
'refund_amount', ['event_or_subevent', 'refund_amount'],
|
||||
lambda event_or_subevent, refund_amount: LazyCurrencyNumber(refund_amount, event_or_subevent.currency),
|
||||
lambda event_or_subevent: LazyCurrencyNumber(Decimal('42.23'), event_or_subevent.currency)
|
||||
),
|
||||
SimpleFunctionalMailTextPlaceholder(
|
||||
'pending_sum', ['event', 'pending_sum'],
|
||||
lambda event, pending_sum: LazyCurrencyNumber(pending_sum, event.currency),
|
||||
lambda event: LazyCurrencyNumber(Decimal('42.23'), event.currency)
|
||||
),
|
||||
SimpleFunctionalMailTextPlaceholder(
|
||||
'total_with_currency', ['event', 'order'], lambda event, order: LazyCurrencyNumber(order.total,
|
||||
event.currency),
|
||||
@@ -512,20 +525,20 @@ def base_placeholders(sender, **kwargs):
|
||||
lambda event: (event if not event.has_subevents or not event.subevents.exists() else event.subevents.first()).get_date_from_display()
|
||||
),
|
||||
SimpleFunctionalMailTextPlaceholder(
|
||||
'url_remove', ['waiting_list_entry', 'event'],
|
||||
lambda waiting_list_entry, event: build_absolute_uri(
|
||||
'url_remove', ['waiting_list_voucher', 'event'],
|
||||
lambda waiting_list_voucher, event: build_absolute_uri(
|
||||
event, 'presale:event.waitinglist.remove'
|
||||
) + '?voucher=' + waiting_list_entry.voucher.code,
|
||||
) + '?voucher=' + waiting_list_voucher.code,
|
||||
lambda event: build_absolute_uri(
|
||||
event,
|
||||
'presale:event.waitinglist.remove',
|
||||
) + '?voucher=68CYU2H6ZTP3WLK5',
|
||||
),
|
||||
SimpleFunctionalMailTextPlaceholder(
|
||||
'url', ['waiting_list_entry', 'event'],
|
||||
lambda waiting_list_entry, event: build_absolute_uri(
|
||||
'url', ['waiting_list_voucher', 'event'],
|
||||
lambda waiting_list_voucher, event: build_absolute_uri(
|
||||
event, 'presale:event.redeem'
|
||||
) + '?voucher=' + waiting_list_entry.voucher.code,
|
||||
) + '?voucher=' + waiting_list_voucher.code,
|
||||
lambda event: build_absolute_uri(
|
||||
event,
|
||||
'presale:event.redeem',
|
||||
@@ -580,7 +593,7 @@ def base_placeholders(sender, **kwargs):
|
||||
_('Sample Admission Ticket')
|
||||
),
|
||||
SimpleFunctionalMailTextPlaceholder(
|
||||
'code', ['waiting_list_entry'], lambda waiting_list_entry: waiting_list_entry.voucher.code,
|
||||
'code', ['waiting_list_voucher'], lambda waiting_list_voucher: waiting_list_voucher.code,
|
||||
'68CYU2H6ZTP3WLK5'
|
||||
),
|
||||
SimpleFunctionalMailTextPlaceholder(
|
||||
|
||||
@@ -36,7 +36,7 @@ import io
|
||||
import tempfile
|
||||
from collections import OrderedDict, namedtuple
|
||||
from decimal import Decimal
|
||||
from typing import Tuple
|
||||
from typing import Optional, Tuple
|
||||
|
||||
import pytz
|
||||
from defusedcsv import csv
|
||||
@@ -84,6 +84,27 @@ class BaseExporter:
|
||||
"""
|
||||
raise NotImplementedError() # NOQA
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
"""
|
||||
A description for this exporter.
|
||||
"""
|
||||
return ""
|
||||
|
||||
@property
|
||||
def category(self) -> Optional[str]:
|
||||
"""
|
||||
A category name for this exporter, or ``None``.
|
||||
"""
|
||||
return None
|
||||
|
||||
@property
|
||||
def featured(self) -> bool:
|
||||
"""
|
||||
If ``True``, this exporter will be highlighted.
|
||||
"""
|
||||
return False
|
||||
|
||||
@property
|
||||
def identifier(self) -> str:
|
||||
"""
|
||||
|
||||
@@ -39,7 +39,7 @@ from zipfile import ZipFile
|
||||
|
||||
from django import forms
|
||||
from django.dispatch import receiver
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.utils.translation import gettext_lazy as _, pgettext_lazy
|
||||
|
||||
from pretix.base.models import QuestionAnswer
|
||||
|
||||
@@ -49,7 +49,10 @@ from ..signals import register_data_exporters
|
||||
|
||||
class AnswerFilesExporter(BaseExporter):
|
||||
identifier = 'answerfiles'
|
||||
verbose_name = _('Answers to file upload questions')
|
||||
verbose_name = _('Question answer file uploads')
|
||||
category = pgettext_lazy('export_category', 'Order data')
|
||||
description = _('Download a ZIP file including all files that have been uploaded by your customers while creating '
|
||||
'an order.')
|
||||
|
||||
@property
|
||||
def export_form_fields(self):
|
||||
|
||||
@@ -36,7 +36,7 @@ from collections import OrderedDict
|
||||
|
||||
from django.dispatch import receiver
|
||||
from django.utils.timezone import get_current_timezone
|
||||
from django.utils.translation import gettext as _, gettext_lazy
|
||||
from django.utils.translation import gettext as _, gettext_lazy, pgettext_lazy
|
||||
|
||||
from pretix.base.settings import PERSON_NAME_SCHEMES
|
||||
|
||||
@@ -48,6 +48,8 @@ class CustomerListExporter(OrganizerLevelExportMixin, ListExporter):
|
||||
identifier = 'customerlist'
|
||||
verbose_name = gettext_lazy('Customer accounts')
|
||||
organizer_required_permission = 'can_manage_customers'
|
||||
category = pgettext_lazy('export_category', 'Customer accounts')
|
||||
description = gettext_lazy('Download a spreadsheet of all currently registered customer accounts.')
|
||||
|
||||
@property
|
||||
def additional_form_fields(self):
|
||||
|
||||
@@ -23,22 +23,24 @@ import json
|
||||
from collections import OrderedDict
|
||||
from decimal import Decimal
|
||||
|
||||
import dateutil
|
||||
from django import forms
|
||||
from django.core.serializers.json import DjangoJSONEncoder
|
||||
from django.dispatch import receiver
|
||||
from django.utils.translation import gettext, gettext_lazy
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext, gettext_lazy, pgettext_lazy
|
||||
|
||||
from pretix.base.i18n import language
|
||||
from pretix.base.models import Invoice, OrderPayment
|
||||
|
||||
from ..exporter import BaseExporter
|
||||
from ..signals import register_data_exporters
|
||||
from ..timeframes import DateFrameField, resolve_timeframe_to_dates_inclusive
|
||||
|
||||
|
||||
class DekodiNREIExporter(BaseExporter):
|
||||
identifier = 'dekodi_nrei'
|
||||
verbose_name = 'dekodi NREI (JSON)'
|
||||
category = pgettext_lazy('export_category', 'Invoices')
|
||||
description = gettext_lazy("Download invoices in a format that can be used by the dekodi NREI conversion software.")
|
||||
|
||||
# Specification: http://manuals.dekodi.de/nexuspub/schnittstellenbuch/
|
||||
|
||||
@@ -113,7 +115,7 @@ class DekodiNREIExporter(BaseExporter):
|
||||
'PTNo14': p.info_data.get('reference') or '',
|
||||
'PTNo15': p.full_id or '',
|
||||
})
|
||||
elif p.provider.startswith('stripe'):
|
||||
elif p.provider and p.provider.startswith('stripe'):
|
||||
src = p.info_data.get("source", p.info_data)
|
||||
payments.append({
|
||||
'PTID': '81',
|
||||
@@ -192,17 +194,12 @@ class DekodiNREIExporter(BaseExporter):
|
||||
def render(self, form_data):
|
||||
qs = self.event.invoices.select_related('order').prefetch_related('lines', 'lines__subevent')
|
||||
|
||||
if form_data.get('date_from'):
|
||||
date_value = form_data.get('date_from')
|
||||
if isinstance(date_value, str):
|
||||
date_value = dateutil.parser.parse(date_value).date()
|
||||
qs = qs.filter(date__gte=date_value)
|
||||
|
||||
if form_data.get('date_to'):
|
||||
date_value = form_data.get('date_to')
|
||||
if isinstance(date_value, str):
|
||||
date_value = dateutil.parser.parse(date_value).date()
|
||||
qs = qs.filter(date__lte=date_value)
|
||||
if form_data.get('date_range'):
|
||||
d_start, d_end = resolve_timeframe_to_dates_inclusive(now(), form_data['date_range'], self.timezone)
|
||||
if d_start:
|
||||
qs = qs.filter(date__gte=d_start)
|
||||
if d_end:
|
||||
qs = qs.filter(date__lte=d_end)
|
||||
|
||||
jo = {
|
||||
'Format': 'NREI',
|
||||
@@ -218,22 +215,14 @@ class DekodiNREIExporter(BaseExporter):
|
||||
def export_form_fields(self):
|
||||
return OrderedDict(
|
||||
[
|
||||
('date_from',
|
||||
forms.DateField(
|
||||
label=gettext_lazy('Start date'),
|
||||
widget=forms.DateInput(attrs={'class': 'datepickerfield'}),
|
||||
('date_range',
|
||||
DateFrameField(
|
||||
label=gettext_lazy('Date range'),
|
||||
include_future_frames=False,
|
||||
required=False,
|
||||
help_text=gettext_lazy('Only include invoices issued on or after this date. Note that the invoice date does '
|
||||
help_text=gettext_lazy('Only include invoices issued in this time frame. Note that the invoice date does '
|
||||
'not always correspond to the order or payment date.')
|
||||
)),
|
||||
('date_to',
|
||||
forms.DateField(
|
||||
label=gettext_lazy('End date'),
|
||||
widget=forms.DateInput(attrs={'class': 'datepickerfield'}),
|
||||
required=False,
|
||||
help_text=gettext_lazy('Only include invoices issued on or before this date. Note that the invoice date '
|
||||
'does not always correspond to the order or payment date.')
|
||||
)),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
from django.dispatch import receiver
|
||||
from django.utils.formats import date_format
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.utils.translation import gettext_lazy as _, pgettext_lazy
|
||||
|
||||
from ...control.forms.filter import get_all_payment_providers
|
||||
from ..exporter import ListExporter
|
||||
@@ -45,6 +45,8 @@ from ..signals import register_multievent_data_exporters
|
||||
class EventDataExporter(ListExporter):
|
||||
identifier = 'eventdata'
|
||||
verbose_name = _('Event data')
|
||||
category = pgettext_lazy('export_category', 'Event data')
|
||||
description = _('Download a spreadsheet with information on all events in this organizer account.')
|
||||
|
||||
@cached_property
|
||||
def providers(self):
|
||||
|
||||
@@ -38,13 +38,15 @@ from collections import OrderedDict
|
||||
from decimal import Decimal
|
||||
from zipfile import ZipFile
|
||||
|
||||
import dateutil.parser
|
||||
from django import forms
|
||||
from django.db.models import CharField, Exists, F, OuterRef, Q, Subquery, Sum
|
||||
from django.dispatch import receiver
|
||||
from django.utils.formats import date_format
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.translation import gettext, gettext_lazy as _, pgettext
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import (
|
||||
gettext, gettext_lazy as _, pgettext, pgettext_lazy,
|
||||
)
|
||||
|
||||
from pretix.base.models import Invoice, InvoiceLine, OrderPayment
|
||||
|
||||
@@ -57,30 +59,24 @@ from ..services.invoices import invoice_pdf_task
|
||||
from ..signals import (
|
||||
register_data_exporters, register_multievent_data_exporters,
|
||||
)
|
||||
from ..timeframes import DateFrameField, resolve_timeframe_to_dates_inclusive
|
||||
|
||||
|
||||
class InvoiceExporterMixin:
|
||||
category = pgettext_lazy('export_category', 'Invoices')
|
||||
|
||||
@property
|
||||
def invoice_exporter_form_fields(self):
|
||||
return OrderedDict(
|
||||
[
|
||||
('date_from',
|
||||
forms.DateField(
|
||||
label=_('Start date'),
|
||||
widget=forms.DateInput(attrs={'class': 'datepickerfield'}),
|
||||
('date_range',
|
||||
DateFrameField(
|
||||
label=_('Date range'),
|
||||
include_future_frames=False,
|
||||
required=False,
|
||||
help_text=_('Only include invoices issued on or after this date. Note that the invoice date does '
|
||||
help_text=_('Only include invoices issued in this time frame. Note that the invoice date does '
|
||||
'not always correspond to the order or payment date.')
|
||||
)),
|
||||
('date_to',
|
||||
forms.DateField(
|
||||
label=_('End date'),
|
||||
widget=forms.DateInput(attrs={'class': 'datepickerfield'}),
|
||||
required=False,
|
||||
help_text=_('Only include invoices issued on or before this date. Note that the invoice date '
|
||||
'does not always correspond to the order or payment date.')
|
||||
)),
|
||||
('payment_provider',
|
||||
forms.ChoiceField(
|
||||
label=_('Payment provider'),
|
||||
@@ -112,16 +108,12 @@ class InvoiceExporterMixin:
|
||||
)
|
||||
)
|
||||
qs = qs.filter(has_payment_with_provider=1)
|
||||
if form_data.get('date_from'):
|
||||
date_value = form_data.get('date_from')
|
||||
if isinstance(date_value, str):
|
||||
date_value = dateutil.parser.parse(date_value).date()
|
||||
qs = qs.filter(date__gte=date_value)
|
||||
if form_data.get('date_to'):
|
||||
date_value = form_data.get('date_to')
|
||||
if isinstance(date_value, str):
|
||||
date_value = dateutil.parser.parse(date_value).date()
|
||||
qs = qs.filter(date__lte=date_value)
|
||||
if form_data.get('date_range'):
|
||||
d_start, d_end = resolve_timeframe_to_dates_inclusive(now(), form_data['date_range'], self.timezone)
|
||||
if d_start:
|
||||
qs = qs.filter(date__gte=d_start)
|
||||
if d_end:
|
||||
qs = qs.filter(date__lte=d_end)
|
||||
|
||||
return qs
|
||||
|
||||
@@ -129,6 +121,7 @@ class InvoiceExporterMixin:
|
||||
class InvoiceExporter(InvoiceExporterMixin, BaseExporter):
|
||||
identifier = 'invoices'
|
||||
verbose_name = _('All invoices')
|
||||
description = _('Download all invoices created by the system as a ZIP file of PDF files.')
|
||||
|
||||
def render(self, form_data: dict, output_file=None):
|
||||
qs = self.invoices_queryset(form_data).filter(shredded=False)
|
||||
@@ -180,6 +173,10 @@ class InvoiceExporter(InvoiceExporterMixin, BaseExporter):
|
||||
class InvoiceDataExporter(InvoiceExporterMixin, MultiSheetListExporter):
|
||||
identifier = 'invoicedata'
|
||||
verbose_name = _('Invoice data')
|
||||
description = _('Download a spreadsheet with the data of all invoices created by the system. The spreadsheet '
|
||||
'includes two sheets, one with a line for every invoice, and one with a line for every position of '
|
||||
'every invoice.')
|
||||
featured = True
|
||||
|
||||
@property
|
||||
def additional_form_fields(self):
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
from django.db.models import Prefetch
|
||||
from django.dispatch import receiver
|
||||
from django.utils.formats import date_format
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.utils.translation import gettext_lazy as _, pgettext_lazy
|
||||
from openpyxl.styles import Alignment
|
||||
from openpyxl.utils import get_column_letter
|
||||
|
||||
@@ -48,6 +48,8 @@ def _min(a1, a2):
|
||||
class ItemDataExporter(ListExporter):
|
||||
identifier = 'itemdata'
|
||||
verbose_name = _('Product data')
|
||||
category = pgettext_lazy('export_category', 'Product data')
|
||||
description = _('Download a spreadsheet with details about all products and variations.')
|
||||
|
||||
def iterate_list(self, form_data):
|
||||
locales = self.event.settings.locales
|
||||
@@ -73,6 +75,7 @@ class ItemDataExporter(ListExporter):
|
||||
_("Free price input"),
|
||||
_("Sales tax"),
|
||||
_("Is an admission ticket"),
|
||||
_("Personalized ticket"),
|
||||
_("Generate tickets"),
|
||||
_("Waiting list"),
|
||||
_("Available from"),
|
||||
@@ -144,6 +147,7 @@ class ItemDataExporter(ListExporter):
|
||||
_("Yes") if i.free_price else "",
|
||||
str(i.tax_rule) if i.tax_rule else "",
|
||||
_("Yes") if i.admission else "",
|
||||
_("Yes") if i.personalized else "",
|
||||
_("Yes") if i.generate_tickets else (_("Default") if i.generate_tickets is None else ""),
|
||||
_("Yes") if i.allow_waitinglist else "",
|
||||
date_format(_max(i.available_from, v.available_from).astimezone(self.timezone),
|
||||
@@ -187,6 +191,7 @@ class ItemDataExporter(ListExporter):
|
||||
_("Yes") if i.free_price else "",
|
||||
str(i.tax_rule) if i.tax_rule else "",
|
||||
_("Yes") if i.admission else "",
|
||||
_("Yes") if i.personalized else "",
|
||||
_("Yes") if i.generate_tickets else (_("Default") if i.generate_tickets is None else ""),
|
||||
_("Yes") if i.allow_waitinglist else "",
|
||||
date_format(i.available_from.astimezone(self.timezone),
|
||||
|
||||
@@ -38,6 +38,8 @@ from decimal import Decimal
|
||||
from django.core.serializers.json import DjangoJSONEncoder
|
||||
from django.db.models import Prefetch
|
||||
from django.dispatch import receiver
|
||||
from django.utils.functional import lazy
|
||||
from django.utils.translation import gettext, gettext_lazy, pgettext_lazy
|
||||
|
||||
from ..exporter import BaseExporter
|
||||
from ..models import ItemMetaValue, ItemVariation, ItemVariationMetaValue
|
||||
@@ -46,7 +48,10 @@ from ..signals import register_data_exporters
|
||||
|
||||
class JSONExporter(BaseExporter):
|
||||
identifier = 'json'
|
||||
verbose_name = 'Order data (JSON)'
|
||||
verbose_name = lazy(lambda *args: gettext('Order data') + ' (JSON)', str)()
|
||||
category = pgettext_lazy('export_category', 'Order data')
|
||||
description = gettext_lazy('Download a structured JSON representation of all orders. This might be useful for the '
|
||||
'import in third-party systems.')
|
||||
|
||||
def render(self, form_data):
|
||||
jo = {
|
||||
@@ -78,6 +83,7 @@ class JSONExporter(BaseExporter):
|
||||
'tax_rate': item.tax_rule.rate if item.tax_rule else Decimal('0.00'),
|
||||
'tax_name': str(item.tax_rule.name) if item.tax_rule else None,
|
||||
'admission': item.admission,
|
||||
'personalized': item.personalized,
|
||||
'active': item.active,
|
||||
'sales_channels': item.sales_channels,
|
||||
'description': str(item.description),
|
||||
@@ -103,6 +109,8 @@ class JSONExporter(BaseExporter):
|
||||
'name': str(variation),
|
||||
'description': str(variation.description),
|
||||
'position': variation.position,
|
||||
'checkin_attention': variation.checkin_attention,
|
||||
'require_approval': variation.require_approval,
|
||||
'require_membership': variation.require_membership,
|
||||
'sales_channels': variation.sales_channels,
|
||||
'available_from': variation.available_from,
|
||||
@@ -187,6 +195,9 @@ class JSONExporter(BaseExporter):
|
||||
'state': position.state,
|
||||
'secret': position.secret,
|
||||
'addon_to': position.addon_to_id,
|
||||
'valid_from': position.valid_from,
|
||||
'valid_until': position.valid_until,
|
||||
'blocked': position.blocked,
|
||||
'answers': [
|
||||
{
|
||||
'question': answer.question_id,
|
||||
|
||||
@@ -36,7 +36,7 @@ from collections import OrderedDict
|
||||
|
||||
from django import forms
|
||||
from django.dispatch import receiver
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.utils.translation import gettext_lazy as _, pgettext_lazy
|
||||
|
||||
from pretix.base.models import OrderPosition
|
||||
|
||||
@@ -50,6 +50,8 @@ from ..signals import (
|
||||
class MailExporter(BaseExporter):
|
||||
identifier = 'mailaddrs'
|
||||
verbose_name = _('Email addresses (text file)')
|
||||
category = pgettext_lazy('export_category', 'Order data')
|
||||
description = _("Download a text file with all email addresses collected either from buyers or from ticket holders.")
|
||||
|
||||
def render(self, form_data: dict):
|
||||
qs = Order.objects.filter(event__in=self.events, status__in=form_data['status']).prefetch_related('event')
|
||||
|
||||