Compare commits

..

1 Commits

Author SHA1 Message Date
Raphael Michel
3cdb992134 Bump dnspython to 2.2.* 2022-12-20 11:55:52 +01:00
474 changed files with 164267 additions and 233343 deletions

View File

@@ -26,10 +26,10 @@ jobs:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.11 - name: Set up Python 3.9
uses: actions/setup-python@v1 uses: actions/setup-python@v1
with: with:
python-version: 3.11 python-version: 3.9
- uses: actions/cache@v1 - uses: actions/cache@v1
with: with:
path: ~/.cache/pip path: ~/.cache/pip

View File

@@ -24,10 +24,10 @@ jobs:
name: Check gettext syntax name: Check gettext syntax
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.11 - name: Set up Python 3.9
uses: actions/setup-python@v1 uses: actions/setup-python@v1
with: with:
python-version: 3.11 python-version: 3.9
- uses: actions/cache@v1 - uses: actions/cache@v1
with: with:
path: ~/.cache/pip path: ~/.cache/pip
@@ -50,10 +50,10 @@ jobs:
name: Spellcheck name: Spellcheck
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.11 - name: Set up Python 3.9
uses: actions/setup-python@v1 uses: actions/setup-python@v1
with: with:
python-version: 3.11 python-version: 3.9
- uses: actions/cache@v1 - uses: actions/cache@v1
with: with:
path: ~/.cache/pip path: ~/.cache/pip

View File

@@ -24,10 +24,10 @@ jobs:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.11 - name: Set up Python 3.9
uses: actions/setup-python@v1 uses: actions/setup-python@v1
with: with:
python-version: 3.11 python-version: 3.9
- uses: actions/cache@v1 - uses: actions/cache@v1
with: with:
path: ~/.cache/pip path: ~/.cache/pip
@@ -45,10 +45,10 @@ jobs:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.11 - name: Set up Python 3.9
uses: actions/setup-python@v1 uses: actions/setup-python@v1
with: with:
python-version: 3.11 python-version: 3.9
- uses: actions/cache@v1 - uses: actions/cache@v1
with: with:
path: ~/.cache/pip path: ~/.cache/pip
@@ -66,10 +66,10 @@ jobs:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.11 - name: Set up Python 3.9
uses: actions/setup-python@v1 uses: actions/setup-python@v1
with: with:
python-version: 3.11 python-version: 3.9
- name: Install Dependencies - name: Install Dependencies
run: pip3 install licenseheaders run: pip3 install licenseheaders
- name: Run licenseheaders - name: Run licenseheaders

View File

@@ -24,22 +24,22 @@ jobs:
name: Tests name: Tests
strategy: strategy:
matrix: matrix:
python-version: ["3.9", "3.10", "3.11"] python-version: ["3.7", "3.9", "3.10"]
database: [sqlite, postgres, mysql] database: [sqlite, postgres, mysql]
exclude: exclude:
- database: mysql - database: mysql
python-version: "3.9" python-version: "3.10"
- database: mysql - database: mysql
python-version: "3.11"
- database: sqlite
python-version: "3.9" python-version: "3.9"
- database: sqlite
python-version: "3.7"
- database: sqlite - database: sqlite
python-version: "3.10" python-version: "3.10"
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: getong/mariadb-action@v1.1 - uses: getong/mariadb-action@v1.1
with: with:
mariadb version: '10.10' mariadb version: '10.4'
mysql database: 'pretix' mysql database: 'pretix'
mysql root password: '' mysql root password: ''
if: matrix.database == 'mysql' if: matrix.database == 'mysql'
@@ -81,6 +81,5 @@ jobs:
uses: codecov/codecov-action@v1 uses: codecov/codecov-action@v1
with: with:
file: src/coverage.xml file: src/coverage.xml
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: true fail_ci_if_error: true
if: matrix.database == 'postgres' && matrix.python-version == '3.11' if: matrix.database == 'postgres' && matrix.python-version == '3.10'

View File

@@ -1,4 +1,4 @@
FROM python:3.11-bullseye FROM python:3.9-bullseye
RUN apt-get update && \ RUN apt-get update && \
apt-get install -y --no-install-recommends \ apt-get install -y --no-install-recommends \

View File

@@ -18,66 +18,44 @@
<title>{{ title|striptags|e }}{{ titlesuffix }}</title> <title>{{ title|striptags|e }}{{ titlesuffix }}</title>
{% endblock %} {% endblock %}
{# FAVICON #}
{#- CSS #} {% if favicon %}
{%- for css in css_files %} <link rel="shortcut icon" href="{{ pathto('_static/' + favicon, 1) }}"/>
{%- if css|attr("rel") %} {% endif %}
<link rel="{{ css.rel }}" href="{{ pathto(css.filename, 1) }}" type="text/css"{% if css.title is not none %} title="{{ css.title }}"{% endif %} /> {# CANONICAL URL #}
{%- else %} {% if theme_canonical_url %}
<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"/> <link rel="canonical" href="{{ theme_canonical_url }}{{ pagename }}.html"/>
{%- endif -%} {% endif %}
{#- CANONICAL URL #} {# CSS #}
{%- if pageurl %}
<link rel="canonical" href="{{ pageurl|e }}" />
{%- endif -%}
{#- JAVASCRIPTS #} {# OPENSEARCH #}
{%- block scripts %} {% if not embedded %}
<!--[if lt IE 9]> {% if use_opensearch %}
<script src="{{ pathto('_static/js/html5shiv.min.js', 1) }}"></script> <link rel="search" type="application/opensearchdescription+xml" title="{% trans docstitle=docstitle|e %}Search within {{ docstitle }}{% endtrans %}" href="{{ pathto('_static/opensearch.xml', 1) }}"/>
<![endif]--> {% 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>
{#- OPENSEARCH #} {% endif %}
{%- if use_opensearch %}
<link rel="search" type="application/opensearchdescription+xml" {# RTD hosts this file, so just load on non RTD builds #}
title="{% trans docstitle=docstitle|e %}Search within {{ docstitle }}{% endtrans %}" <link rel="stylesheet" href="{{ pathto('_static/' + style, 1) }}" type="text/css" />
href="{{ pathto('_static/opensearch.xml', 1) }}"/>
{%- endif %} {% for cssfile in css_files %}
{%- endif %} <link rel="stylesheet" href="{{ pathto(cssfile, 1) }}" type="text/css" />
{%- endblock %} {% endfor %}
{% for cssfile in extra_css_files %}
<link rel="stylesheet" href="{{ pathto(cssfile, 1) }}" type="text/css" />
{% endfor %}
{%- block linktags %} {%- block linktags %}
{%- if hasdoc('about') %} {%- if hasdoc('about') %}
<link rel="author" title="{{ _('About these documents') }}" href="{{ pathto('about') }}" /> <link rel="author" title="{{ _('About these documents') }}"
href="{{ pathto('about') }}"/>
{%- endif %} {%- endif %}
{%- if hasdoc('genindex') %} {%- if hasdoc('genindex') %}
<link rel="index" title="{{ _('Index') }}" href="{{ pathto('genindex') }}" /> <link rel="index" title="{{ _('Index') }}"
href="{{ pathto('genindex') }}"/>
{%- endif %} {%- endif %}
{%- if hasdoc('search') %} {%- if hasdoc('search') %}
<link rel="search" title="{{ _('Search') }}" href="{{ pathto('search') }}"/> <link rel="search" title="{{ _('Search') }}" href="{{ pathto('search') }}"/>
@@ -85,6 +63,10 @@
{%- if hasdoc('copyright') %} {%- if hasdoc('copyright') %}
<link rel="copyright" title="{{ _('Copyright') }}" href="{{ pathto('copyright') }}"/> <link rel="copyright" title="{{ _('Copyright') }}" href="{{ pathto('copyright') }}"/>
{%- endif %} {%- 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 }}"/>
{%- endif %}
{%- if next %} {%- 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 %} {%- endif %}
@@ -94,6 +76,9 @@
{%- endblock %} {%- endblock %}
{%- block extrahead %} {% endblock %} {%- block extrahead %} {% endblock %}
{# Keep modernizr in head - http://modernizr.com/docs/#installing #}
<script src="{{ pathto('_static/js/modernizr.min.js', 1) }}"></script>
</head> </head>
<body class="wy-body-for-nav" role="document"> <body class="wy-body-for-nav" role="document">
@@ -107,14 +92,16 @@
<div class="wy-side-nav-search"> <div class="wy-side-nav-search">
{% block sidebartitle %} {% block sidebartitle %}
{# the logo helper function was removed in Sphinx 6 and deprecated since Sphinx 4 #} {% if logo and theme_logo_only %}
{# the master_doc variable was renamed to root_doc in Sphinx 4 (master_doc still exists in later Sphinx versions) #} <a href="{{ pathto('index') }}">
{%- set _logo_url = logo_url|default(pathto('_static/' + (logo or ""), 1)) %} {% else %}
{%- set _root_doc = root_doc|default(master_doc) %} <a href="{{ pathto('index') }}" class="icon icon-home"> {{ project }}
<a href="{{ pathto(_root_doc) }}"{% if not theme_logo_only %} class="icon icon-home"{% endif %}> {% endif %}
{%- if logo or logo_url %}
<img src="{{ _logo_url }}" class="logo" alt="{{ _('Logo') }}"/> {% if logo %}
{%- endif %} {# 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 %}
</a> </a>
{% include "searchbox.html" %} {% include "searchbox.html" %}

View File

@@ -5,37 +5,31 @@
Template for the search page. Template for the search page.
:copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS.
:license: BSD, see https://github.com/sphinx-doc/sphinx/blob/master/LICENSE for details. :license: BSD, see LICENSE for details.
#} #}
{%- extends "layout.html" %} {%- extends "layout.html" %}
{% set title = _('Search') %} {% set title = _('Search') %}
{% set display_vcs_links = False %} {% set script_files = script_files + ['_static/searchtools.js'] %}
{%- block scripts %}
{{ super() }}
<script src="{{ pathto('_static/searchtools.js', 1) }}"></script>
<script src="{{ pathto('_static/language_data.js', 1) }}"></script>
{%- endblock %}
{% block footer %} {% block footer %}
<script> <script type="text/javascript">
jQuery(function() { Search.loadIndex("{{ pathto('searchindex.js', 1) }}"); }); jQuery(function() { Search.loadIndex("{{ pathto('searchindex.js', 1) }}"); });
</script> </script>
{# this is used when loading the search index using $.ajax fails, {# this is used when loading the search index using $.ajax fails,
such as on Chrome for documents on localhost #} such as on Chrome for documents on localhost #}
<script id="searchindexloader"></script> <script type="text/javascript" id="searchindexloader"></script>
{{ super() }} {{ super() }}
{% endblock %} {% endblock %}
{% block body %} {% block body %}
<noscript> <noscript>
<div id="fallback" class="admonition warning"> <div id="fallback" class="admonition warning">
<p class="last"> <p class="last">
{% trans trimmed %}Please activate JavaScript to enable the search {% trans %}Please activate JavaScript to enable the search
functionality.{% endtrans %} functionality.{% endtrans %}
</p> </p>
</div> </div>
</noscript> </noscript>
{% if search_performed %} {% if search_performed %}
{# Translators: Search is a noun, not a verb #}
<h2>{{ _('Search Results') }}</h2> <h2>{{ _('Search Results') }}</h2>
{% if not search_results %} {% 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> <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>

View File

@@ -2,7 +2,7 @@
.. _`config`: .. _`config`:
.. spelling:word-list:: Galera .. spelling:: Galera
Configuration file Configuration file
================== ==================
@@ -84,7 +84,7 @@ Example::
Enables or disables the "keep me logged in" button. Defaults to ``on``. Enables or disables the "keep me logged in" button. Defaults to ``on``.
``ecb_rates`` ``ecb_rates``
By default, pretix periodically downloads currency rates from the European Central Bank as well as other authorities By default, pretix periodically downloads a XML file from the European Central Bank to retrieve exchange rates
that are used to print tax amounts in the customer currency on invoices for some currencies. Set to ``off`` to 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``. disable this feature. Defaults to ``on``.
@@ -106,11 +106,6 @@ Example::
proxy that actively removes and re-adds the header to make sure the correct value is set. proxy that actively removes and re-adds the header to make sure the correct value is set.
Defaults to ``off``. 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`` ``csp_log``
Log violations of the Content Security Policy (CSP). Defaults to ``on``. Log violations of the Content Security Policy (CSP). Defaults to ``on``.
@@ -146,7 +141,7 @@ Database settings
Example:: Example::
[database] [database]
backend=postgresql backend=mysql
name=pretix name=pretix
user=pretix user=pretix
password=abcd password=abcd
@@ -154,7 +149,7 @@ Example::
port=3306 port=3306
``backend`` ``backend``
One of ``mysql`` (deprecated), ``sqlite3`` and ``postgresql``. One of ``mysql``, ``sqlite3``, ``oracle`` and ``postgresql``.
Default: ``sqlite3``. Default: ``sqlite3``.
If you use MySQL, be sure to create your database using If you use MySQL, be sure to create your database using
@@ -168,7 +163,7 @@ Example::
Connection details for the database connection. Empty by default. Connection details for the database connection. Empty by default.
``galera`` ``galera``
(Deprecated) Indicates if the database backend is a MySQL/MariaDB Galera cluster and Indicates if the database backend is a MySQL/MariaDB Galera cluster and
turns on some optimizations/special case handlers. Default: ``False`` turns on some optimizations/special case handlers. Default: ``False``
.. _`config-replica`: .. _`config-replica`:
@@ -199,7 +194,7 @@ Example::
[urls] [urls]
media=/media/ media=/media/
static=/static/ static=/media/
``media`` ``media``
The URL to be used to serve user-uploaded content. You should not need to modify The URL to be used to serve user-uploaded content. You should not need to modify

View File

@@ -14,5 +14,4 @@ This documentation is for everyone who wants to install pretix on a server.
maintainance maintainance
scaling scaling
errors errors
mysql2postgres
indexes indexes

View File

@@ -45,8 +45,8 @@ Here is the currently recommended set of commands::
CREATE INDEX CONCURRENTLY pretix_addidx_order_comment CREATE INDEX CONCURRENTLY pretix_addidx_order_comment
ON pretixbase_order ON pretixbase_order
USING gin (upper("comment") gin_trgm_ops); USING gin (upper("comment") gin_trgm_ops);
CREATE INDEX CONCURRENTLY pretix_addidx_order_event_date_id CREATE INDEX CONCURRENTLY pretix_addidx_order_event_date
ON public.pretixbase_order (event_id, datetime, id); ON public.pretixbase_order (event_id, datetime);
CREATE INDEX CONCURRENTLY pretix_addidx_orderpos_name CREATE INDEX CONCURRENTLY pretix_addidx_orderpos_name
ON pretixbase_orderposition ON pretixbase_orderposition
USING gin (upper("attendee_name_cached") gin_trgm_ops); 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)); ON public.pretixbase_orderposition (upper((attendee_email)::text));
CREATE INDEX CONCURRENTLY pretix_addidx_voucher_code_upper CREATE INDEX CONCURRENTLY pretix_addidx_voucher_code_upper
ON public.pretixbase_voucher (upper((code)::text)); ON public.pretixbase_voucher (upper((code)::text));
CREATE INDEX CONCURRENTLY pretix_addidx_logentry_event_date_id CREATE INDEX CONCURRENTLY pretix_addidx_logentry_event_date
ON public.pretixbase_logentry (event_id, datetime, id); ON public.pretixbase_logentry (event_id, datetime);
CREATE INDEX CONCURRENTLY pretix_addidx_logentry_event_cid_date_id CREATE INDEX CONCURRENTLY pretix_addidx_logentry_event_cid_date
ON public.pretixbase_logentry (event_id, content_type_id, datetime, id); ON public.pretixbase_logentry (event_id, content_type_id, datetime);
Also, if you use our ``pretix-shipping`` plugin:: Also, if you use our ``pretix-shipping`` plugin::

View File

@@ -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 get it right. If you're not feeling comfortable managing a Linux server, check out our hosting and service
offers at `pretix.eu`_. offers at `pretix.eu`_.
We tested this guide on the Linux distribution **Debian 11.0** but it should work very similar on other We tested this guide on the Linux distribution **Debian 8.0** but it should work very similar on other
modern distributions, especially on all systemd-based ones. modern distributions, especially on all systemd-based ones.
Requirements Requirements
@@ -26,7 +26,7 @@ installation guides):
* `Docker`_ * `Docker`_
* A SMTP server to send out mails, e.g. `Postfix`_ on your machine or some third-party server you have credentials for * 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 HTTP reverse proxy, e.g. `nginx`_ or Apache to allow HTTPS connections
* A `PostgreSQL`_ 9.6+ database server * A `PostgreSQL`_ 9.6+, `MySQL`_ 5.7+, or MariaDB 10.2.7+ database server
* A `redis`_ 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 We also recommend that you use a firewall, although this is not a pretix-specific recommendation. If you're new to
@@ -58,6 +58,9 @@ directory writable to the user that runs pretix inside the docker container::
Database 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 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 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:: the following command::
@@ -83,6 +86,13 @@ 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. 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 Redis
----- -----
@@ -142,13 +152,15 @@ Fill the configuration file ``/etc/pretix/pretix.cfg`` with the following conten
trust_x_forwarded_proto=on trust_x_forwarded_proto=on
[database] [database]
; Replace postgresql with mysql for MySQL
backend=postgresql backend=postgresql
name=pretix name=pretix
user=pretix user=pretix
; Replace with the password you chose above ; Replace with the password you chose above
password=********* password=*********
; In most docker setups, 172.17.0.1 is the address of the docker host. Adjust ; 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. ; this to wherever your database is running, e.g. the name of a linked container
; or of a mounted MySQL socket.
host=172.17.0.1 host=172.17.0.1
[mail] [mail]
@@ -200,6 +212,8 @@ named ``/etc/systemd/system/pretix.service`` with the following content::
[Install] [Install]
WantedBy=multi-user.target 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 You can now run the following commands
to enable and start the service:: to enable and start the service::
@@ -325,6 +339,7 @@ workers, e.g. ``docker run … taskworker -Q notifications --concurrency 32``.
.. _nginx: https://botleg.com/stories/https-with-lets-encrypt-and-nginx/ .. _nginx: https://botleg.com/stories/https-with-lets-encrypt-and-nginx/
.. _Let's Encrypt: https://letsencrypt.org/ .. _Let's Encrypt: https://letsencrypt.org/
.. _pretix.eu: https://pretix.eu/ .. _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 .. _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/ .. _redis: https://blog.programster.org/debian-8-install-redis-server/
.. _ufw: https://en.wikipedia.org/wiki/Uncomplicated_Firewall .. _ufw: https://en.wikipedia.org/wiki/Uncomplicated_Firewall

View File

@@ -1,6 +1,6 @@
.. highlight:: ini .. highlight:: ini
.. spelling:word-list:: SQL .. spelling:: SQL
General remarks General remarks
=============== ===============

View File

@@ -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 get it right. If you're not feeling comfortable managing a Linux server, check out our hosting and service
offers at `pretix.eu`_. offers at `pretix.eu`_.
We tested this guide on the Linux distribution **Debian 11.6** but it should work very similar on other We tested this guide on the Linux distribution **Debian 10.0** but it should work very similar on other
modern distributions, especially on all systemd-based ones. modern distributions, especially on all systemd-based ones.
Requirements 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 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 HTTP reverse proxy, e.g. `nginx`_ or Apache to allow HTTPS connections
* A `PostgreSQL`_ 11+ database server * A `PostgreSQL`_ 9.6+, `MySQL`_ 5.7+, or MariaDB 10.2.7+ database server
* A `redis`_ server * A `redis`_ server
* A `nodejs`_ installation * A `nodejs`_ installation
@@ -47,6 +47,9 @@ In this guide, all code lines prepended with a ``#`` symbol are commands that yo
Database 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 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 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:: best compatibility. You can check this with the following command::
@@ -58,6 +61,12 @@ For PostgreSQL database creation, we would do::
# sudo -u postgres createuser pretix # sudo -u postgres createuser pretix
# sudo -u postgres createdb -O pretix 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 Package dependencies
-------------------- --------------------
@@ -65,7 +74,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 \ # 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 \ python3-dev libxml2-dev libxslt1-dev libffi-dev zlib1g-dev libssl-dev \
gettext libpq-dev libjpeg-dev libopenjp2-7-dev gettext libpq-dev libmariadb-dev libjpeg-dev libopenjp2-7-dev
Config file Config file
----------- -----------
@@ -88,12 +97,16 @@ Fill the configuration file ``/etc/pretix/pretix.cfg`` with the following conten
trust_x_forwarded_proto=on trust_x_forwarded_proto=on
[database] [database]
; For MySQL, replace with "mysql"
backend=postgresql backend=postgresql
name=pretix name=pretix
user=pretix user=pretix
; For PostgreSQL on the same host, we don't need a password because we can use ; For MySQL, enter the user password. For PostgreSQL on the same host,
; peer authentication if our PostgreSQL user matches our unix user. ; we don't need one because we can use peer authentification if our
; PostgreSQL user matches our unix user.
password= 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 ; For local postgres authentication, you can leave it empty
host= host=
@@ -127,7 +140,11 @@ We now install pretix, its direct dependencies and gunicorn::
(venv)$ pip3 install pretix gunicorn (venv)$ pip3 install pretix gunicorn
Note that you need Python 3.9 or newer. You can find out your Python version using ``python -V``. 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``.
We also need to create a data directory:: We also need to create a data directory::
@@ -327,6 +344,7 @@ Then, proceed like after any plugin installation::
.. _nginx: https://botleg.com/stories/https-with-lets-encrypt-and-nginx/ .. _nginx: https://botleg.com/stories/https-with-lets-encrypt-and-nginx/
.. _Let's Encrypt: https://letsencrypt.org/ .. _Let's Encrypt: https://letsencrypt.org/
.. _pretix.eu: https://pretix.eu/ .. _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 .. _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/ .. _redis: https://blog.programster.org/debian-8-install-redis-server/
.. _ufw: https://en.wikipedia.org/wiki/Uncomplicated_Firewall .. _ufw: https://en.wikipedia.org/wiki/Uncomplicated_Firewall

View File

@@ -17,11 +17,11 @@ Backups
There are essentially two things which you should create backups of: There are essentially two things which you should create backups of:
Database Database
Your SQL database. This is critical and you should **absolutely always create automatic Your SQL database (MySQL or PostgreSQL). This is critical and you should **absolutely
backups of your database**. There are tons of tutorials on the internet on how to do this, always create automatic backups of your database**. There are tons of tutorials on the
and the exact process depends on the choice of your database. For PostgreSQL, see the internet on how to do this, and the exact process depends on the choice of your database.
``pg_dump`` tool. You probably want to create a cronjob that does the backups for you on a For MySQL, see ``mysqldump`` and for PostgreSQL, see the ``pg_dump`` tool. You probably
regular schedule. want to create a cronjob that does the backups for you on a regular schedule.
Data directory Data directory
The data directory of your pretix configuration might contain some things that you should The data directory of your pretix configuration might contain some things that you should

View File

@@ -1,156 +0,0 @@
.. 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

View File

@@ -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. * ``pretix-worker`` is a Celery-based application that processes tasks that should be run asynchronously outside of the web application process.
* A **PostgreSQL database** keeps all the important data and processes the actual transactions. * A **SQL database** keeps all the important data and processes the actual transactions. We recommend using PostgreSQL, but MySQL/MariaDB works as well.
* 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``. * 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 processes and the number of connections each worker process accepts. Double-check that TLS session caching works, because TLS
handshakes can get really expensive. handshakes can get really expensive.
During a traffic peak, your web server will be able to make use of more CPU resources, while memory usage will stay comparatively low, During a traffic peak, your web server will be able to make us 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. 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) Make sure that pretix' static files (such as CSS and JavaScript assets) as well as user-uploaded media files (event logos, etc)

View File

@@ -192,9 +192,6 @@ Relative date *either* String in ISO 8601 ``"2017-12-27"``,
File URL in responses, ``file:`` ``"https://…"``, ``"file:…"`` File URL in responses, ``file:`` ``"https://…"``, ``"file:…"``
specifiers in requests specifiers in requests
(see below). (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 Query parameters

View File

@@ -1,4 +1,4 @@
.. spelling:word-list:: checkin .. spelling:: checkin
.. _rest-checkin: .. _rest-checkin:
@@ -203,8 +203,6 @@ Checking a ticket in
* ``invalid`` - Ticket is not known. * ``invalid`` - Ticket is not known.
* ``unpaid`` - Ticket is not paid for. * ``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. * ``canceled`` Ticket is canceled or expired.
* ``already_redeemed`` - Ticket already has been redeemed. * ``already_redeemed`` - Ticket already has been redeemed.
* ``product`` - Tickets with this product may not be scanned at this device. * ``product`` - Tickets with this product may not be scanned at this device.

View File

@@ -1,4 +1,4 @@
.. spelling:word-list:: checkin .. spelling:: checkin
.. _rest-checkinlists: .. _rest-checkinlists:
@@ -98,8 +98,6 @@ 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 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 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 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 organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch :param event: The ``slug`` field of the event to fetch
:statuscode 200: no error :statuscode 200: no error
@@ -364,7 +362,7 @@ Endpoints
Stores a failed check-in. Only necessary for statistical purposes if you perform scan validation offline. 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``, :<json boolean error_reason: One of ``canceled``, ``invalid``, ``unpaid``, ``product``, ``rules``, ``revoked``,
``incomplete``, ``already_redeemed``, ``blocked``, ``invalid_time``, or ``error``. Required. ``incomplete``, ``already_redeemed``, or ``error``. Required.
:<json raw_barcode: The raw barcode you scanned. Required. :<json raw_barcode: The raw barcode you scanned. Required.
:<json datetime: Date and time of the scan. Optional. :<json datetime: Date and time of the scan. Optional.
:<json type: Type of scan, defaults to ``"entry"``. :<json type: Type of scan, defaults to ``"entry"``.
@@ -743,8 +741,6 @@ Order position endpoints
* ``invalid`` - Ticket code not known. * ``invalid`` - Ticket code not known.
* ``unpaid`` - Ticket is not paid for. * ``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`` Ticket is canceled or expired. This reason is only sent when your request sets.
``canceled_supported`` to ``true``, otherwise these orders return ``unpaid``. ``canceled_supported`` to ``true``, otherwise these orders return ``unpaid``.
* ``already_redeemed`` - Ticket already has been redeemed. * ``already_redeemed`` - Ticket already has been redeemed.

View File

@@ -1,4 +1,4 @@
.. spelling:word-list:: fullname .. spelling:: fullname
.. _`rest-devices`: .. _`rest-devices`:

View File

@@ -1,4 +1,4 @@
.. spelling:word-list:: .. spelling::
geo geo
lat lat
@@ -49,7 +49,6 @@ valid_keys object Cryptographic k
only contained in detail views. Value can be cached. only contained in detail views. Value can be cached.
sales_channels list A list of sales channels this event is available for sales_channels list A list of sales channels this event is available for
sale on. sale on.
public_url string The public, customer-facing URL of the event (read-only).
===================================== ========================== ======================================================= ===================================== ========================== =======================================================
@@ -66,10 +65,6 @@ Endpoints
The ``search`` query parameter has been added to filter events by their slug, name, or location in any language. 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/ .. http:get:: /api/v1/organizers/(organizer)/events/
Returns a list of all events within a given organizer the authenticated user/token has access to. Returns a list of all events within a given organizer the authenticated user/token has access to.
@@ -128,8 +123,7 @@ Endpoints
"web", "web",
"pretixpos", "pretixpos",
"resellers" "resellers"
], ]
"public_url": "https://pretix.eu/bigevents/sampleconf/"
} }
] ]
} }
@@ -137,7 +131,6 @@ Endpoints
:query page: The page number in case of a multi-page result set, default is 1 :query page: The page number in case of a multi-page result set, default is 1
:query is_public: If set to ``true``/``false``, only events with a matching value of ``is_public`` are returned. :query is_public: If set to ``true``/``false``, only events with a matching value of ``is_public`` are returned.
:query live: If set to ``true``/``false``, only events with a matching value of ``live`` are returned. :query live: If set to ``true``/``false``, only events with a matching value of ``live`` are returned.
:query testmode: If set to ``true``/``false``, only events with a matching value of ``testmode`` are returned.
:query has_subevents: If set to ``true``/``false``, only events with a matching value of ``has_subevents`` are returned. :query has_subevents: If set to ``true``/``false``, only events with a matching value of ``has_subevents`` are returned.
:query is_future: If set to ``true`` (``false``), only events that happen currently or in the future are (not) returned. Event series are never (always) returned. :query is_future: If set to ``true`` (``false``), only events that happen currently or in the future are (not) returned. Event series are never (always) returned.
:query is_past: If set to ``true`` (``false``), only events that are over are (not) returned. Event series are never (always) returned. :query is_past: If set to ``true`` (``false``), only events that are over are (not) returned. Event series are never (always) returned.
@@ -218,8 +211,7 @@ Endpoints
"web", "web",
"pretixpos", "pretixpos",
"resellers" "resellers"
], ]
"public_url": "https://pretix.eu/bigevents/sampleconf/"
} }
:param organizer: The ``slug`` field of the organizer to fetch :param organizer: The ``slug`` field of the organizer to fetch
@@ -315,8 +307,7 @@ Endpoints
"web", "web",
"pretixpos", "pretixpos",
"resellers" "resellers"
], ]
"public_url": "https://pretix.eu/bigevents/sampleconf/"
} }
:param organizer: The ``slug`` field of the organizer of the event to create. :param organizer: The ``slug`` field of the organizer of the event to create.
@@ -420,8 +411,7 @@ Endpoints
"web", "web",
"pretixpos", "pretixpos",
"resellers" "resellers"
], ]
"public_url": "https://pretix.eu/bigevents/sampleconf/"
} }
:param organizer: The ``slug`` field of the organizer of the event to create. :param organizer: The ``slug`` field of the organizer of the event to create.
@@ -495,8 +485,7 @@ Endpoints
"web", "web",
"pretixpos", "pretixpos",
"resellers" "resellers"
], ]
"public_url": "https://pretix.eu/bigevents/sampleconf/"
} }
:param organizer: The ``slug`` field of the organizer of the event to update :param organizer: The ``slug`` field of the organizer of the event to update

View File

@@ -1,4 +1,4 @@
.. spelling:word-list:: checkin .. spelling:: checkin
Data exporters Data exporters
============== ==============

View File

@@ -42,7 +42,6 @@ introductory_text string Text to be prin
additional_text string Text to be printed below the product list 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_provider_text string Text to be printed below the product list with
payment information 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 footer_text string Text to be printed in the page footer area
lines list of objects The actual invoice contents lines list of objects The actual invoice contents
├ position integer Number of the line within an invoice. ├ position integer Number of the line within an invoice.
@@ -179,7 +178,6 @@ Endpoints
"internal_reference": "", "internal_reference": "",
"additional_text": "We are looking forward to see you on our conference!", "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_text": "Please transfer the money to our account ABC…",
"payment_provider_stamp": null,
"footer_text": "Big Events LLC - Registration No. 123456 - VAT ID: EU0987654321", "footer_text": "Big Events LLC - Registration No. 123456 - VAT ID: EU0987654321",
"lines": [ "lines": [
{ {
@@ -270,7 +268,6 @@ Endpoints
"internal_reference": "", "internal_reference": "",
"additional_text": "We are looking forward to see you on our conference!", "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_text": "Please transfer the money to our account ABC…",
"payment_provider_stamp": null,
"footer_text": "Big Events LLC - Registration No. 123456 - VAT ID: EU0987654321", "footer_text": "Big Events LLC - Registration No. 123456 - VAT ID: EU0987654321",
"lines": [ "lines": [
{ {

View File

@@ -24,9 +24,6 @@ active boolean If ``false``, t
description multi-lingual string A public description of the variation. May contain description multi-lingual string A public description of the variation. May contain
Markdown syntax or can be ``null``. Markdown syntax or can be ``null``.
position integer An integer, used for sorting 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 require_approval boolean If ``true``, orders with this variation will need to be
approved by the event organizer before they can be approved by the event organizer before they can be
paid. paid.
@@ -51,7 +48,7 @@ meta_data object Values set for
.. versionchanged:: 4.16 .. versionchanged:: 4.16
The ``meta_data`` and ``checkin_attention`` attributes have been added. The ``meta_data`` attribute has been added.
Endpoints Endpoints
--------- ---------
@@ -87,7 +84,6 @@ Endpoints
"en": "S" "en": "S"
}, },
"active": true, "active": true,
"checkin_attention": false,
"require_approval": false, "require_approval": false,
"require_membership": false, "require_membership": false,
"require_membership_hidden": false, "require_membership_hidden": false,
@@ -111,7 +107,6 @@ Endpoints
"en": "L" "en": "L"
}, },
"active": true, "active": true,
"checkin_attention": false,
"require_approval": false, "require_approval": false,
"require_membership": false, "require_membership": false,
"require_membership_hidden": false, "require_membership_hidden": false,
@@ -164,7 +159,6 @@ Endpoints
"price": "10.00", "price": "10.00",
"original_price": null, "original_price": null,
"active": true, "active": true,
"checkin_attention": false,
"require_approval": false, "require_approval": false,
"require_membership": false, "require_membership": false,
"require_membership_hidden": false, "require_membership_hidden": false,
@@ -203,7 +197,6 @@ Endpoints
"value": {"en": "Student"}, "value": {"en": "Student"},
"default_price": "10.00", "default_price": "10.00",
"active": true, "active": true,
"checkin_attention": false,
"require_approval": false, "require_approval": false,
"require_membership": false, "require_membership": false,
"require_membership_hidden": false, "require_membership_hidden": false,
@@ -232,7 +225,6 @@ Endpoints
"price": "10.00", "price": "10.00",
"original_price": null, "original_price": null,
"active": true, "active": true,
"checkin_attention": false,
"require_approval": false, "require_approval": false,
"require_membership": false, "require_membership": false,
"require_membership_hidden": false, "require_membership_hidden": false,
@@ -292,7 +284,6 @@ Endpoints
"price": "10.00", "price": "10.00",
"original_price": null, "original_price": null,
"active": false, "active": false,
"checkin_attention": false,
"require_approval": false, "require_approval": false,
"require_membership": false, "require_membership": false,
"require_membership_hidden": false, "require_membership_hidden": false,

View File

@@ -11,9 +11,9 @@ The item resource contains the following public fields:
.. rst-class:: rest-resource-table .. rst-class:: rest-resource-table
======================================= ========================== ======================================================= ===================================== ========================== =======================================================
Field Type Description Field Type Description
======================================= ========================== ======================================================= ===================================== ========================== =======================================================
id integer Internal ID of the item id integer Internal ID of the item
name multi-lingual string The item's visible name name multi-lingual string The item's visible name
internal_name string An optional name that is only used in the backend internal_name string An optional name that is only used in the backend
@@ -35,12 +35,6 @@ tax_rule integer The internal
admission boolean ``true`` for items that grant admission to the event admission boolean ``true`` for items that grant admission to the event
(such as primary tickets) and ``false`` for others (such as primary tickets) and ``false`` for others
(such as add-ons or merchandise). (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 position integer An integer, used for sorting
picture file A product picture to be displayed in the shop picture file A product picture to be displayed in the shop
(can be ``null``). (can be ``null``).
@@ -87,18 +81,6 @@ grant_membership_duration_days integer If ``grant_me
days for the membership. days for the membership.
grant_membership_duration_months integer If ``grant_membership_duration_like_event`` is ``false``, this sets the number of grant_membership_duration_months integer If ``grant_membership_duration_like_event`` is ``false``, this sets the number of
calendar months for the membership. 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 generate_tickets boolean If ``false``, tickets are never generated for this
product, regardless of other settings. If ``true``, product, regardless of other settings. If ``true``,
tickets are generated even if this is a tickets are generated even if this is a
@@ -124,12 +106,6 @@ variations list of objects A list with o
for price calculations (or ``null``). for price calculations (or ``null``).
├ active boolean If ``false``, this variation will not be sold or shown. ├ active boolean If ``false``, this variation will not be sold or shown.
├ description multi-lingual string A public description of the variation. May contain ├ 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 boolean If ``true``, booking this variation requires an active membership.
├ require_membership_hidden boolean If ``true`` and ``require_membership`` is set, this variation will ├ require_membership_hidden boolean If ``true`` and ``require_membership`` is set, this variation will
be hidden from users without a valid membership. be hidden from users without a valid membership.
@@ -169,7 +145,7 @@ bundles list of objects Definition of
used to split the price of the base item e.g. for mixed used to split the price of the base item e.g. for mixed
taxation. This is not added to the price. taxation. This is not added to the price.
meta_data object Values set for event-specific meta data parameters. meta_data object Values set for event-specific meta data parameters.
======================================= ========================== ======================================================= ===================================== ========================== =======================================================
.. versionchanged:: 4.0 .. versionchanged:: 4.0
@@ -182,12 +158,7 @@ meta_data object Values set fo
.. versionchanged:: 4.16 .. versionchanged:: 4.16
The ``variations[x].meta_data`` and ``variations[x].checkin_attention`` attributes have been added. The ``variations[x].meta_data`` attribute has been added.
The ``personalized`` attribute has been added.
.. versionchanged:: 4.17
The ``validity_*`` attributes have been added.
Notes Notes
----- -----
@@ -242,7 +213,6 @@ Endpoints
"tax_rate": "0.00", "tax_rate": "0.00",
"tax_rule": 1, "tax_rule": 1,
"admission": false, "admission": false,
"personalized": false,
"issue_giftcard": false, "issue_giftcard": false,
"meta_data": {}, "meta_data": {},
"position": 0, "position": 0,
@@ -268,14 +238,6 @@ Endpoints
"grant_membership_duration_like_event": true, "grant_membership_duration_like_event": true,
"grant_membership_duration_days": 0, "grant_membership_duration_days": 0,
"grant_membership_duration_months": 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": [ "variations": [
{ {
"value": {"en": "Student"}, "value": {"en": "Student"},
@@ -283,8 +245,6 @@ Endpoints
"price": "10.00", "price": "10.00",
"original_price": null, "original_price": null,
"active": true, "active": true,
"checkin_attention": false,
"require_approval": false,
"require_membership": false, "require_membership": false,
"require_membership_types": [], "require_membership_types": [],
"sales_channels": ["web"], "sales_channels": ["web"],
@@ -301,8 +261,6 @@ Endpoints
"price": "23.00", "price": "23.00",
"original_price": null, "original_price": null,
"active": true, "active": true,
"checkin_attention": false,
"require_approval": false,
"require_membership": false, "require_membership": false,
"require_membership_types": [], "require_membership_types": [],
"sales_channels": ["web"], "sales_channels": ["web"],
@@ -371,7 +329,6 @@ Endpoints
"tax_rate": "0.00", "tax_rate": "0.00",
"tax_rule": 1, "tax_rule": 1,
"admission": false, "admission": false,
"personalized": false,
"issue_giftcard": false, "issue_giftcard": false,
"meta_data": {}, "meta_data": {},
"position": 0, "position": 0,
@@ -397,14 +354,6 @@ Endpoints
"grant_membership_duration_like_event": true, "grant_membership_duration_like_event": true,
"grant_membership_duration_days": 0, "grant_membership_duration_days": 0,
"grant_membership_duration_months": 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": [ "variations": [
{ {
"value": {"en": "Student"}, "value": {"en": "Student"},
@@ -412,8 +361,6 @@ Endpoints
"price": "10.00", "price": "10.00",
"original_price": null, "original_price": null,
"active": true, "active": true,
"checkin_attention": false,
"require_approval": false,
"require_membership": false, "require_membership": false,
"require_membership_types": [], "require_membership_types": [],
"description": null, "description": null,
@@ -430,8 +377,6 @@ Endpoints
"price": "23.00", "price": "23.00",
"original_price": null, "original_price": null,
"active": true, "active": true,
"checkin_attention": false,
"require_approval": false,
"require_membership": false, "require_membership": false,
"require_membership_types": [], "require_membership_types": [],
"sales_channels": ["web"], "sales_channels": ["web"],
@@ -481,7 +426,6 @@ Endpoints
"tax_rate": "0.00", "tax_rate": "0.00",
"tax_rule": 1, "tax_rule": 1,
"admission": false, "admission": false,
"personalized": false,
"issue_giftcard": false, "issue_giftcard": false,
"meta_data": {}, "meta_data": {},
"position": 0, "position": 0,
@@ -506,14 +450,6 @@ Endpoints
"grant_membership_duration_like_event": true, "grant_membership_duration_like_event": true,
"grant_membership_duration_days": 0, "grant_membership_duration_days": 0,
"grant_membership_duration_months": 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": [ "variations": [
{ {
"value": {"en": "Student"}, "value": {"en": "Student"},
@@ -521,8 +457,6 @@ Endpoints
"price": "10.00", "price": "10.00",
"original_price": null, "original_price": null,
"active": true, "active": true,
"checkin_attention": false,
"require_approval": false,
"require_membership": false, "require_membership": false,
"require_membership_types": [], "require_membership_types": [],
"sales_channels": ["web"], "sales_channels": ["web"],
@@ -539,8 +473,6 @@ Endpoints
"price": "23.00", "price": "23.00",
"original_price": null, "original_price": null,
"active": true, "active": true,
"checkin_attention": false,
"require_approval": false,
"require_membership": false, "require_membership": false,
"require_membership_types": [], "require_membership_types": [],
"sales_channels": ["web"], "sales_channels": ["web"],
@@ -578,7 +510,6 @@ Endpoints
"tax_rate": "0.00", "tax_rate": "0.00",
"tax_rule": 1, "tax_rule": 1,
"admission": false, "admission": false,
"personalized": false,
"issue_giftcard": false, "issue_giftcard": false,
"meta_data": {}, "meta_data": {},
"position": 0, "position": 0,
@@ -604,14 +535,6 @@ Endpoints
"grant_membership_duration_like_event": true, "grant_membership_duration_like_event": true,
"grant_membership_duration_days": 0, "grant_membership_duration_days": 0,
"grant_membership_duration_months": 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": [ "variations": [
{ {
"value": {"en": "Student"}, "value": {"en": "Student"},
@@ -619,8 +542,6 @@ Endpoints
"price": "10.00", "price": "10.00",
"original_price": null, "original_price": null,
"active": true, "active": true,
"checkin_attention": false,
"require_approval": false,
"require_membership": false, "require_membership": false,
"require_membership_types": [], "require_membership_types": [],
"sales_channels": ["web"], "sales_channels": ["web"],
@@ -637,8 +558,6 @@ Endpoints
"price": "23.00", "price": "23.00",
"original_price": null, "original_price": null,
"active": true, "active": true,
"checkin_attention": false,
"require_approval": false,
"require_membership": false, "require_membership": false,
"require_membership_types": [], "require_membership_types": [],
"sales_channels": ["web"], "sales_channels": ["web"],
@@ -707,7 +626,6 @@ Endpoints
"tax_rate": "0.00", "tax_rate": "0.00",
"tax_rule": 1, "tax_rule": 1,
"admission": false, "admission": false,
"personalized": false,
"issue_giftcard": false, "issue_giftcard": false,
"meta_data": {}, "meta_data": {},
"position": 0, "position": 0,
@@ -733,14 +651,6 @@ Endpoints
"grant_membership_duration_like_event": true, "grant_membership_duration_like_event": true,
"grant_membership_duration_days": 0, "grant_membership_duration_days": 0,
"grant_membership_duration_months": 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": [ "variations": [
{ {
"value": {"en": "Student"}, "value": {"en": "Student"},
@@ -748,8 +658,6 @@ Endpoints
"price": "10.00", "price": "10.00",
"original_price": null, "original_price": null,
"active": true, "active": true,
"checkin_attention": false,
"require_approval": false,
"require_membership": false, "require_membership": false,
"require_membership_types": [], "require_membership_types": [],
"sales_channels": ["web"], "sales_channels": ["web"],
@@ -766,8 +674,6 @@ Endpoints
"price": "23.00", "price": "23.00",
"original_price": null, "original_price": null,
"active": true, "active": true,
"checkin_attention": false,
"require_approval": false,
"require_membership": false, "require_membership": false,
"require_membership_types": [], "require_membership_types": [],
"sales_channels": ["web"], "sales_channels": ["web"],

View File

@@ -1,4 +1,4 @@
.. spelling:word-list:: .. spelling::
checkins checkins
pdf pdf
@@ -91,10 +91,6 @@ require_approval boolean If ``true`` and
needs approval by an organizer before it can needs approval by an organizer before it can
continue. If ``true`` and the order is canceled, continue. If ``true`` and the order is canceled,
this order has been denied by the event organizer. 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 url string The full URL to the order confirmation page
payments list of objects List of payment processes (see below) payments list of objects List of payment processes (see below)
refunds list of objects List of refund processes (see below) refunds list of objects List of refund processes (see below)
@@ -126,10 +122,6 @@ last_modified datetime Last modificati
The ``include`` query parameter has been added. The ``include`` query parameter has been added.
.. versionchanged:: 4.16
The ``valid_if_pending`` attribute has been added.
.. _order-position-resource: .. _order-position-resource:
@@ -167,9 +159,6 @@ secret string Secret code pri
addon_to integer Internal ID of the position this position is an add-on for (or ``null``) 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``). 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``). 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 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 checkins list of objects List of **successful** check-ins with this ticket
├ id integer Internal ID of the check-in event ├ id integer Internal ID of the check-in event
@@ -197,10 +186,6 @@ pdf_data object Data object req
``pdf_data=true`` query parameter to your request. ``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:
Order payment resource Order payment resource
@@ -309,7 +294,6 @@ List of all orders
"custom_followup_at": null, "custom_followup_at": null,
"checkin_attention": false, "checkin_attention": false,
"require_approval": false, "require_approval": false,
"valid_if_pending": false,
"invoice_address": { "invoice_address": {
"last_modified": "2017-12-01T10:00:00Z", "last_modified": "2017-12-01T10:00:00Z",
"is_business": true, "is_business": true,
@@ -352,9 +336,6 @@ List of all orders
"secret": "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", "secret": "z3fsn8jyufm5kpk768q69gkbyr5f4h6w",
"addon_to": null, "addon_to": null,
"subevent": null, "subevent": null,
"valid_from": null,
"valid_until": null,
"blocked": null,
"discount": null, "discount": null,
"pseudonymization_id": "MQLJvANO3B", "pseudonymization_id": "MQLJvANO3B",
"seat": null, "seat": null,
@@ -486,7 +467,6 @@ Fetching individual orders
"custom_followup_at": null, "custom_followup_at": null,
"checkin_attention": false, "checkin_attention": false,
"require_approval": false, "require_approval": false,
"valid_if_pending": false,
"invoice_address": { "invoice_address": {
"last_modified": "2017-12-01T10:00:00Z", "last_modified": "2017-12-01T10:00:00Z",
"company": "Sample company", "company": "Sample company",
@@ -529,9 +509,6 @@ Fetching individual orders
"secret": "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", "secret": "z3fsn8jyufm5kpk768q69gkbyr5f4h6w",
"addon_to": null, "addon_to": null,
"subevent": null, "subevent": null,
"valid_from": null,
"valid_until": null,
"blocked": null,
"discount": null, "discount": null,
"pseudonymization_id": "MQLJvANO3B", "pseudonymization_id": "MQLJvANO3B",
"seat": null, "seat": null,
@@ -663,8 +640,6 @@ Updating order fields
* ``invoice_address`` (you always need to supply the full object, or ``null`` to delete the current address) * ``invoice_address`` (you always need to supply the full object, or ``null`` to delete the current address)
* ``valid_if_pending``
**Example request**: **Example request**:
.. sourcecode:: http .. sourcecode:: http
@@ -840,7 +815,6 @@ Creating orders
* does not support or validate memberships * does not support or validate memberships
You can supply the following fields of the resource: You can supply the following fields of the resource:
* ``code`` (optional) Only ``A-Z`` and ``0-9``, but without ``O`` and ``1``. * ``code`` (optional) Only ``A-Z`` and ``0-9``, but without ``O`` and ``1``.
@@ -871,7 +845,6 @@ Creating orders
* ``custom_followup_at`` (optional) * ``custom_followup_at`` (optional)
* ``checkin_attention`` (optional) * ``checkin_attention`` (optional)
* ``require_approval`` (optional) * ``require_approval`` (optional)
* ``valid_if_pending`` (optional)
* ``invoice_address`` (optional) * ``invoice_address`` (optional)
* ``company`` * ``company``
@@ -907,9 +880,6 @@ Creating orders
* ``secret`` (optional) * ``secret`` (optional)
* ``addon_to`` (optional, see below) * ``addon_to`` (optional, see below)
* ``subevent`` (optional) * ``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`` * ``answers``
* ``question`` * ``question``
@@ -980,7 +950,7 @@ Creating orders
"street": "Sesam Street 12", "street": "Sesam Street 12",
"zipcode": "12345", "zipcode": "12345",
"city": "Sample City", "city": "Sample City",
"country": "GB", "country": "UK",
"state": "", "state": "",
"internal_reference": "", "internal_reference": "",
"vat_id": "" "vat_id": ""
@@ -1478,9 +1448,6 @@ List of all order positions
"seat": null, "seat": null,
"addon_to": null, "addon_to": null,
"subevent": null, "subevent": null,
"valid_from": null,
"valid_until": null,
"blocked": null,
"checkins": [ "checkins": [
{ {
"list": 44, "list": 44,
@@ -1587,9 +1554,6 @@ Fetching individual positions
"secret": "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", "secret": "z3fsn8jyufm5kpk768q69gkbyr5f4h6w",
"addon_to": null, "addon_to": null,
"subevent": null, "subevent": null,
"valid_from": null,
"valid_until": null,
"blocked": null,
"discount": null, "discount": null,
"pseudonymization_id": "MQLJvANO3B", "pseudonymization_id": "MQLJvANO3B",
"seat": null, "seat": null,
@@ -1695,10 +1659,6 @@ Manipulating individual positions
The ``PATCH`` method now supports changing items, variations, subevents, seats, prices, and tax rules. The ``PATCH`` method now supports changing items, variations, subevents, seats, prices, and tax rules.
The ``POST`` endpoint to add individual positions has been added. 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)/ .. http:patch:: /api/v1/organizers/(organizer)/events/(event)/orderpositions/(id)/
Updates specific fields on an order position. Currently, only the following fields are supported: Updates specific fields on an order position. Currently, only the following fields are supported:
@@ -1737,10 +1697,6 @@ Manipulating individual positions
* ``tax_rule`` * ``tax_rule``
* ``valid_from``
* ``valid_until``
Changing parameters such as ``item`` or ``price`` will **not** automatically trigger creation of a new invoice, Changing parameters such as ``item`` or ``price`` will **not** automatically trigger creation of a new invoice,
you need to take care of that yourself. you need to take care of that yourself.
@@ -1815,10 +1771,6 @@ Manipulating individual positions
and ``option_identifiers`` will be ignored. As a special case, you can submit the magic value 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. ``"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. This will **not** automatically trigger creation of a new invoice, you need to take care of that yourself.
**Example request**: **Example request**:
@@ -1882,82 +1834,6 @@ Manipulating individual positions
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource. :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. :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 Changing order contents
----------------------- -----------------------
@@ -1976,7 +1852,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 * ``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``, ``body`` specifying the desired changed values of the position (``item``, ``variation``, ``subevent``, ``seat``,
``price``, ``tax_rule``, ``valid_from``, ``valid_until``). ``price``, ``tax_rule``).
* ``cancel_positions``: A list of objects with the single key ``position`` specifying an order position ID. * ``cancel_positions``: A list of objects with the single key ``position`` specifying an order position ID.
@@ -2665,57 +2541,3 @@ With some non-default ticket secret generation methods, a list of revoked ticket
:statuscode 200: no error :statuscode 200: no error
:statuscode 401: Authentication failure :statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource. :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.

View File

@@ -17,18 +17,12 @@ Field Type Description
name string The organizer's full name, i.e. the name of an name string The organizer's full name, i.e. the name of an
organization or company. organization or company.
slug string A short form of the name, used e.g. in URLs. 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 Endpoints
--------- ---------
.. versionchanged:: 4.17
The ``public_url`` field has been added.
.. http:get:: /api/v1/organizers/ .. http:get:: /api/v1/organizers/
Returns a list of all organizers the authenticated user/token has access to. Returns a list of all organizers the authenticated user/token has access to.
@@ -57,7 +51,6 @@ Endpoints
{ {
"name": "Big Events LLC", "name": "Big Events LLC",
"slug": "Big Events", "slug": "Big Events",
"public_url": "https://pretix.eu/bigevents/"
} }
] ]
} }
@@ -91,7 +84,6 @@ Endpoints
{ {
"name": "Big Events LLC", "name": "Big Events LLC",
"slug": "Big Events", "slug": "Big Events",
"public_url": "https://pretix.eu/bigevents/"
} }
:param organizer: The ``slug`` field of the organizer to fetch :param organizer: The ``slug`` field of the organizer to fetch

View File

@@ -1,4 +1,4 @@
.. spelling:word-list:: .. spelling::
checkin checkin
datetime datetime

View File

@@ -1,4 +1,4 @@
.. spelling:word-list:: checkin .. spelling:: checkin
Data shredders Data shredders
============== ==============

View File

@@ -1,4 +1,4 @@
.. spelling:word-list:: .. spelling::
geo geo
lat lat

View File

@@ -1,4 +1,4 @@
.. spelling:word-list:: fullname checkin .. spelling:: fullname checkin
.. _`rest-teams`: .. _`rest-teams`:

View File

@@ -26,7 +26,6 @@ limit_events list of strings If ``all_events
action_types list of strings A list of action type filters that limit the action_types list of strings A list of action type filters that limit the
notifications sent to this webhook. See below for notifications sent to this webhook. See below for
valid values valid values
comment string Internal comment on this webhook, default ``null``
===================================== ========================== ======================================================= ===================================== ========================== =======================================================
The following values for ``action_types`` are valid with pretix core: The following values for ``action_types`` are valid with pretix core:
@@ -57,7 +56,6 @@ The following values for ``action_types`` are valid with pretix core:
* ``pretix.subevent.added`` * ``pretix.subevent.added``
* ``pretix.subevent.changed`` * ``pretix.subevent.changed``
* ``pretix.subevent.deleted`` * ``pretix.subevent.deleted``
* ``pretix.event.item.*``
* ``pretix.event.live.activated`` * ``pretix.event.live.activated``
* ``pretix.event.live.deactivated`` * ``pretix.event.live.deactivated``
* ``pretix.event.testmode.activated`` * ``pretix.event.testmode.activated``
@@ -100,8 +98,7 @@ Endpoints
"target_url": "https://httpstat.us/200", "target_url": "https://httpstat.us/200",
"all_events": false, "all_events": false,
"limit_events": ["democon"], "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
} }
] ]
} }
@@ -138,8 +135,7 @@ Endpoints
"target_url": "https://httpstat.us/200", "target_url": "https://httpstat.us/200",
"all_events": false, "all_events": false,
"limit_events": ["democon"], "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 :param organizer: The ``slug`` field of the organizer to fetch
@@ -166,8 +162,7 @@ Endpoints
"target_url": "https://httpstat.us/200", "target_url": "https://httpstat.us/200",
"all_events": false, "all_events": false,
"limit_events": ["democon"], "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**: **Example response**:
@@ -184,8 +179,7 @@ Endpoints
"target_url": "https://httpstat.us/200", "target_url": "https://httpstat.us/200",
"all_events": false, "all_events": false,
"limit_events": ["democon"], "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 :param organizer: The ``slug`` field of the organizer to create a webhook for
@@ -230,8 +224,7 @@ Endpoints
"target_url": "https://httpstat.us/200", "target_url": "https://httpstat.us/200",
"all_events": false, "all_events": false,
"limit_events": ["democon"], "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 :param organizer: The ``slug`` field of the organizer to modify

View File

@@ -13,6 +13,10 @@
# All configuration values have a default; values that are commented out # All configuration values have a default; values that are commented out
# serve to show the default. # 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 sys
import os import os
@@ -24,13 +28,12 @@ from datetime import date
sys.path.insert(0, os.path.abspath('../src')) sys.path.insert(0, os.path.abspath('../src'))
import django import django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "pretix.testutils.settings") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "pretix.testutils.settings")
django.setup() django.setup()
try:
import enchant # noqa
try:
import enchant
HAS_PYENCHANT = True HAS_PYENCHANT = True
except: except:
HAS_PYENCHANT = False HAS_PYENCHANT = False
@@ -49,7 +52,6 @@ extensions = [
'sphinx.ext.coverage', 'sphinx.ext.coverage',
'sphinxcontrib.httpdomain', 'sphinxcontrib.httpdomain',
'sphinxcontrib.images', 'sphinxcontrib.images',
'sphinxcontrib.jquery',
'sphinxemoji.sphinxemoji', 'sphinxemoji.sphinxemoji',
] ]
if HAS_PYENCHANT: if HAS_PYENCHANT:
@@ -77,7 +79,6 @@ copyright = '2014-{}, Raphael Michel'.format(date.today().year)
# #
# The short X.Y version. # The short X.Y version.
from pretix import __version__ from pretix import __version__
version = '.'.join(__version__.split('.')[:2]) version = '.'.join(__version__.split('.')[:2])
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
release = __version__ release = __version__
@@ -216,6 +217,7 @@ htmlhelp_basename = 'pretixdoc'
html_theme = 'pretix_theme' html_theme = 'pretix_theme'
html_theme_path = [os.path.abspath('_themes')] html_theme_path = [os.path.abspath('_themes')]
# -- Options for LaTeX output --------------------------------------------- # -- Options for LaTeX output ---------------------------------------------
latex_elements = { latex_elements = {
@@ -320,5 +322,4 @@ if HAS_PYENCHANT:
# List of filter classes to be added to the tokenizer that produces words to be checked. # List of filter classes to be added to the tokenizer that produces words to be checked.
from checkin_filter import CheckinFilter from checkin_filter import CheckinFilter
spelling_filters=[CheckinFilter] spelling_filters=[CheckinFilter]

View File

@@ -76,10 +76,6 @@ The exporter class
This is an abstract attribute, you **must** override this! This is an abstract attribute, you **must** override this!
.. autoattribute:: description
.. autoattribute:: category
.. autoattribute:: export_form_fields .. autoattribute:: export_form_fields
.. automethod:: render .. automethod:: render

View File

@@ -61,7 +61,7 @@ Backend
item_formsets, order_search_filter_q, order_search_forms item_formsets, order_search_filter_q, order_search_forms
.. automodule:: pretix.base.signals .. automodule:: pretix.base.signals
:members: logentry_display, logentry_object_link, requiredaction_display, timeline_events, orderposition_blocked_display :members: logentry_display, logentry_object_link, requiredaction_display, timeline_events
Vouchers Vouchers
"""""""" """"""""

View File

@@ -102,8 +102,6 @@ The provider class
.. automethod:: render_invoice_text .. automethod:: render_invoice_text
.. automethod:: render_invoice_stamp
.. automethod:: order_change_allowed .. automethod:: order_change_allowed
.. automethod:: payment_prepare .. automethod:: payment_prepare
@@ -122,8 +120,6 @@ The provider class
.. automethod:: refund_control_render .. automethod:: refund_control_render
.. automethod:: refund_control_render_short
.. automethod:: new_refund_control_form_render .. automethod:: new_refund_control_form_render
.. automethod:: new_refund_control_form_process .. automethod:: new_refund_control_form_process
@@ -134,8 +130,6 @@ The provider class
.. automethod:: matching_id .. automethod:: matching_id
.. automethod:: refund_matching_id
.. automethod:: shred_payment_info .. automethod:: shred_payment_info
.. automethod:: cancel_payment .. automethod:: cancel_payment

View File

@@ -55,6 +55,7 @@ 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 restricted boolean (optional) ``False`` by default, restricts a plugin such that it can only be enabled
for an event by system administrators / superusers. for an event by system administrators / superusers.
experimental boolean (optional) ``False`` by default, marks a plugin as an experimental feature in the plugins list. 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. compatibility string Specifier for compatible pretix versions.
================== ==================== =========================================================== ================== ==================== ===========================================================

View File

@@ -1,4 +1,4 @@
.. spelling:word-list:: Rebase rebasing .. spelling:: Rebase rebasing
Coding style and quality Coding style and quality
======================== ========================

View File

@@ -1,7 +1,7 @@
.. highlight:: python .. highlight:: python
:linenothreshold: 5 :linenothreshold: 5
.. spelling:word-list:: answ contrib .. spelling:: answ contrib
Data model Data model
========== ==========

Binary file not shown.

Before

Width:  |  Height:  |  Size: 278 KiB

After

Width:  |  Height:  |  Size: 274 KiB

View File

@@ -22,14 +22,6 @@ partition "data-based check" {
"Is the order in status PAID or PENDING\nand is the position not canceled?" --> if "" then "Is the order in status PAID or PENDING\nand is the position not canceled?" --> if "" then
-right->[no] "Return error CANCELED" -right->[no] "Return error CANCELED"
else
-down->[yes] "Is one or more block set on the ticket?"
--> if "" then
-right->[no] "Return error BLOCKED"
else
-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_TIME"
else else
-down->[yes] "Is the product part of the check-in list?" -down->[yes] "Is the product part of the check-in list?"
--> if "" then --> if "" then
@@ -42,31 +34,19 @@ partition "data-based check" {
else else
-down->[yes] "Is the order in status PAID?" -down->[yes] "Is the order in status PAID?"
--> if "" then --> if "" then
-right->[no] "Is Order.require_approval set?" -right->[no] "Does the check-in list include pending orders?"
--> if "" then --> if "" then
-->[yes] "Return error UNPAID " -right->[no] "Return error UNPAID "
else else
-right->[no] "Is Order.valid_if_pending set?" -down->[yes] "Is ignore_unpaid set?\n(Has the operator confirmed\nthe checkin?)"
--> if "" then --> if "" then
-->[yes] "Is this an entry or exit?" -right->[no] "Return error UNPAID "
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 else
-down->[yes] "Is this an entry or exit?" -down->[yes] "Is this an entry or exit?"
endif endif
endif endif
else
-down->[yes] "Is this an entry or exit?"
endif endif
endif endif
endif endif
@@ -117,14 +97,6 @@ partition "dataless check" {
-down->[yes] "Is the ticket secret on the revocation list?" -down->[yes] "Is the ticket secret on the revocation list?"
--> if "" then --> if "" then
-right->[yes] "Return error REVOKED" -right->[yes] "Return error REVOKED"
else
-down->[yes] "Is the ticket secret on the block list?"
--> if "" then
-right->[yes] "Return error BLOCKED "
else
-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_TIME "
else else
-down->[no] "Is the product part of the check-in list? " -down->[no] "Is the product part of the check-in list? "
--> if "" then --> if "" then
@@ -139,8 +111,6 @@ partition "dataless check" {
endif endif
endif endif
endif endif
endif
endif
else else
-right>[no] "Return error INVALID " -right>[no] "Return error INVALID "
endif endif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 177 KiB

After

Width:  |  Height:  |  Size: 175 KiB

View File

@@ -39,14 +39,6 @@ endif
"Is the order in status PAID or PENDING\nand is the position not canceled?" --> if "" then "Is the order in status PAID or PENDING\nand is the position not canceled?" --> if "" then
-right->[no] "Return error CANCELED" -right->[no] "Return error CANCELED"
else
-down->[yes] "Is one or more block set on the ticket?"
--> if "" then
-right->[no] "Return error BLOCKED"
else
-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_TIME"
else else
-down->[yes] "Is the product part of the check-in list?" -down->[yes] "Is the product part of the check-in list?"
--> if "" then --> if "" then
@@ -58,35 +50,23 @@ else
else else
-down->[yes] "Is the order in status PAID\nor is this a forced upload?" -down->[yes] "Is the order in status PAID\nor is this a forced upload?"
--> if "" then --> if "" then
-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?" -right->[no] "Does the check-in list include pending orders?"
--> if "" then --> if "" then
-right->[no] "Return error UNPAID " -right->[no] "Return error UNPAID "
else else
-down->[yes] "Is ignore_unpaid set on the request?\n(Has the operator confirmed\nthe checkin?)" -down->[yes] "Is ignore_unpaid set?\n(Has the operator confirmed\nthe checkin?)"
--> if "" then --> if "" then
-right->[no] "Return error UNPAID " -right->[no] "Return error UNPAID "
else else
-down->[yes] "Is this an entry or exit?\nIs the upload forced?" -down->[yes] "Is this an entry or exit?\nIs the upload forced?"
endif endif
endif endif
endif
else
-->[yes] "Return error UNPAID "
endif
else else
-down->[yes] "Is this an entry or exit?\nIs the upload forced?" -down->[yes] "Is this an entry or exit?\nIs the upload forced?"
endif endif
endif endif
endif endif
endif endif
endif
endif
"Is this an entry or exit?\nIs the upload forced?" --> if "" then "Is this an entry or exit?\nIs the upload forced?" --> if "" then
-right->[entry && not force] Evaluate custom logic (rules) -right->[entry && not force] Evaluate custom logic (rules)

View File

@@ -1 +1,69 @@
<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> <?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=&quot;svg2&quot;
version=&quot;1.1&quot;&gt;
<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>

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@@ -1,4 +1,4 @@
.. spelling:word-list:: .. spelling::
AGPL AGPL
AGPLv3 AGPLv3

View File

@@ -1,4 +1,4 @@
.. spelling:word-list:: .. spelling::
Analytics Analytics
List of plugins List of plugins

View File

@@ -1,5 +1,5 @@
.. highlight:: ini .. highlight:: ini
.. spelling:word-list:: .. spelling::
IdP IdP
skIDentity skIDentity

View File

@@ -17,13 +17,9 @@ Field Type Description
id integer Internal layout ID id integer Internal layout ID
name string Internal layout description name string Internal layout description
default boolean ``true`` if this is the default layout default boolean ``true`` if this is the default layout
layout list Dynamic layout specification. Each list element layout object Layout specification for libpretixprint
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 background URL Background PDF file
item_assignments list of objects Products this layout is assigned to (currently read-only) item_assignments list of objects Products this layout is assigned to
├ sales_channel string Sales channel (defaults to ``web``). ├ sales_channel string Sales channel (defaults to ``web``).
└ item integer Item ID └ item integer Item ID
===================================== ========================== ======================================================= ===================================== ========================== =======================================================
@@ -62,7 +58,7 @@ Endpoints
"name": "Default layout", "name": "Default layout",
"default": true, "default": true,
"layout": {…}, "layout": {…},
"background": null, "background": {},
"item_assignments": [] "item_assignments": []
} }
] ]
@@ -100,7 +96,7 @@ Endpoints
"name": "Default layout", "name": "Default layout",
"default": true, "default": true,
"layout": {…}, "layout": {…},
"background": null, "background": {},
"item_assignments": [] "item_assignments": []
} }
@@ -151,122 +147,3 @@ Endpoints
:statuscode 200: no error :statuscode 200: no error
:statuscode 401: Authentication failure :statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to view it. :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

View File

@@ -1,10 +1,10 @@
sphinx==6.1.* sphinx==2.3.*
jinja2==3.1.* jinja2==3.0.*
sphinx-rtd-theme sphinx-rtd-theme
sphinxcontrib-httpdomain sphinxcontrib-httpdomain
sphinxcontrib-images sphinxcontrib-images
sphinxcontrib-jquery sphinxcontrib-spelling==4.*
sphinxcontrib-spelling==7.*
sphinxemoji sphinxemoji
pygments-markdown-lexer pygments-markdown-lexer
pyenchant==3.2.* # See https://github.com/rfk/pyenchant/pull/130
git+https://github.com/raphaelm/pyenchant.git@patch-1#egg=pyenchant

View File

@@ -1,11 +1,11 @@
-e ../src/ -e ../src/
sphinx==6.1.* sphinx==2.3.*
jinja2==3.1.* jinja2==3.0.*
sphinx-rtd-theme sphinx-rtd-theme
sphinxcontrib-httpdomain sphinxcontrib-httpdomain
sphinxcontrib-images sphinxcontrib-images
sphinxcontrib-jquery sphinxcontrib-spelling==4.*
sphinxcontrib-spelling==7.*
sphinxemoji sphinxemoji
pygments-markdown-lexer pygments-markdown-lexer
pyenchant==3.2.* # See https://github.com/rfk/pyenchant/pull/130
git+https://github.com/raphaelm/pyenchant.git@patch-1#egg=pyenchant

View File

@@ -97,7 +97,6 @@ overpayment
param param
passphrase passphrase
percental percental
personalization
pluggable pluggable
positionid positionid
pre pre

View File

@@ -1,4 +1,4 @@
.. spelling:word-list:: .. spelling::
Warengutschein Warengutschein
Wertgutschein Wertgutschein

View File

@@ -1,7 +1,7 @@
Invoice settings Invoice settings
================ ================
.. spelling:word-list:: Inv .. spelling:: Inv
The settings at "Settings" → "Invoice" allow you to specify if and how pretix should generate invoices for your orders. The settings at "Settings" → "Invoice" allow you to specify if and how pretix should generate invoices for your orders.

View File

@@ -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 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:: view. If you do not set it, the choice will be taken from your organizer settings::
<pretix-widget event="https://pretix.eu/demo/series/" list-type="list"></pretix-widget> <pretix-widget event="https://pretix.eu/demo/series/" style="list"></pretix-widget>
<pretix-widget event="https://pretix.eu/demo/series/" list-type="calendar"></pretix-widget> <pretix-widget event="https://pretix.eu/demo/series/" style="calendar"></pretix-widget>
<pretix-widget event="https://pretix.eu/demo/series/" list-type="week"></pretix-widget> <pretix-widget event="https://pretix.eu/demo/series/" style="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 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. reasons instead.
@@ -164,7 +164,7 @@ You can see an example here:
.. raw:: html .. raw:: html
<pretix-widget event="https://pretix.eu/demo/series/" list-type="calendar"></pretix-widget> <pretix-widget event="https://pretix.eu/demo/series/" style="calendar"></pretix-widget>
<noscript> <noscript>
<div class="pretix-widget"> <div class="pretix-widget">
<div class="pretix-widget-info-message"> <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 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:: 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/" list-type="list" filter="attr[Promoted]=Yes"></pretix-widget> <pretix-widget event="https://pretix.eu/demo/series/" style="list" filter="attr[Promoted]=Yes"></pretix-widget>
pretix Button pretix Button
------------- -------------

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@@ -1 +1,36 @@
<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> <?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>

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@@ -1 +1,72 @@
<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> <?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>

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@@ -1 +1,68 @@
<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> <?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>

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@@ -19,4 +19,4 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see # 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/>. # <https://www.gnu.org/licenses/>.
# #
__version__ = "4.18.0.dev0" __version__ = "4.16.0.dev0"

View File

@@ -19,12 +19,9 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see # 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/>. # <https://www.gnu.org/licenses/>.
# #
import logging
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
logger = logging.getLogger(__name__)
class FullAccessSecurityProfile: class FullAccessSecurityProfile:
identifier = 'full' identifier = 'full'
@@ -39,13 +36,7 @@ class AllowListSecurityProfile:
def is_allowed(self, request): def is_allowed(self, request):
key = (request.method, f"{request.resolver_match.namespace}:{request.resolver_match.url_name}") key = (request.method, f"{request.resolver_match.namespace}:{request.resolver_match.url_name}")
if key in self.allowlist: return key in self.allowlist
return True
else:
logger.info(
f'Request {key} not allowed in profile {self.identifier}'
)
return False
class PretixScanSecurityProfile(AllowListSecurityProfile): class PretixScanSecurityProfile(AllowListSecurityProfile):
@@ -74,7 +65,6 @@ class PretixScanSecurityProfile(AllowListSecurityProfile):
('GET', 'api-v1:checkinlistpos-list'), ('GET', 'api-v1:checkinlistpos-list'),
('POST', 'api-v1:checkinlistpos-redeem'), ('POST', 'api-v1:checkinlistpos-redeem'),
('GET', 'api-v1:revokedsecrets-list'), ('GET', 'api-v1:revokedsecrets-list'),
('GET', 'api-v1:blockedsecrets-list'),
('GET', 'api-v1:order-list'), ('GET', 'api-v1:order-list'),
('GET', 'api-v1:orderposition-pdf_image'), ('GET', 'api-v1:orderposition-pdf_image'),
('GET', 'api-v1:event.settings'), ('GET', 'api-v1:event.settings'),
@@ -109,7 +99,6 @@ class PretixScanNoSyncNoSearchSecurityProfile(AllowListSecurityProfile):
('POST', 'api-v1:checkinlist-failed_checkins'), ('POST', 'api-v1:checkinlist-failed_checkins'),
('POST', 'api-v1:checkinlistpos-redeem'), ('POST', 'api-v1:checkinlistpos-redeem'),
('GET', 'api-v1:revokedsecrets-list'), ('GET', 'api-v1:revokedsecrets-list'),
('GET', 'api-v1:blockedsecrets-list'),
('GET', 'api-v1:orderposition-pdf_image'), ('GET', 'api-v1:orderposition-pdf_image'),
('GET', 'api-v1:event.settings'), ('GET', 'api-v1:event.settings'),
('POST', 'api-v1:upload'), ('POST', 'api-v1:upload'),
@@ -144,7 +133,6 @@ class PretixScanNoSyncSecurityProfile(AllowListSecurityProfile):
('GET', 'api-v1:checkinlistpos-list'), ('GET', 'api-v1:checkinlistpos-list'),
('POST', 'api-v1:checkinlistpos-redeem'), ('POST', 'api-v1:checkinlistpos-redeem'),
('GET', 'api-v1:revokedsecrets-list'), ('GET', 'api-v1:revokedsecrets-list'),
('GET', 'api-v1:blockedsecrets-list'),
('GET', 'api-v1:orderposition-pdf_image'), ('GET', 'api-v1:orderposition-pdf_image'),
('GET', 'api-v1:event.settings'), ('GET', 'api-v1:event.settings'),
('POST', 'api-v1:upload'), ('POST', 'api-v1:upload'),
@@ -211,7 +199,6 @@ class PretixPosSecurityProfile(AllowListSecurityProfile):
('POST', 'plugins:pretix_posbackend:stripeterminal.paymentintent'), ('POST', 'plugins:pretix_posbackend:stripeterminal.paymentintent'),
('PUT', 'plugins:pretix_posbackend:file.upload'), ('PUT', 'plugins:pretix_posbackend:file.upload'),
('GET', 'api-v1:revokedsecrets-list'), ('GET', 'api-v1:revokedsecrets-list'),
('GET', 'api-v1:blockedsecrets-list'),
('GET', 'api-v1:event.settings'), ('GET', 'api-v1:event.settings'),
('GET', 'plugins:pretix_seating:event.event'), ('GET', 'plugins:pretix_seating:event.event'),
('GET', 'plugins:pretix_seating:event.event.subevent'), ('GET', 'plugins:pretix_seating:event.event.subevent'),

View File

@@ -20,7 +20,6 @@
# <https://www.gnu.org/licenses/>. # <https://www.gnu.org/licenses/>.
# #
import json import json
import logging
from hashlib import sha1 from hashlib import sha1
from django.conf import settings from django.conf import settings
@@ -33,9 +32,6 @@ from rest_framework import status
from pretix.api.models import ApiCall from pretix.api.models import ApiCall
from pretix.base.models import Organizer from pretix.base.models import Organizer
from pretix.helpers import OF_SELF
logger = logging.getLogger(__name__)
class IdempotencyMiddleware: class IdempotencyMiddleware:
@@ -60,7 +56,7 @@ class IdempotencyMiddleware:
idempotency_key = request.headers.get('X-Idempotency-Key', '') idempotency_key = request.headers.get('X-Idempotency-Key', '')
with transaction.atomic(): with transaction.atomic():
call, created = ApiCall.objects.select_for_update(of=OF_SELF).get_or_create( call, created = ApiCall.objects.select_for_update().get_or_create(
auth_hash=auth_hash, auth_hash=auth_hash,
idempotency_key=idempotency_key, idempotency_key=idempotency_key,
defaults={ defaults={
@@ -100,9 +96,6 @@ class IdempotencyMiddleware:
return resp return resp
else: else:
if call.locked: if call.locked:
logger.info(
f'Concurrent request with idempotency key {idempotency_key} blocked.'
)
r = JsonResponse( r = JsonResponse(
{'detail': 'Concurrent request with idempotency key.'}, {'detail': 'Concurrent request with idempotency key.'},
status=status.HTTP_409_CONFLICT, status=status.HTTP_409_CONFLICT,
@@ -117,7 +110,6 @@ class IdempotencyMiddleware:
content=content, content=content,
status=call.response_code, 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(): for k, v in json.loads(call.response_headers).values():
r[k] = v r[k] = v
return r return r

View File

@@ -1,18 +0,0 @@
# 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),
),
]

View File

@@ -112,7 +112,6 @@ class WebHook(models.Model):
target_url = models.URLField(verbose_name=_("Target URL"), max_length=255) 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)")) 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) 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: class Meta:
ordering = ('id',) ordering = ('id',)

View File

@@ -19,19 +19,9 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see # 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/>. # <https://www.gnu.org/licenses/>.
# #
from rest_framework.filters import OrderingFilter
from rest_framework.pagination import PageNumberPagination from rest_framework.pagination import PageNumberPagination
from pretix.helpers import get_deterministic_ordering
class Pagination(PageNumberPagination): class Pagination(PageNumberPagination):
page_size_query_param = 'page_size' page_size_query_param = 'page_size'
max_page_size = 50 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

View File

@@ -59,7 +59,6 @@ from pretix.base.settings import (
LazyI18nStringList, validate_event_settings, LazyI18nStringList, validate_event_settings,
) )
from pretix.base.signals import api_event_settings_fields from pretix.base.signals import api_event_settings_fields
from pretix.multidomain.urlreverse import build_absolute_uri
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -165,10 +164,6 @@ class EventSerializer(I18nAwareModelSerializer):
timezone = TimeZoneField(required=False, choices=[(a, a) for a in common_timezones]) timezone = TimeZoneField(required=False, choices=[(a, a) for a in common_timezones])
valid_keys = ValidKeysField(source='*', read_only=True) valid_keys = ValidKeysField(source='*', read_only=True)
best_availability_state = serializers.IntegerField(allow_null=True, 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: class Meta:
model = Event model = Event
@@ -176,7 +171,7 @@ class EventSerializer(I18nAwareModelSerializer):
'date_to', 'date_admission', 'is_public', 'presale_start', 'date_to', 'date_admission', 'is_public', 'presale_start',
'presale_end', 'location', 'geo_lat', 'geo_lon', 'has_subevents', 'meta_data', 'seating_plan', 'presale_end', 'location', 'geo_lat', 'geo_lon', 'has_subevents', 'meta_data', 'seating_plan',
'plugins', 'seat_category_mapping', 'timezone', 'item_meta_properties', 'valid_keys', 'plugins', 'seat_category_mapping', 'timezone', 'item_meta_properties', 'valid_keys',
'sales_channels', 'best_availability_state', 'public_url') 'sales_channels', 'best_availability_state')
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@@ -667,7 +662,6 @@ class EventSettingsSerializer(SettingsSerializer):
'show_times', 'show_times',
'show_items_outside_presale_period', 'show_items_outside_presale_period',
'display_net_prices', 'display_net_prices',
'hide_prices_from_attendees',
'presale_start_show_date', 'presale_start_show_date',
'locales', 'locales',
'locale', 'locale',
@@ -693,7 +687,6 @@ class EventSettingsSerializer(SettingsSerializer):
'frontpage_subevent_ordering', 'frontpage_subevent_ordering',
'event_list_type', 'event_list_type',
'event_list_available_only', 'event_list_available_only',
'event_calendar_future_only',
'frontpage_text', 'frontpage_text',
'event_info_text', 'event_info_text',
'attendee_names_asked', 'attendee_names_asked',
@@ -785,7 +778,6 @@ class EventSettingsSerializer(SettingsSerializer):
'change_allow_user_addons', 'change_allow_user_addons',
'change_allow_user_until', 'change_allow_user_until',
'change_allow_user_price', 'change_allow_user_price',
'change_allow_attendee',
'primary_color', 'primary_color',
'theme_color_success', 'theme_color_success',
'theme_color_danger', 'theme_color_danger',

View File

@@ -22,10 +22,8 @@
from django import forms from django import forms
from django.http import QueryDict from django.http import QueryDict
from rest_framework import serializers from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from pretix.base.exporter import OrganizerLevelExportMixin from pretix.base.exporter import OrganizerLevelExportMixin
from pretix.base.timeframes import DateFrameField, SerializerDateFrameField
class FormFieldWrapperField(serializers.Field): class FormFieldWrapperField(serializers.Field):
@@ -144,12 +142,6 @@ class JobRunSerializer(serializers.Serializer):
allow_null=not v.required, allow_null=not v.required,
validators=v.validators, validators=v.validators,
) )
elif isinstance(v, DateFrameField):
self.fields[k] = SerializerDateFrameField(
required=v.required,
allow_null=not v.required,
validators=v.validators,
)
else: else:
self.fields[k] = FormFieldWrapperField(form_field=v, required=v.required, allow_null=not v.required) self.fields[k] = FormFieldWrapperField(form_field=v, required=v.required, allow_null=not v.required)
@@ -159,40 +151,5 @@ class JobRunSerializer(serializers.Serializer):
for k, v in self.fields.items(): for k, v in self.fields.items():
if isinstance(v, serializers.ManyRelatedField) and k not in data: if isinstance(v, serializers.ManyRelatedField) and k not in data:
data[k] = [] 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) data = super().to_internal_value(data)
return 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)

View File

@@ -20,7 +20,6 @@
# <https://www.gnu.org/licenses/>. # <https://www.gnu.org/licenses/>.
# #
from django.conf import settings from django.conf import settings
from django.core.validators import URLValidator
from i18nfield.fields import I18nCharField, I18nTextField from i18nfield.fields import I18nCharField, I18nTextField
from i18nfield.strings import LazyI18nString from i18nfield.strings import LazyI18nString
from rest_framework.exceptions import ValidationError from rest_framework.exceptions import ValidationError
@@ -70,17 +69,3 @@ class I18nAwareModelSerializer(ModelSerializer):
I18nAwareModelSerializer.serializer_field_mapping[I18nCharField] = I18nField I18nAwareModelSerializer.serializer_field_mapping[I18nCharField] = I18nField
I18nAwareModelSerializer.serializer_field_mapping[I18nTextField] = I18nField I18nAwareModelSerializer.serializer_field_mapping[I18nTextField] = I18nField
class I18nURLField(I18nField):
def to_internal_value(self, value):
value = super().to_internal_value(value)
if not value:
return value
if isinstance(value.data, dict):
for v in value.data.values():
if v:
URLValidator()(v)
else:
URLValidator()(value.data)
return value

View File

@@ -52,7 +52,7 @@ from pretix.base.models import (
class InlineItemVariationSerializer(I18nAwareModelSerializer): class InlineItemVariationSerializer(I18nAwareModelSerializer):
price = serializers.DecimalField(read_only=True, decimal_places=2, max_digits=13, price = serializers.DecimalField(read_only=True, decimal_places=2, max_digits=10,
coerce_to_string=True) coerce_to_string=True)
meta_data = MetaDataField(required=False, source='*') meta_data = MetaDataField(required=False, source='*')
@@ -60,8 +60,8 @@ class InlineItemVariationSerializer(I18nAwareModelSerializer):
model = ItemVariation model = ItemVariation
fields = ('id', 'value', 'active', 'description', fields = ('id', 'value', 'active', 'description',
'position', 'default_price', 'price', 'original_price', 'require_approval', 'position', 'default_price', 'price', 'original_price', 'require_approval',
'require_membership', 'require_membership_types', 'require_membership_hidden', 'require_membership', 'require_membership_types',
'checkin_attention', 'available_from', 'available_until', 'require_membership_hidden', 'available_from', 'available_until',
'sales_channels', 'hide_without_voucher', 'meta_data') 'sales_channels', 'hide_without_voucher', 'meta_data')
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@@ -76,7 +76,7 @@ class InlineItemVariationSerializer(I18nAwareModelSerializer):
class ItemVariationSerializer(I18nAwareModelSerializer): class ItemVariationSerializer(I18nAwareModelSerializer):
price = serializers.DecimalField(read_only=True, decimal_places=2, max_digits=13, price = serializers.DecimalField(read_only=True, decimal_places=2, max_digits=10,
coerce_to_string=True) coerce_to_string=True)
meta_data = MetaDataField(required=False, source='*') meta_data = MetaDataField(required=False, source='*')
@@ -84,8 +84,8 @@ class ItemVariationSerializer(I18nAwareModelSerializer):
model = ItemVariation model = ItemVariation
fields = ('id', 'value', 'active', 'description', fields = ('id', 'value', 'active', 'description',
'position', 'default_price', 'price', 'original_price', 'require_approval', 'position', 'default_price', 'price', 'original_price', 'require_approval',
'require_membership', 'require_membership_types', 'require_membership_hidden', 'require_membership', 'require_membership_types',
'checkin_attention', 'available_from', 'available_until', 'require_membership_hidden', 'available_from', 'available_until',
'sales_channels', 'hide_without_voucher', 'meta_data') 'sales_channels', 'hide_without_voucher', 'meta_data')
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@@ -95,12 +95,8 @@ class ItemVariationSerializer(I18nAwareModelSerializer):
@transaction.atomic @transaction.atomic
def create(self, validated_data): def create(self, validated_data):
meta_data = validated_data.pop('meta_data', None) meta_data = validated_data.pop('meta_data', None)
require_membership_types = validated_data.pop('require_membership_types', [])
variation = ItemVariation.objects.create(**validated_data) variation = ItemVariation.objects.create(**validated_data)
if require_membership_types:
variation.require_membership_types.add(*require_membership_types)
# Meta data # Meta data
if meta_data is not None: if meta_data is not None:
for key, value in meta_data.items(): for key, value in meta_data.items():
@@ -234,7 +230,7 @@ class ItemSerializer(I18nAwareModelSerializer):
class Meta: class Meta:
model = Item model = Item
fields = ('id', 'category', 'name', 'internal_name', 'active', 'sales_channels', 'description', fields = ('id', 'category', 'name', 'internal_name', 'active', 'sales_channels', 'description',
'default_price', 'free_price', 'tax_rate', 'tax_rule', 'admission', 'personalized', 'default_price', 'free_price', 'tax_rate', 'tax_rule', 'admission',
'position', 'picture', 'available_from', 'available_until', 'position', 'picture', 'available_from', 'available_until',
'require_voucher', 'hide_without_voucher', 'allow_cancel', 'require_bundling', 'require_voucher', 'hide_without_voucher', 'allow_cancel', 'require_bundling',
'min_per_order', 'max_per_order', 'checkin_attention', 'has_variations', 'variations', 'min_per_order', 'max_per_order', 'checkin_attention', 'has_variations', 'variations',
@@ -242,9 +238,7 @@ class ItemSerializer(I18nAwareModelSerializer):
'show_quota_left', 'hidden_if_available', 'allow_waitinglist', 'issue_giftcard', 'meta_data', 'show_quota_left', 'hidden_if_available', 'allow_waitinglist', 'issue_giftcard', 'meta_data',
'require_membership', 'require_membership_types', 'require_membership_hidden', 'grant_membership_type', 'require_membership', 'require_membership_types', 'require_membership_hidden', 'grant_membership_type',
'grant_membership_duration_like_event', 'grant_membership_duration_days', 'grant_membership_duration_like_event', 'grant_membership_duration_days',
'grant_membership_duration_months', 'validity_mode', 'validity_fixed_from', 'validity_fixed_until', 'grant_membership_duration_months')
'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',) read_only_fields = ('has_variations',)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@@ -264,15 +258,6 @@ class ItemSerializer(I18nAwareModelSerializer):
Item.clean_per_order(data.get('min_per_order'), data.get('max_per_order')) 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')) 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('issue_giftcard'):
if data.get('tax_rule') and data.get('tax_rule').rate > 0: if data.get('tax_rule') and data.get('tax_rule').rate > 0:
raise ValidationError( raise ValidationError(
@@ -304,9 +289,9 @@ class ItemSerializer(I18nAwareModelSerializer):
if not self.instance: if not self.instance:
for addon_data in value: for addon_data in value:
ItemAddOn.clean_categories(self.context['event'], None, self.instance, addon_data['addon_category']) ItemAddOn.clean_categories(self.context['event'], None, self.instance, addon_data['addon_category'])
ItemAddOn.clean_min_count(addon_data.get('min_count', 0)) ItemAddOn.clean_min_count(addon_data['min_count'])
ItemAddOn.clean_max_count(addon_data.get('max_count', 0)) ItemAddOn.clean_max_count(addon_data['max_count'])
ItemAddOn.clean_max_min_count(addon_data.get('max_count', 0), addon_data.get('min_count', 0)) ItemAddOn.clean_max_min_count(addon_data['max_count'], addon_data['min_count'])
return value return value
@cached_property @cached_property

View File

@@ -52,8 +52,7 @@ from pretix.base.models import (
SubEvent, TaxRule, Voucher, SubEvent, TaxRule, Voucher,
) )
from pretix.base.models.orders import ( from pretix.base.models.orders import (
BlockedTicketSecret, CartPosition, OrderFee, OrderPayment, OrderRefund, CartPosition, OrderFee, OrderPayment, OrderRefund, RevokedTicketSecret,
RevokedTicketSecret,
) )
from pretix.base.pdf import get_images, get_variables from pretix.base.pdf import get_images, get_variables
from pretix.base.services.cart import error_messages from pretix.base.services.cart import error_messages
@@ -119,10 +118,6 @@ class InvoiceAddressSerializer(I18nAwareModelSerializer):
raise ValidationError( raise ValidationError(
{'name': ['Do not specify name if you specified name_parts.']} {'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'): if data.get('name_parts') and '_scheme' not in data.get('name_parts'):
data['name_parts']['_scheme'] = self.context['request'].event.settings.name_scheme data['name_parts']['_scheme'] = self.context['request'].event.settings.name_scheme
@@ -301,9 +296,7 @@ class FailedCheckinSerializer(I18nAwareModelSerializer):
class OrderDownloadsField(serializers.Field): class OrderDownloadsField(serializers.Field):
def to_representation(self, instance: Order): def to_representation(self, instance: Order):
if instance.status != Order.STATUS_PAID: if instance.status != Order.STATUS_PAID:
if instance.status != Order.STATUS_PENDING or instance.require_approval or ( if instance.status != Order.STATUS_PENDING or instance.require_approval or not instance.event.settings.ticket_download_pending:
not instance.valid_if_pending and not instance.event.settings.ticket_download_pending
):
return [] return []
request = self.context['request'] request = self.context['request']
@@ -327,9 +320,7 @@ class OrderDownloadsField(serializers.Field):
class PositionDownloadsField(serializers.Field): class PositionDownloadsField(serializers.Field):
def to_representation(self, instance: OrderPosition): def to_representation(self, instance: OrderPosition):
if instance.order.status != Order.STATUS_PAID: if instance.order.status != Order.STATUS_PAID:
if instance.order.status != Order.STATUS_PENDING or instance.order.require_approval or ( if instance.order.status != Order.STATUS_PENDING or instance.order.require_approval or not instance.order.event.settings.ticket_download_pending:
not instance.order.valid_if_pending and not instance.order.event.settings.ticket_download_pending
):
return [] return []
if not instance.generate_ticket: if not instance.generate_ticket:
return [] return []
@@ -381,9 +372,11 @@ class PdfDataSerializer(serializers.Field):
res['meta:' + k] = v res['meta:' + k] = v
if instance.variation_id: if instance.variation_id:
print(instance, instance.variation, instance.variation_id, instance.item)
if not hasattr(instance.variation, '_cached_meta_data'): if not hasattr(instance.variation, '_cached_meta_data'):
instance.variation.item = instance.item # saves some database lookups instance.variation.item = instance.item # saves some database lookups
instance.variation._cached_meta_data = instance.variation.meta_data instance.variation._cached_meta_data = instance.variation.meta_data
print(instance.variation._cached_meta_data.items())
for k, v in instance.variation._cached_meta_data.items(): for k, v in instance.variation._cached_meta_data.items():
res['itemmeta:' + k] = v res['itemmeta:' + k] = v
else: else:
@@ -440,12 +433,11 @@ class OrderPositionSerializer(I18nAwareModelSerializer):
fields = ('id', 'order', 'positionid', 'item', 'variation', 'price', 'attendee_name', 'attendee_name_parts', fields = ('id', 'order', 'positionid', 'item', 'variation', 'price', 'attendee_name', 'attendee_name_parts',
'company', 'street', 'zipcode', 'city', 'country', 'state', 'discount', 'company', 'street', 'zipcode', 'city', 'country', 'state', 'discount',
'attendee_email', 'voucher', 'tax_rate', 'tax_value', 'secret', 'addon_to', 'subevent', 'checkins', '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 = ( read_only_fields = (
'id', 'order', 'positionid', 'item', 'variation', 'price', 'voucher', 'tax_rate', 'tax_value', 'secret', 'id', 'order', 'positionid', 'item', 'variation', 'price', 'voucher', 'tax_rate', 'tax_value', 'secret',
'addon_to', 'subevent', 'checkins', 'downloads', 'answers', 'tax_rule', 'pseudonymization_id', 'pdf_data', 'addon_to', 'subevent', 'checkins', 'downloads', 'answers', 'tax_rule', 'pseudonymization_id', 'pdf_data',
'seat', 'canceled', 'discount', 'valid_from', 'valid_until', 'blocked' 'seat', 'canceled', 'discount',
) )
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@@ -467,7 +459,7 @@ class OrderPositionSerializer(I18nAwareModelSerializer):
class RequireAttentionField(serializers.Field): class RequireAttentionField(serializers.Field):
def to_representation(self, instance: OrderPosition): def to_representation(self, instance: OrderPosition):
return instance.require_checkin_attention return instance.order.checkin_attention or instance.item.checkin_attention
class AttendeeNameField(serializers.Field): class AttendeeNameField(serializers.Field):
@@ -512,7 +504,7 @@ class CheckinListOrderPositionSerializer(OrderPositionSerializer):
'company', 'street', 'zipcode', 'city', 'country', 'state', 'company', 'street', 'zipcode', 'city', 'country', 'state',
'attendee_email', 'voucher', 'tax_rate', 'tax_value', 'secret', 'addon_to', 'subevent', 'checkins', 'attendee_email', 'voucher', 'tax_rate', 'tax_value', 'secret', 'addon_to', 'subevent', 'checkins',
'downloads', 'answers', 'tax_rule', 'pseudonymization_id', 'pdf_data', 'seat', 'require_attention', 'downloads', 'answers', 'tax_rule', 'pseudonymization_id', 'pdf_data', 'seat', 'require_attention',
'order__status', 'valid_from', 'valid_until', 'blocked') 'order__status')
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@@ -626,7 +618,7 @@ class OrderSerializer(I18nAwareModelSerializer):
'code', 'status', 'testmode', 'secret', 'email', 'phone', 'locale', 'datetime', 'expires', 'payment_date', 'code', 'status', 'testmode', 'secret', 'email', 'phone', 'locale', 'datetime', 'expires', 'payment_date',
'payment_provider', 'fees', 'total', 'comment', 'custom_followup_at', 'invoice_address', 'positions', 'downloads', 'payment_provider', 'fees', 'total', 'comment', 'custom_followup_at', 'invoice_address', 'positions', 'downloads',
'checkin_attention', 'last_modified', 'payments', 'refunds', 'require_approval', 'sales_channel', 'checkin_attention', 'last_modified', 'payments', 'refunds', 'require_approval', 'sales_channel',
'url', 'customer', 'valid_if_pending' 'url', 'customer'
) )
read_only_fields = ( read_only_fields = (
'code', 'status', 'testmode', 'secret', 'datetime', 'expires', 'payment_date', 'code', 'status', 'testmode', 'secret', 'datetime', 'expires', 'payment_date',
@@ -681,8 +673,7 @@ class OrderSerializer(I18nAwareModelSerializer):
def update(self, instance, validated_data): def update(self, instance, validated_data):
# Even though all fields that shouldn't be edited are marked as read_only in the serializer # 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. # (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: if 'invoice_address' in validated_data:
iadata = validated_data.pop('invoice_address') iadata = validated_data.pop('invoice_address')
@@ -779,18 +770,16 @@ class OrderPositionCreateSerializer(I18nAwareModelSerializer):
attendee_name = serializers.CharField(required=False, allow_null=True) attendee_name = serializers.CharField(required=False, allow_null=True)
seat = serializers.CharField(required=False, allow_null=True) seat = serializers.CharField(required=False, allow_null=True)
price = serializers.DecimalField(required=False, allow_null=True, decimal_places=2, price = serializers.DecimalField(required=False, allow_null=True, decimal_places=2,
max_digits=13) max_digits=10)
voucher = serializers.SlugRelatedField(slug_field='code', queryset=Voucher.objects.none(), voucher = serializers.SlugRelatedField(slug_field='code', queryset=Voucher.objects.none(),
required=False, allow_null=True) required=False, allow_null=True)
country = CompatibleCountryField(source='*') country = CompatibleCountryField(source='*')
requested_valid_from = serializers.DateTimeField(required=False, allow_null=True)
class Meta: class Meta:
model = OrderPosition model = OrderPosition
fields = ('positionid', 'item', 'variation', 'price', 'attendee_name', 'attendee_name_parts', 'attendee_email', fields = ('positionid', 'item', 'variation', 'price', 'attendee_name', 'attendee_name_parts', 'attendee_email',
'company', 'street', 'zipcode', 'city', 'country', 'state', 'is_bundled', 'company', 'street', 'zipcode', 'city', 'country', 'state', 'is_bundled',
'secret', 'addon_to', 'subevent', 'answers', 'seat', 'voucher', 'valid_from', 'valid_until', 'secret', 'addon_to', 'subevent', 'answers', 'seat', 'voucher')
'requested_valid_from')
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@@ -852,10 +841,6 @@ class OrderPositionCreateSerializer(I18nAwareModelSerializer):
raise ValidationError( raise ValidationError(
{'attendee_name': ['Do not specify attendee_name if you specified attendee_name_parts.']} {'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'): 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 data['attendee_name_parts']['_scheme'] = self.context['request'].event.settings.name_scheme
@@ -948,8 +933,7 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
model = Order model = Order
fields = ('code', 'status', 'testmode', 'email', 'phone', 'locale', 'payment_provider', 'fees', 'comment', 'sales_channel', fields = ('code', 'status', 'testmode', 'email', 'phone', 'locale', 'payment_provider', 'fees', 'comment', 'sales_channel',
'invoice_address', 'positions', 'checkin_attention', 'payment_info', 'payment_date', 'consume_carts', '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): def validate_payment_provider(self, pp):
if pp is None: if pp is None:
@@ -1177,20 +1161,6 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
elif seated: elif seated:
errs[i]['seat'] = ['The specified product requires to choose a seat.'] 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: if not force:
for i, pos_data in enumerate(positions_data): for i, pos_data in enumerate(positions_data):
if pos_data.get('voucher'): if pos_data.get('voucher'):
@@ -1506,9 +1476,9 @@ class InvoiceSerializer(I18nAwareModelSerializer):
'invoice_to', 'invoice_to_company', 'invoice_to_name', 'invoice_to_street', 'invoice_to_zipcode', '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', 'invoice_to_city', 'invoice_to_state', 'invoice_to_country', 'invoice_to_vat_id', 'invoice_to_beneficiary',
'custom_field', 'date', 'refers', 'locale', 'custom_field', 'date', 'refers', 'locale',
'introductory_text', 'additional_text', 'payment_provider_text', 'payment_provider_stamp', 'introductory_text', 'additional_text', 'payment_provider_text', 'footer_text', 'lines',
'footer_text', 'lines', 'foreign_currency_display', 'foreign_currency_rate', 'foreign_currency_display', 'foreign_currency_rate', 'foreign_currency_rate_date',
'foreign_currency_rate_date', 'internal_reference') 'internal_reference')
class OrderPaymentCreateSerializer(I18nAwareModelSerializer): class OrderPaymentCreateSerializer(I18nAwareModelSerializer):
@@ -1554,10 +1524,3 @@ class RevokedTicketSecretSerializer(I18nAwareModelSerializer):
class Meta: class Meta:
model = RevokedTicketSecret model = RevokedTicketSecret
fields = ('id', 'secret', 'created') fields = ('id', 'secret', 'created')
class BlockedTicketSecretSerializer(I18nAwareModelSerializer):
class Meta:
model = BlockedTicketSecret
fields = ('id', 'secret', 'updated', 'blocked')

View File

@@ -24,7 +24,6 @@ import os
import pycountry import pycountry
from django.core.files import File from django.core.files import File
from django.core.validators import RegexValidator
from rest_framework import serializers from rest_framework import serializers
from rest_framework.exceptions import ValidationError from rest_framework.exceptions import ValidationError
@@ -47,14 +46,14 @@ class OrderPositionCreateForExistingOrderSerializer(OrderPositionCreateSerialize
attendee_name = serializers.CharField(required=False, allow_null=True) attendee_name = serializers.CharField(required=False, allow_null=True)
seat = serializers.CharField(required=False, allow_null=True) seat = serializers.CharField(required=False, allow_null=True)
price = serializers.DecimalField(required=False, allow_null=True, decimal_places=2, price = serializers.DecimalField(required=False, allow_null=True, decimal_places=2,
max_digits=13) max_digits=10)
country = CompatibleCountryField(source='*') country = CompatibleCountryField(source='*')
class Meta: class Meta:
model = OrderPosition model = OrderPosition
fields = ('order', 'item', 'variation', 'price', 'attendee_name', 'attendee_name_parts', 'attendee_email', fields = ('order', 'item', 'variation', 'price', 'attendee_name', 'attendee_name_parts', 'attendee_email',
'company', 'street', 'zipcode', 'city', 'country', 'state', 'company', 'street', 'zipcode', 'city', 'country', 'state',
'secret', 'addon_to', 'subevent', 'answers', 'seat', 'valid_from', 'valid_until') 'secret', 'addon_to', 'subevent', 'answers', 'seat')
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@@ -90,8 +89,6 @@ class OrderPositionCreateForExistingOrderSerializer(OrderPositionCreateSerialize
addon_to=validated_data.get('addon_to'), addon_to=validated_data.get('addon_to'),
subevent=validated_data.get('subevent'), subevent=validated_data.get('subevent'),
seat=validated_data.get('seat'), 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): if self.context.get('commit', True):
ocm.commit() ocm.commit()
@@ -161,14 +158,12 @@ class OrderPositionInfoPatchSerializer(serializers.ModelSerializer):
a.question_id: a for a in instance.answers.all() a.question_id: a for a in instance.answers.all()
} }
for answ_data in answers_data: for answ_data in answers_data:
if not answ_data.get('answer'):
continue
options = answ_data.pop('options', []) options = answ_data.pop('options', [])
if answ_data['question'].pk in qs_seen: if answ_data['question'].pk in qs_seen:
raise ValidationError(f'Question {answ_data["question"]} was sent twice.') raise ValidationError(f'Question {answ_data["question"]} was sent twice.')
if answ_data['question'].pk in answercache: if answ_data['question'].pk in answercache:
a = answercache[answ_data['question'].pk] a = answercache[answ_data['question'].pk]
if isinstance(answ_data.get('answer'), File): if isinstance(answ_data['answer'], File):
a.file.save(answ_data['answer'].name, answ_data['answer'], save=False) a.file.save(answ_data['answer'].name, answ_data['answer'], save=False)
a.answer = 'file://' + a.file.name a.answer = 'file://' + a.file.name
elif a.answer.startswith('file://') and answ_data['answer'] == "file:keep": elif a.answer.startswith('file://') and answ_data['answer'] == "file:keep":
@@ -178,7 +173,7 @@ class OrderPositionInfoPatchSerializer(serializers.ModelSerializer):
setattr(a, attr, value) setattr(a, attr, value)
a.save() a.save()
else: else:
if isinstance(answ_data.get('answer'), File): if isinstance(answ_data['answer'], File):
an = answ_data.pop('answer') an = answ_data.pop('answer')
a = instance.answers.create(**answ_data, answer='') a = instance.answers.create(**answ_data, answer='')
a.file.save(os.path.basename(an.name), an, save=False) a.file.save(os.path.basename(an.name), an, save=False)
@@ -201,7 +196,7 @@ class OrderPositionChangeSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = OrderPosition model = OrderPosition
fields = ( fields = (
'item', 'variation', 'subevent', 'seat', 'price', 'tax_rule', 'valid_from', 'valid_until' 'item', 'variation', 'subevent', 'seat', 'price', 'tax_rule',
) )
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@@ -267,8 +262,6 @@ class OrderPositionChangeSerializer(serializers.ModelSerializer):
price = validated_data.get('price', instance.price) price = validated_data.get('price', instance.price)
seat = validated_data.get('seat', current_seat) seat = validated_data.get('seat', current_seat)
tax_rule = validated_data.get('tax_rule', instance.tax_rule) 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 change_item = None
if item != instance.item or variation != instance.variation: if item != instance.item or variation != instance.variation:
@@ -295,12 +288,6 @@ class OrderPositionChangeSerializer(serializers.ModelSerializer):
if tax_rule != instance.tax_rule: if tax_rule != instance.tax_rule:
ocm.change_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): if self.context.get('commit', True):
ocm.commit() ocm.commit()
instance.refresh_from_db() instance.refresh_from_db()
@@ -434,7 +421,3 @@ class OrderChangeOperationSerializer(serializers.Serializer):
seen_positions.add(d['fee']) seen_positions.add(d['fee'])
return data return data
class BlockNameSerializer(serializers.Serializer):
name = serializers.CharField(validators=[RegexValidator('^(admin|api:[a-zA-Z0-9._]+)$')])

View File

@@ -41,21 +41,15 @@ from pretix.base.models import (
from pretix.base.models.seating import SeatingPlanLayoutValidator from pretix.base.models.seating import SeatingPlanLayoutValidator
from pretix.base.services.mail import SendMailException, mail from pretix.base.services.mail import SendMailException, mail
from pretix.base.settings import validate_organizer_settings from pretix.base.settings import validate_organizer_settings
from pretix.helpers.urls import build_absolute_uri as build_global_uri from pretix.helpers.urls import build_absolute_uri
from pretix.multidomain.urlreverse import build_absolute_uri
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class OrganizerSerializer(I18nAwareModelSerializer): 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: class Meta:
model = Organizer model = Organizer
fields = ('name', 'slug', 'public_url') fields = ('name', 'slug')
class SeatingPlanSerializer(I18nAwareModelSerializer): class SeatingPlanSerializer(I18nAwareModelSerializer):
@@ -85,13 +79,6 @@ class CustomerSerializer(I18nAwareModelSerializer):
validated_data['external_identifier'] = instance.external_identifier validated_data['external_identifier'] = instance.external_identifier
return super().update(instance, validated_data) 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): class CustomerCreateSerializer(CustomerSerializer):
send_email = serializers.BooleanField(default=False, required=False, allow_null=True) send_email = serializers.BooleanField(default=False, required=False, allow_null=True)
@@ -128,7 +115,7 @@ class MembershipSerializer(I18nAwareModelSerializer):
class GiftCardSerializer(I18nAwareModelSerializer): class GiftCardSerializer(I18nAwareModelSerializer):
value = serializers.DecimalField(max_digits=13, decimal_places=2, min_value=Decimal('0.00')) value = serializers.DecimalField(max_digits=10, decimal_places=2, min_value=Decimal('0.00'))
def validate(self, data): def validate(self, data):
data = super().validate(data) data = super().validate(data)
@@ -233,7 +220,7 @@ class TeamInviteSerializer(serializers.ModelSerializer):
'user': self, 'user': self,
'organizer': self.context['organizer'].name, 'organizer': self.context['organizer'].name,
'team': instance.team.name, 'team': instance.team.name,
'url': build_global_uri('control:auth.invite', kwargs={ 'url': build_absolute_uri('control:auth.invite', kwargs={
'token': instance.token 'token': instance.token
}) })
}, },

View File

@@ -55,7 +55,7 @@ class WebHookSerializer(I18nAwareModelSerializer):
class Meta: class Meta:
model = WebHook model = WebHook
fields = ('id', 'enabled', 'target_url', 'all_events', 'limit_events', 'action_types', 'comment') fields = ('id', 'enabled', 'target_url', 'all_events', 'limit_events', 'action_types')
def validate(self, data): def validate(self, data):
data = super().validate(data) data = super().validate(data)

View File

@@ -81,7 +81,6 @@ event_router.register(r'orders', order.OrderViewSet)
event_router.register(r'orderpositions', order.OrderPositionViewSet) event_router.register(r'orderpositions', order.OrderPositionViewSet)
event_router.register(r'invoices', order.InvoiceViewSet) event_router.register(r'invoices', order.InvoiceViewSet)
event_router.register(r'revokedsecrets', order.RevokedSecretViewSet, basename='revokedsecrets') 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'taxrules', event.TaxRuleViewSet)
event_router.register(r'waitinglistentries', waitinglist.WaitingListViewSet) event_router.register(r'waitinglistentries', waitinglist.WaitingListViewSet)
event_router.register(r'checkinlists', checkin.CheckinListViewSet) event_router.register(r'checkinlists', checkin.CheckinListViewSet)

View File

@@ -24,11 +24,10 @@ from calendar import timegm
from django.db.models import Max from django.db.models import Max
from django.http import HttpResponse from django.http import HttpResponse
from django.utils.http import http_date, parse_http_date_safe 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(TotalOrderingFilter): class RichOrderingFilter(OrderingFilter):
def filter_queryset(self, request, queryset, view): def filter_queryset(self, request, queryset, view):
ordering = self.get_ordering(request, queryset, view) ordering = self.get_ordering(request, queryset, view)

View File

@@ -29,11 +29,11 @@ from django.utils.translation import gettext as _
from rest_framework import status, viewsets from rest_framework import status, viewsets
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError from rest_framework.exceptions import ValidationError
from rest_framework.filters import OrderingFilter
from rest_framework.mixins import CreateModelMixin, DestroyModelMixin from rest_framework.mixins import CreateModelMixin, DestroyModelMixin
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.serializers import as_serializer_error from rest_framework.serializers import as_serializer_error
from pretix.api.pagination import TotalOrderingFilter
from pretix.api.serializers.cart import ( from pretix.api.serializers.cart import (
CartPositionCreateSerializer, CartPositionSerializer, CartPositionCreateSerializer, CartPositionSerializer,
) )
@@ -47,7 +47,7 @@ from pretix.base.services.locking import NoLockManager
class CartPositionViewSet(CreateModelMixin, DestroyModelMixin, viewsets.ReadOnlyModelViewSet): class CartPositionViewSet(CreateModelMixin, DestroyModelMixin, viewsets.ReadOnlyModelViewSet):
serializer_class = CartPositionSerializer serializer_class = CartPositionSerializer
queryset = CartPosition.objects.none() queryset = CartPosition.objects.none()
filter_backends = (TotalOrderingFilter,) filter_backends = (OrderingFilter,)
ordering = ('datetime',) ordering = ('datetime',)
ordering_fields = ('datetime', 'cart_id') ordering_fields = ('datetime', 'cart_id')
lookup_field = 'id' lookup_field = 'id'

View File

@@ -93,10 +93,8 @@ with scopes_disabled():
class CheckinListViewSet(viewsets.ModelViewSet): class CheckinListViewSet(viewsets.ModelViewSet):
serializer_class = CheckinListSerializer serializer_class = CheckinListSerializer
queryset = CheckinList.objects.none() queryset = CheckinList.objects.none()
filter_backends = (DjangoFilterBackend, RichOrderingFilter) filter_backends = (DjangoFilterBackend,)
filterset_class = CheckinListFilter filterset_class = CheckinListFilter
ordering = ('subevent__date_from', 'name', 'id')
ordering_fields = ('subevent__date_from', 'id', 'name',)
def _get_permission_name(self, request): def _get_permission_name(self, request):
if request.path.endswith('/failed_checkins/'): if request.path.endswith('/failed_checkins/'):
@@ -273,13 +271,7 @@ with scopes_disabled():
def check_rules_qs(self, queryset, name, value): def check_rules_qs(self, queryset, name, value):
if not self.checkinlist.rules: if not self.checkinlist.rules:
return queryset return queryset
return queryset.filter( return queryset.filter(SQLLogic(self.checkinlist).apply(self.checkinlist.rules))
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): def _handle_file_upload(data, user, auth):
@@ -331,13 +323,7 @@ def _checkin_list_position_queryset(checkinlists, ignore_status=False, ignore_pr
if checkinlist.subevent: if checkinlist.subevent:
list_q &= Q(subevent=checkinlist.subevent) list_q &= Q(subevent=checkinlist.subevent)
if not ignore_status: if not ignore_status:
if checkinlist.include_pending: list_q &= Q(order__status__in=[Order.STATUS_PAID, Order.STATUS_PENDING] if checkinlist.include_pending else [Order.STATUS_PAID])
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: if not checkinlist.all_products and not ignore_products:
list_q &= Q(item__in=checkinlist.limit_products.values_list('id', flat=True)) list_q &= Q(item__in=checkinlist.limit_products.values_list('id', flat=True))
lists_qs.append(list_q) lists_qs.append(list_q)
@@ -594,7 +580,7 @@ def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force,
'status': 'error', 'status': 'error',
'reason': Checkin.REASON_AMBIGUOUS, 'reason': Checkin.REASON_AMBIGUOUS,
'reason_explanation': None, 'reason_explanation': None,
'require_attention': op.require_checkin_attention, 'require_attention': op.item.checkin_attention or op.order.checkin_attention,
'position': CheckinListOrderPositionSerializer(op, context=_make_context(context, op.order.event)).data, 'position': CheckinListOrderPositionSerializer(op, context=_make_context(context, op.order.event)).data,
'list': MiniCheckinListSerializer(list_by_event[op.order.event_id]).data, 'list': MiniCheckinListSerializer(list_by_event[op.order.event_id]).data,
}, status=400) }, status=400)
@@ -639,7 +625,7 @@ def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force,
except RequiredQuestionsError as e: except RequiredQuestionsError as e:
return Response({ return Response({
'status': 'incomplete', 'status': 'incomplete',
'require_attention': op.require_checkin_attention, 'require_attention': op.item.checkin_attention or op.order.checkin_attention,
'position': CheckinListOrderPositionSerializer(op, context=_make_context(context, op.order.event)).data, 'position': CheckinListOrderPositionSerializer(op, context=_make_context(context, op.order.event)).data,
'questions': [ 'questions': [
QuestionSerializer(q).data for q in e.questions QuestionSerializer(q).data for q in e.questions
@@ -668,14 +654,14 @@ def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force,
'status': 'error', 'status': 'error',
'reason': e.code, 'reason': e.code,
'reason_explanation': e.reason, 'reason_explanation': e.reason,
'require_attention': op.require_checkin_attention, 'require_attention': op.item.checkin_attention or op.order.checkin_attention,
'position': CheckinListOrderPositionSerializer(op, context=_make_context(context, op.order.event)).data, 'position': CheckinListOrderPositionSerializer(op, context=_make_context(context, op.order.event)).data,
'list': MiniCheckinListSerializer(list_by_event[op.order.event_id]).data, 'list': MiniCheckinListSerializer(list_by_event[op.order.event_id]).data,
}, status=400) }, status=400)
else: else:
return Response({ return Response({
'status': 'ok', 'status': 'ok',
'require_attention': op.require_checkin_attention, 'require_attention': op.item.checkin_attention or op.order.checkin_attention,
'position': CheckinListOrderPositionSerializer(op, context=_make_context(context, op.order.event)).data, 'position': CheckinListOrderPositionSerializer(op, context=_make_context(context, op.order.event)).data,
'list': MiniCheckinListSerializer(list_by_event[op.order.event_id]).data, 'list': MiniCheckinListSerializer(list_by_event[op.order.event_id]).data,
}, status=201) }, status=201)
@@ -696,7 +682,7 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = CheckinListOrderPositionSerializer serializer_class = CheckinListOrderPositionSerializer
queryset = OrderPosition.all.none() queryset = OrderPosition.all.none()
filter_backends = (ExtendedBackend, RichOrderingFilter) filter_backends = (ExtendedBackend, RichOrderingFilter)
ordering = (F('attendee_name_cached').asc(nulls_last=True), 'pk') ordering = (F('attendee_name_cached').asc(nulls_last=True), 'positionid')
ordering_fields = ( ordering_fields = (
'order__code', 'order__datetime', 'positionid', 'attendee_name', 'order__code', 'order__datetime', 'positionid', 'attendee_name',
'last_checked_in', 'order__email', 'last_checked_in', 'order__email',

View File

@@ -36,8 +36,8 @@ from django_filters.rest_framework import DjangoFilterBackend, FilterSet
from django_scopes import scopes_disabled from django_scopes import scopes_disabled
from rest_framework import viewsets from rest_framework import viewsets
from rest_framework.exceptions import PermissionDenied 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.serializers.discount import DiscountSerializer
from pretix.api.views import ConditionalListView from pretix.api.views import ConditionalListView
from pretix.base.models import CartPosition, Discount from pretix.base.models import CartPosition, Discount
@@ -52,7 +52,7 @@ with scopes_disabled():
class DiscountViewSet(ConditionalListView, viewsets.ModelViewSet): class DiscountViewSet(ConditionalListView, viewsets.ModelViewSet):
serializer_class = DiscountSerializer serializer_class = DiscountSerializer
queryset = Discount.objects.none() queryset = Discount.objects.none()
filter_backends = (DjangoFilterBackend, TotalOrderingFilter) filter_backends = (DjangoFilterBackend, OrderingFilter)
filterset_class = DiscountFilter filterset_class = DiscountFilter
ordering_fields = ('id', 'position') ordering_fields = ('id', 'position')
ordering = ('position', 'id') ordering = ('position', 'id')

View File

@@ -39,12 +39,11 @@ from django.db.models import Prefetch, ProtectedError, Q
from django.utils.timezone import now from django.utils.timezone import now
from django_filters.rest_framework import DjangoFilterBackend, FilterSet from django_filters.rest_framework import DjangoFilterBackend, FilterSet
from django_scopes import scopes_disabled from django_scopes import scopes_disabled
from rest_framework import serializers, views, viewsets from rest_framework import filters, serializers, views, viewsets
from rest_framework.exceptions import PermissionDenied, ValidationError from rest_framework.exceptions import PermissionDenied, ValidationError
from rest_framework.response import Response from rest_framework.response import Response
from pretix.api.auth.permission import EventCRUDPermission from pretix.api.auth.permission import EventCRUDPermission
from pretix.api.pagination import TotalOrderingFilter
from pretix.api.serializers.event import ( from pretix.api.serializers.event import (
CloneEventSerializer, DeviceEventSettingsSerializer, EventSerializer, CloneEventSerializer, DeviceEventSettingsSerializer, EventSerializer,
EventSettingsSerializer, SubEventSerializer, TaxRuleSerializer, EventSettingsSerializer, SubEventSerializer, TaxRuleSerializer,
@@ -72,7 +71,7 @@ with scopes_disabled():
class Meta: class Meta:
model = Event model = Event
fields = ['is_public', 'live', 'has_subevents', 'testmode'] fields = ['is_public', 'live', 'has_subevents']
def ends_after_qs(self, queryset, name, value): def ends_after_qs(self, queryset, name, value):
expr = ( expr = (
@@ -128,7 +127,7 @@ class EventViewSet(viewsets.ModelViewSet):
lookup_url_kwarg = 'event' lookup_url_kwarg = 'event'
lookup_value_regex = '[^/]+' lookup_value_regex = '[^/]+'
permission_classes = (EventCRUDPermission,) permission_classes = (EventCRUDPermission,)
filter_backends = (DjangoFilterBackend, TotalOrderingFilter) filter_backends = (DjangoFilterBackend, filters.OrderingFilter)
ordering = ('slug',) ordering = ('slug',)
ordering_fields = ('date_from', 'slug') ordering_fields = ('date_from', 'slug')
filterset_class = EventFilter filterset_class = EventFilter
@@ -380,7 +379,7 @@ class SubEventViewSet(ConditionalListView, viewsets.ModelViewSet):
serializer_class = SubEventSerializer serializer_class = SubEventSerializer
queryset = SubEvent.objects.none() queryset = SubEvent.objects.none()
write_permission = 'can_change_event_settings' write_permission = 'can_change_event_settings'
filter_backends = (DjangoFilterBackend, TotalOrderingFilter) filter_backends = (DjangoFilterBackend, filters.OrderingFilter)
filterset_class = SubEventFilter filterset_class = SubEventFilter
ordering = ('date_from',) ordering = ('date_from',)
ordering_fields = ('id', 'date_from', 'last_modified') ordering_fields = ('id', 'date_from', 'last_modified')

View File

@@ -41,9 +41,9 @@ from django_scopes import scopes_disabled
from rest_framework import viewsets from rest_framework import viewsets
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.exceptions import PermissionDenied from rest_framework.exceptions import PermissionDenied
from rest_framework.filters import OrderingFilter
from rest_framework.response import Response from rest_framework.response import Response
from pretix.api.pagination import TotalOrderingFilter
from pretix.api.serializers.item import ( from pretix.api.serializers.item import (
ItemAddOnSerializer, ItemBundleSerializer, ItemCategorySerializer, ItemAddOnSerializer, ItemBundleSerializer, ItemCategorySerializer,
ItemSerializer, ItemVariationSerializer, QuestionOptionSerializer, ItemSerializer, ItemVariationSerializer, QuestionOptionSerializer,
@@ -75,7 +75,7 @@ with scopes_disabled():
class ItemViewSet(ConditionalListView, viewsets.ModelViewSet): class ItemViewSet(ConditionalListView, viewsets.ModelViewSet):
serializer_class = ItemSerializer serializer_class = ItemSerializer
queryset = Item.objects.none() queryset = Item.objects.none()
filter_backends = (DjangoFilterBackend, TotalOrderingFilter) filter_backends = (DjangoFilterBackend, OrderingFilter)
ordering_fields = ('id', 'position') ordering_fields = ('id', 'position')
ordering = ('position', 'id') ordering = ('position', 'id')
filterset_class = ItemFilter filterset_class = ItemFilter
@@ -138,7 +138,7 @@ class ItemViewSet(ConditionalListView, viewsets.ModelViewSet):
class ItemVariationViewSet(viewsets.ModelViewSet): class ItemVariationViewSet(viewsets.ModelViewSet):
serializer_class = ItemVariationSerializer serializer_class = ItemVariationSerializer
queryset = ItemVariation.objects.none() queryset = ItemVariation.objects.none()
filter_backends = (DjangoFilterBackend, TotalOrderingFilter,) filter_backends = (DjangoFilterBackend, OrderingFilter,)
ordering_fields = ('id', 'position') ordering_fields = ('id', 'position')
ordering = ('id',) ordering = ('id',)
permission = None permission = None
@@ -208,7 +208,7 @@ class ItemVariationViewSet(viewsets.ModelViewSet):
class ItemBundleViewSet(viewsets.ModelViewSet): class ItemBundleViewSet(viewsets.ModelViewSet):
serializer_class = ItemBundleSerializer serializer_class = ItemBundleSerializer
queryset = ItemBundle.objects.none() queryset = ItemBundle.objects.none()
filter_backends = (DjangoFilterBackend, TotalOrderingFilter,) filter_backends = (DjangoFilterBackend, OrderingFilter,)
ordering_fields = ('id',) ordering_fields = ('id',)
ordering = ('id',) ordering = ('id',)
permission = None permission = None
@@ -260,7 +260,7 @@ class ItemBundleViewSet(viewsets.ModelViewSet):
class ItemAddOnViewSet(viewsets.ModelViewSet): class ItemAddOnViewSet(viewsets.ModelViewSet):
serializer_class = ItemAddOnSerializer serializer_class = ItemAddOnSerializer
queryset = ItemAddOn.objects.none() queryset = ItemAddOn.objects.none()
filter_backends = (DjangoFilterBackend, TotalOrderingFilter,) filter_backends = (DjangoFilterBackend, OrderingFilter,)
ordering_fields = ('id', 'position') ordering_fields = ('id', 'position')
ordering = ('id',) ordering = ('id',)
permission = None permission = None
@@ -318,7 +318,7 @@ class ItemCategoryFilter(FilterSet):
class ItemCategoryViewSet(ConditionalListView, viewsets.ModelViewSet): class ItemCategoryViewSet(ConditionalListView, viewsets.ModelViewSet):
serializer_class = ItemCategorySerializer serializer_class = ItemCategorySerializer
queryset = ItemCategory.objects.none() queryset = ItemCategory.objects.none()
filter_backends = (DjangoFilterBackend, TotalOrderingFilter) filter_backends = (DjangoFilterBackend, OrderingFilter)
filterset_class = ItemCategoryFilter filterset_class = ItemCategoryFilter
ordering_fields = ('id', 'position') ordering_fields = ('id', 'position')
ordering = ('position', 'id') ordering = ('position', 'id')
@@ -373,7 +373,7 @@ with scopes_disabled():
class QuestionViewSet(ConditionalListView, viewsets.ModelViewSet): class QuestionViewSet(ConditionalListView, viewsets.ModelViewSet):
serializer_class = QuestionSerializer serializer_class = QuestionSerializer
queryset = Question.objects.none() queryset = Question.objects.none()
filter_backends = (DjangoFilterBackend, TotalOrderingFilter) filter_backends = (DjangoFilterBackend, OrderingFilter)
filterset_class = QuestionFilter filterset_class = QuestionFilter
ordering_fields = ('id', 'position') ordering_fields = ('id', 'position')
ordering = ('position', 'id') ordering = ('position', 'id')
@@ -418,7 +418,7 @@ class QuestionViewSet(ConditionalListView, viewsets.ModelViewSet):
class QuestionOptionViewSet(viewsets.ModelViewSet): class QuestionOptionViewSet(viewsets.ModelViewSet):
serializer_class = QuestionOptionSerializer serializer_class = QuestionOptionSerializer
queryset = QuestionOption.objects.none() queryset = QuestionOption.objects.none()
filter_backends = (DjangoFilterBackend, TotalOrderingFilter,) filter_backends = (DjangoFilterBackend, OrderingFilter,)
ordering_fields = ('id', 'position') ordering_fields = ('id', 'position')
ordering = ('position',) ordering = ('position',)
permission = None permission = None
@@ -475,7 +475,7 @@ with scopes_disabled():
class QuotaViewSet(ConditionalListView, viewsets.ModelViewSet): class QuotaViewSet(ConditionalListView, viewsets.ModelViewSet):
serializer_class = QuotaSerializer serializer_class = QuotaSerializer
queryset = Quota.objects.none() queryset = Quota.objects.none()
filter_backends = (DjangoFilterBackend, TotalOrderingFilter,) filter_backends = (DjangoFilterBackend, OrderingFilter,)
filterset_class = QuotaFilter filterset_class = QuotaFilter
ordering_fields = ('id', 'size') ordering_fields = ('id', 'size')
ordering = ('id',) ordering = ('id',)

View File

@@ -34,7 +34,6 @@ from oauth2_provider.views import (
from pretix.api.models import OAuthApplication from pretix.api.models import OAuthApplication
from pretix.base.models import Organizer from pretix.base.models import Organizer
from pretix.control.views.user import RecentAuthenticationRequiredMixin
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -55,7 +54,7 @@ class OAuthAllowForm(AllowForm):
del self.fields['organizers'] del self.fields['organizers']
class AuthorizationView(RecentAuthenticationRequiredMixin, BaseAuthorizationView): class AuthorizationView(BaseAuthorizationView):
template_name = "pretixcontrol/auth/oauth_authorization.html" template_name = "pretixcontrol/auth/oauth_authorization.html"
form_class = OAuthAllowForm form_class = OAuthAllowForm
@@ -112,7 +111,6 @@ class AuthorizationView(RecentAuthenticationRequiredMixin, BaseAuthorizationView
self.request.user.log_action('pretix.user.oauth.authorized', user=self.request.user, data={ self.request.user.log_action('pretix.user.oauth.authorized', user=self.request.user, data={
'application_id': application.pk, 'application_id': application.pk,
'application_name': application.name, 'application_name': application.name,
'organizers': [o.pk for o in form.cleaned_data.get("organizers")] if form.cleaned_data.get("organizers") else []
}) })
return self.redirect(self.success_url, application) return self.redirect(self.success_url, application)

View File

@@ -43,21 +43,21 @@ from rest_framework.decorators import action
from rest_framework.exceptions import ( from rest_framework.exceptions import (
APIException, NotFound, PermissionDenied, ValidationError, APIException, NotFound, PermissionDenied, ValidationError,
) )
from rest_framework.filters import OrderingFilter
from rest_framework.mixins import CreateModelMixin from rest_framework.mixins import CreateModelMixin
from rest_framework.response import Response from rest_framework.response import Response
from pretix.api.models import OAuthAccessToken from pretix.api.models import OAuthAccessToken
from pretix.api.pagination import TotalOrderingFilter
from pretix.api.serializers.order import ( from pretix.api.serializers.order import (
BlockedTicketSecretSerializer, InvoiceSerializer, OrderCreateSerializer, InvoiceSerializer, OrderCreateSerializer, OrderPaymentCreateSerializer,
OrderPaymentCreateSerializer, OrderPaymentSerializer, OrderPaymentSerializer, OrderPositionSerializer,
OrderPositionSerializer, OrderRefundCreateSerializer, OrderRefundCreateSerializer, OrderRefundSerializer, OrderSerializer,
OrderRefundSerializer, OrderSerializer, PriceCalcSerializer, PriceCalcSerializer, RevokedTicketSecretSerializer,
RevokedTicketSecretSerializer, SimulatedOrderSerializer, SimulatedOrderSerializer,
) )
from pretix.api.serializers.orderchange import ( from pretix.api.serializers.orderchange import (
BlockNameSerializer, OrderChangeOperationSerializer, OrderChangeOperationSerializer, OrderFeeChangeSerializer,
OrderFeeChangeSerializer, OrderPositionChangeSerializer, OrderPositionChangeSerializer,
OrderPositionCreateForExistingOrderSerializer, OrderPositionCreateForExistingOrderSerializer,
OrderPositionInfoPatchSerializer, OrderPositionInfoPatchSerializer,
) )
@@ -70,9 +70,7 @@ from pretix.base.models import (
OrderRefund, Quota, SubEvent, SubEventMetaValue, TaxRule, TeamAPIToken, OrderRefund, Quota, SubEvent, SubEventMetaValue, TaxRule, TeamAPIToken,
generate_secret, generate_secret,
) )
from pretix.base.models.orders import ( from pretix.base.models.orders import QuestionAnswer, RevokedTicketSecret
BlockedTicketSecret, QuestionAnswer, RevokedTicketSecret,
)
from pretix.base.payment import PaymentException from pretix.base.payment import PaymentException
from pretix.base.pdf import get_images from pretix.base.pdf import get_images
from pretix.base.secrets import assign_ticket_secret from pretix.base.secrets import assign_ticket_secret
@@ -105,9 +103,9 @@ with scopes_disabled():
subevent_after = django_filters.IsoDateTimeFilter(method='subevent_after_qs') subevent_after = django_filters.IsoDateTimeFilter(method='subevent_after_qs')
subevent_before = django_filters.IsoDateTimeFilter(method='subevent_before_qs') subevent_before = django_filters.IsoDateTimeFilter(method='subevent_before_qs')
search = django_filters.CharFilter(method='search_qs') search = django_filters.CharFilter(method='search_qs')
item = django_filters.CharFilter(field_name='all_positions', lookup_expr='item_id', distinct=True) item = django_filters.CharFilter(field_name='all_positions', lookup_expr='item_id')
variation = django_filters.CharFilter(field_name='all_positions', lookup_expr='variation_id', distinct=True) variation = django_filters.CharFilter(field_name='all_positions', lookup_expr='variation_id')
subevent = django_filters.CharFilter(field_name='all_positions', lookup_expr='subevent_id', distinct=True) subevent = django_filters.CharFilter(field_name='all_positions', lookup_expr='subevent_id')
class Meta: class Meta:
model = Order model = Order
@@ -181,7 +179,7 @@ with scopes_disabled():
class OrderViewSet(viewsets.ModelViewSet): class OrderViewSet(viewsets.ModelViewSet):
serializer_class = OrderSerializer serializer_class = OrderSerializer
queryset = Order.objects.none() queryset = Order.objects.none()
filter_backends = (DjangoFilterBackend, TotalOrderingFilter) filter_backends = (DjangoFilterBackend, OrderingFilter)
ordering = ('datetime',) ordering = ('datetime',)
ordering_fields = ('datetime', 'code', 'status', 'last_modified') ordering_fields = ('datetime', 'code', 'status', 'last_modified')
filterset_class = OrderFilter filterset_class = OrderFilter
@@ -289,7 +287,7 @@ class OrderViewSet(viewsets.ModelViewSet):
if order.status in (Order.STATUS_CANCELED, Order.STATUS_EXPIRED): if order.status in (Order.STATUS_CANCELED, Order.STATUS_EXPIRED):
raise PermissionDenied("Downloads are not available for canceled or expired orders.") raise PermissionDenied("Downloads are not available for canceled or expired orders.")
if order.status == Order.STATUS_PENDING and not (order.valid_if_pending or request.event.settings.ticket_download_pending): if order.status == Order.STATUS_PENDING and not request.event.settings.ticket_download_pending:
raise PermissionDenied("Downloads are not available for pending orders.") raise PermissionDenied("Downloads are not available for pending orders.")
ct = CachedCombinedTicket.objects.filter( ct = CachedCombinedTicket.objects.filter(
@@ -765,16 +763,6 @@ 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'): if 'email' in self.request.data and serializer.instance.email != self.request.data.get('email'):
serializer.instance.email_known_to_work = False serializer.instance.email_known_to_work = False
serializer.instance.log_action( serializer.instance.log_action(
@@ -1195,7 +1183,7 @@ class OrderPositionViewSet(viewsets.ModelViewSet):
if pos.order.status in (Order.STATUS_CANCELED, Order.STATUS_EXPIRED): if pos.order.status in (Order.STATUS_CANCELED, Order.STATUS_EXPIRED):
raise PermissionDenied("Downloads are not available for canceled or expired orders.") raise PermissionDenied("Downloads are not available for canceled or expired orders.")
if pos.order.status == Order.STATUS_PENDING and not (pos.order.valid_if_pending or request.event.settings.ticket_download_pending): if pos.order.status == Order.STATUS_PENDING and not request.event.settings.ticket_download_pending:
raise PermissionDenied("Downloads are not available for pending orders.") raise PermissionDenied("Downloads are not available for pending orders.")
if not pos.generate_ticket: if not pos.generate_ticket:
raise PermissionDenied("Downloads are not enabled for this product.") raise PermissionDenied("Downloads are not enabled for this product.")
@@ -1237,54 +1225,6 @@ class OrderPositionViewSet(viewsets.ModelViewSet):
raise ValidationError(str(e)) raise ValidationError(str(e))
return self.retrieve(request, [], **kwargs) 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): def perform_destroy(self, instance):
try: try:
ocm = OrderChangeManager( ocm = OrderChangeManager(
@@ -1507,7 +1447,7 @@ class PaymentViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
@action(detail=True, methods=['POST']) @action(detail=True, methods=['POST'])
def refund(self, request, **kwargs): def refund(self, request, **kwargs):
payment = self.get_object() payment = self.get_object()
amount = serializers.DecimalField(max_digits=13, decimal_places=2).to_internal_value( amount = serializers.DecimalField(max_digits=10, decimal_places=2).to_internal_value(
request.data.get('amount', str(payment.amount)) request.data.get('amount', str(payment.amount))
) )
if 'mark_refunded' in request.data: if 'mark_refunded' in request.data:
@@ -1539,27 +1479,21 @@ class PaymentViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
source=OrderRefund.REFUND_SOURCE_ADMIN, source=OrderRefund.REFUND_SOURCE_ADMIN,
state=OrderRefund.REFUND_STATE_CREATED, state=OrderRefund.REFUND_STATE_CREATED,
amount=amount, 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: try:
r.payment_provider.execute_refund(r) r.payment_provider.execute_refund(r)
except PaymentException as e: except PaymentException as e:
r.state = OrderRefund.REFUND_STATE_FAILED r.state = OrderRefund.REFUND_STATE_FAILED
r.save() 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))}, return Response({'detail': 'External error: {}'.format(str(e))},
status=status.HTTP_400_BAD_REQUEST) status=status.HTTP_400_BAD_REQUEST)
else: 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 payment.order.pending_sum > 0:
if mark_refunded: if mark_refunded:
mark_order_refunded(payment.order, mark_order_refunded(payment.order,
@@ -1749,7 +1683,7 @@ class RetryException(APIException):
class InvoiceViewSet(viewsets.ReadOnlyModelViewSet): class InvoiceViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = InvoiceSerializer serializer_class = InvoiceSerializer
queryset = Invoice.objects.none() queryset = Invoice.objects.none()
filter_backends = (DjangoFilterBackend, TotalOrderingFilter) filter_backends = (DjangoFilterBackend, OrderingFilter)
ordering = ('nr',) ordering = ('nr',)
ordering_fields = ('nr', 'date') ordering_fields = ('nr', 'date')
filterset_class = InvoiceFilter filterset_class = InvoiceFilter
@@ -1842,7 +1776,7 @@ with scopes_disabled():
class RevokedSecretViewSet(viewsets.ReadOnlyModelViewSet): class RevokedSecretViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = RevokedTicketSecretSerializer serializer_class = RevokedTicketSecretSerializer
queryset = RevokedTicketSecret.objects.none() queryset = RevokedTicketSecret.objects.none()
filter_backends = (DjangoFilterBackend, TotalOrderingFilter) filter_backends = (DjangoFilterBackend, OrderingFilter)
ordering = ('-created',) ordering = ('-created',)
ordering_fields = ('created', 'secret') ordering_fields = ('created', 'secret')
filterset_class = RevokedSecretFilter filterset_class = RevokedSecretFilter
@@ -1851,25 +1785,3 @@ class RevokedSecretViewSet(viewsets.ReadOnlyModelViewSet):
def get_queryset(self): def get_queryset(self):
return RevokedTicketSecret.objects.filter(event=self.request.event) 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)

View File

@@ -28,7 +28,9 @@ from django.shortcuts import get_object_or_404
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django_filters.rest_framework import DjangoFilterBackend, FilterSet from django_filters.rest_framework import DjangoFilterBackend, FilterSet
from django_scopes import scopes_disabled from django_scopes import scopes_disabled
from rest_framework import mixins, serializers, status, views, viewsets from rest_framework import (
filters, mixins, serializers, status, views, viewsets,
)
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.exceptions import MethodNotAllowed, PermissionDenied from rest_framework.exceptions import MethodNotAllowed, PermissionDenied
from rest_framework.mixins import CreateModelMixin, DestroyModelMixin from rest_framework.mixins import CreateModelMixin, DestroyModelMixin
@@ -36,7 +38,6 @@ from rest_framework.response import Response
from rest_framework.viewsets import GenericViewSet from rest_framework.viewsets import GenericViewSet
from pretix.api.models import OAuthAccessToken from pretix.api.models import OAuthAccessToken
from pretix.api.pagination import TotalOrderingFilter
from pretix.api.serializers.organizer import ( from pretix.api.serializers.organizer import (
CustomerCreateSerializer, CustomerSerializer, DeviceSerializer, CustomerCreateSerializer, CustomerSerializer, DeviceSerializer,
GiftCardSerializer, GiftCardTransactionSerializer, MembershipSerializer, GiftCardSerializer, GiftCardTransactionSerializer, MembershipSerializer,
@@ -50,7 +51,6 @@ from pretix.base.models import (
User, User,
) )
from pretix.base.settings import SETTINGS_AFFECTING_CSS from pretix.base.settings import SETTINGS_AFFECTING_CSS
from pretix.helpers import OF_SELF
from pretix.helpers.dicts import merge_dicts from pretix.helpers.dicts import merge_dicts
from pretix.presale.style import regenerate_organizer_css from pretix.presale.style import regenerate_organizer_css
@@ -61,7 +61,7 @@ class OrganizerViewSet(viewsets.ReadOnlyModelViewSet):
lookup_field = 'slug' lookup_field = 'slug'
lookup_url_kwarg = 'organizer' lookup_url_kwarg = 'organizer'
lookup_value_regex = '[^/]+' lookup_value_regex = '[^/]+'
filter_backends = (TotalOrderingFilter,) filter_backends = (filters.OrderingFilter,)
ordering = ('slug',) ordering = ('slug',)
ordering_fields = ('name', 'slug') ordering_fields = ('name', 'slug')
@@ -178,7 +178,7 @@ class GiftCardViewSet(viewsets.ModelViewSet):
def perform_update(self, serializer): def perform_update(self, serializer):
if 'include_accepted' in self.request.GET: if 'include_accepted' in self.request.GET:
raise PermissionDenied("Accepted gift cards cannot be updated, use transact instead.") raise PermissionDenied("Accepted gift cards cannot be updated, use transact instead.")
GiftCard.objects.select_for_update(of=OF_SELF).get(pk=self.get_object().pk) GiftCard.objects.select_for_update().get(pk=self.get_object().pk)
old_value = serializer.instance.value old_value = serializer.instance.value
value = serializer.validated_data.pop('value') value = serializer.validated_data.pop('value')
inst = serializer.save(secret=serializer.instance.secret, currency=serializer.instance.currency, inst = serializer.save(secret=serializer.instance.secret, currency=serializer.instance.currency,
@@ -196,8 +196,8 @@ class GiftCardViewSet(viewsets.ModelViewSet):
@action(detail=True, methods=["POST"]) @action(detail=True, methods=["POST"])
@transaction.atomic() @transaction.atomic()
def transact(self, request, **kwargs): def transact(self, request, **kwargs):
gc = GiftCard.objects.select_for_update(of=OF_SELF).get(pk=self.get_object().pk) gc = GiftCard.objects.select_for_update().get(pk=self.get_object().pk)
value = serializers.DecimalField(max_digits=13, decimal_places=2).to_internal_value( value = serializers.DecimalField(max_digits=10, decimal_places=2).to_internal_value(
request.data.get('value') request.data.get('value')
) )
text = serializers.CharField(allow_blank=True, allow_null=True).to_internal_value( text = serializers.CharField(allow_blank=True, allow_null=True).to_internal_value(

View File

@@ -21,7 +21,6 @@
# #
import datetime import datetime
from django.core.exceptions import ValidationError as DjangoValidationError
from django.utils.timezone import now from django.utils.timezone import now
from oauth2_provider.contrib.rest_framework import OAuth2Authentication from oauth2_provider.contrib.rest_framework import OAuth2Authentication
from rest_framework.authentication import SessionAuthentication from rest_framework.authentication import SessionAuthentication
@@ -34,9 +33,6 @@ from pretix.api.auth.device import DeviceTokenAuthentication
from pretix.api.auth.permission import AnyAuthenticatedClientPermission from pretix.api.auth.permission import AnyAuthenticatedClientPermission
from pretix.api.auth.token import TeamTokenAuthentication from pretix.api.auth.token import TeamTokenAuthentication
from pretix.base.models import CachedFile from pretix.base.models import CachedFile
from pretix.helpers.images import (
IMAGE_TYPES, validate_uploaded_file_for_valid_image,
)
ALLOWED_TYPES = { ALLOWED_TYPES = {
'image/gif': {'.gif'}, 'image/gif': {'.gif'},
@@ -65,13 +61,6 @@ class UploadView(APIView):
name=file_obj.name, name=file_obj.name,
type=content_type 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( cf = CachedFile.objects.create(
expires=now() + datetime.timedelta(days=1), expires=now() + datetime.timedelta(days=1),
date=now(), date=now(),

View File

@@ -31,9 +31,9 @@ from django_scopes import scopes_disabled
from rest_framework import status, viewsets from rest_framework import status, viewsets
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.exceptions import PermissionDenied from rest_framework.exceptions import PermissionDenied
from rest_framework.filters import OrderingFilter
from rest_framework.response import Response from rest_framework.response import Response
from pretix.api.pagination import TotalOrderingFilter
from pretix.api.serializers.voucher import VoucherSerializer from pretix.api.serializers.voucher import VoucherSerializer
from pretix.base.models import Voucher from pretix.base.models import Voucher
@@ -59,7 +59,7 @@ with scopes_disabled():
class VoucherViewSet(viewsets.ModelViewSet): class VoucherViewSet(viewsets.ModelViewSet):
serializer_class = VoucherSerializer serializer_class = VoucherSerializer
queryset = Voucher.objects.none() queryset = Voucher.objects.none()
filter_backends = (DjangoFilterBackend, TotalOrderingFilter) filter_backends = (DjangoFilterBackend, OrderingFilter)
ordering = ('id',) ordering = ('id',)
ordering_fields = ('id', 'code', 'max_usages', 'valid_until', 'value') ordering_fields = ('id', 'code', 'max_usages', 'valid_until', 'value')
filterset_class = VoucherFilter filterset_class = VoucherFilter

View File

@@ -25,9 +25,9 @@ from django_scopes import scopes_disabled
from rest_framework import viewsets from rest_framework import viewsets
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.exceptions import PermissionDenied, ValidationError from rest_framework.exceptions import PermissionDenied, ValidationError
from rest_framework.filters import OrderingFilter
from rest_framework.response import Response from rest_framework.response import Response
from pretix.api.pagination import TotalOrderingFilter
from pretix.api.serializers.waitinglist import WaitingListSerializer from pretix.api.serializers.waitinglist import WaitingListSerializer
from pretix.base.models import WaitingListEntry from pretix.base.models import WaitingListEntry
from pretix.base.models.waitinglist import WaitingListException from pretix.base.models.waitinglist import WaitingListException
@@ -47,8 +47,8 @@ with scopes_disabled():
class WaitingListViewSet(viewsets.ModelViewSet): class WaitingListViewSet(viewsets.ModelViewSet):
serializer_class = WaitingListSerializer serializer_class = WaitingListSerializer
queryset = WaitingListEntry.objects.none() queryset = WaitingListEntry.objects.none()
filter_backends = (DjangoFilterBackend, TotalOrderingFilter) filter_backends = (DjangoFilterBackend, OrderingFilter)
ordering = ('created', 'pk',) ordering = ('created',)
ordering_fields = ('id', 'created', 'email', 'item') ordering_fields = ('id', 'created', 'email', 'item')
filterset_class = WaitingListFilter filterset_class = WaitingListFilter
permission = 'can_view_orders' permission = 'can_view_orders'

View File

@@ -42,7 +42,6 @@ from pretix.base.models import LogEntry
from pretix.base.services.tasks import ProfiledTask, TransactionAwareTask from pretix.base.services.tasks import ProfiledTask, TransactionAwareTask
from pretix.base.signals import periodic_task from pretix.base.signals import periodic_task
from pretix.celery_app import app from pretix.celery_app import app
from pretix.helpers import OF_SELF
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
_ALL_EVENTS = None _ALL_EVENTS = None
@@ -96,7 +95,7 @@ def get_all_webhook_events():
return types return types
class ParametrizedWebhookEvent(WebhookEvent): class ParametrizedOrderWebhookEvent(WebhookEvent):
def __init__(self, action_type, verbose_name): def __init__(self, action_type, verbose_name):
self._action_type = action_type self._action_type = action_type
self._verbose_name = verbose_name self._verbose_name = verbose_name
@@ -110,8 +109,6 @@ class ParametrizedWebhookEvent(WebhookEvent):
def verbose_name(self): def verbose_name(self):
return self._verbose_name return self._verbose_name
class ParametrizedOrderWebhookEvent(ParametrizedWebhookEvent):
def build_payload(self, logentry: LogEntry): def build_payload(self, logentry: LogEntry):
order = logentry.content_object order = logentry.content_object
if not order: if not order:
@@ -126,7 +123,19 @@ class ParametrizedOrderWebhookEvent(ParametrizedWebhookEvent):
} }
class ParametrizedEventWebhookEvent(ParametrizedWebhookEvent): 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
def build_payload(self, logentry: LogEntry): def build_payload(self, logentry: LogEntry):
if logentry.action_type == 'pretix.event.deleted': if logentry.action_type == 'pretix.event.deleted':
@@ -150,7 +159,19 @@ class ParametrizedEventWebhookEvent(ParametrizedWebhookEvent):
} }
class ParametrizedSubEventWebhookEvent(ParametrizedWebhookEvent): 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
def build_payload(self, logentry: LogEntry): def build_payload(self, logentry: LogEntry):
# do not use content_object, this is also called in deletion # do not use content_object, this is also called in deletion
@@ -163,19 +184,6 @@ class ParametrizedSubEventWebhookEvent(ParametrizedWebhookEvent):
} }
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): class ParametrizedOrderPositionWebhookEvent(ParametrizedOrderWebhookEvent):
def build_payload(self, logentry: LogEntry): def build_payload(self, logentry: LogEntry):
@@ -296,11 +304,6 @@ def register_default_webhook_events(sender, **kwargs):
'pretix.subevent.deleted', 'pretix.subevent.deleted',
pgettext_lazy('subevent', 'Event series date 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( ParametrizedEventWebhookEvent(
'pretix.event.live.activated', 'pretix.event.live.activated',
_('Shop taken live'), _('Shop taken live'),
@@ -499,8 +502,7 @@ def manually_retry_all_calls(webhook_id: int):
webhook = WebHook.objects.get(id=webhook_id) webhook = WebHook.objects.get(id=webhook_id)
with scope(organizer=webhook.organizer), transaction.atomic(): with scope(organizer=webhook.organizer), transaction.atomic():
for whcr in webhook.retries.select_for_update( 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( send_webhook.apply_async(
args=(whcr.logentry_id, whcr.action_type, whcr.webhook_id, whcr.retry_count), args=(whcr.logentry_id, whcr.action_type, whcr.webhook_id, whcr.retry_count),
@@ -513,8 +515,7 @@ def manually_retry_all_calls(webhook_id: int):
def schedule_webhook_retries_on_celery(sender, **kwargs): def schedule_webhook_retries_on_celery(sender, **kwargs):
with transaction.atomic(): with transaction.atomic():
for whcr in WebHookCallRetry.objects.select_for_update( 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()): ).filter(retry_not_before__lt=now()):
send_webhook.apply_async( send_webhook.apply_async(
args=(whcr.logentry_id, whcr.action_type, whcr.webhook_id, whcr.retry_count), args=(whcr.logentry_id, whcr.action_type, whcr.webhook_id, whcr.retry_count),

View File

@@ -42,6 +42,7 @@ from localflavor.fr.forms import FRZipCodeField
from localflavor.gb.forms import GBPostcodeField from localflavor.gb.forms import GBPostcodeField
from localflavor.gr.forms import GRPostalCodeField from localflavor.gr.forms import GRPostalCodeField
from localflavor.hr.forms import HRPostalCodeField from localflavor.hr.forms import HRPostalCodeField
from localflavor.id_.forms import IDPostCodeField
from localflavor.ie.forms import EircodeField from localflavor.ie.forms import EircodeField
from localflavor.il.forms import ILPostalCodeField from localflavor.il.forms import ILPostalCodeField
from localflavor.in_.forms import INZipCodeField from localflavor.in_.forms import INZipCodeField
@@ -79,8 +80,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 # We don't presume this for countries we don't have knowledge about, there are countries in the
# world e.g. without zipcodes # world e.g. without zipcodes
'AR', 'AT', 'AU', 'BE', 'BR', 'CA', 'CH', 'CN', 'CU', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FI', 'FR', 'AR', 'AT', 'AU', 'BE', 'BR', 'CA', 'CH', 'CN', 'CU', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FI', 'FR',
'GB', 'GR', 'HR', 'IE', 'IL', 'IN', 'IR', 'IS', 'IT', 'JP', 'LT', 'LV', 'MA', 'MT', 'MX', 'NL', 'GB', 'GR', 'HR', 'ID', 'IE', 'IL', 'IN', 'IR', 'IS', 'IT', 'JP', 'LT', 'LV', 'MA', 'MT', 'MX',
'NO', 'NZ', 'PK', 'PL', 'PT', 'RO', 'RU', 'SE', 'SG', 'SI', 'SK', 'TR', 'UA', 'US', 'ZA', 'NL', 'NO', 'NZ', 'PK', 'PL', 'PT', 'RO', 'RU', 'SE', 'SG', 'SI', 'SK', 'TR', 'UA', 'US', 'ZA',
} }
@@ -166,6 +167,7 @@ _zip_code_fields = {
'GB': GBPostcodeField, 'GB': GBPostcodeField,
'GR': GRPostalCodeField, 'GR': GRPostalCodeField,
'HR': HRPostalCodeField, 'HR': HRPostalCodeField,
'ID': IDPostCodeField,
'IE': EircodeField, 'IE': EircodeField,
'IL': ILPostalCodeField, 'IL': ILPostalCodeField,
'IN': INZipCodeField, 'IN': INZipCodeField,

View File

@@ -46,7 +46,7 @@ class PretixBaseConfig(AppConfig):
from . import invoice # NOQA from . import invoice # NOQA
from . import notifications # NOQA from . import notifications # NOQA
from . import email # NOQA from . import email # NOQA
from .services import auth, checkin, currencies, export, mail, tickets, cart, orderimport, orders, invoices, cleanup, update_check, quotas, notifications, vouchers # NOQA from .services import auth, checkin, export, mail, tickets, cart, orderimport, orders, invoices, cleanup, update_check, quotas, notifications, vouchers # NOQA
from .models import _transactions # NOQA from .models import _transactions # NOQA
from django.conf import settings from django.conf import settings

View File

@@ -394,11 +394,6 @@ def base_placeholders(sender, **kwargs):
lambda event_or_subevent, refund_amount: LazyCurrencyNumber(refund_amount, event_or_subevent.currency), 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) 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( SimpleFunctionalMailTextPlaceholder(
'total_with_currency', ['event', 'order'], lambda event, order: LazyCurrencyNumber(order.total, 'total_with_currency', ['event', 'order'], lambda event, order: LazyCurrencyNumber(order.total,
event.currency), event.currency),
@@ -525,20 +520,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() lambda event: (event if not event.has_subevents or not event.subevents.exists() else event.subevents.first()).get_date_from_display()
), ),
SimpleFunctionalMailTextPlaceholder( SimpleFunctionalMailTextPlaceholder(
'url_remove', ['waiting_list_voucher', 'event'], 'url_remove', ['waiting_list_entry', 'event'],
lambda waiting_list_voucher, event: build_absolute_uri( lambda waiting_list_entry, event: build_absolute_uri(
event, 'presale:event.waitinglist.remove' event, 'presale:event.waitinglist.remove'
) + '?voucher=' + waiting_list_voucher.code, ) + '?voucher=' + waiting_list_entry.voucher.code,
lambda event: build_absolute_uri( lambda event: build_absolute_uri(
event, event,
'presale:event.waitinglist.remove', 'presale:event.waitinglist.remove',
) + '?voucher=68CYU2H6ZTP3WLK5', ) + '?voucher=68CYU2H6ZTP3WLK5',
), ),
SimpleFunctionalMailTextPlaceholder( SimpleFunctionalMailTextPlaceholder(
'url', ['waiting_list_voucher', 'event'], 'url', ['waiting_list_entry', 'event'],
lambda waiting_list_voucher, event: build_absolute_uri( lambda waiting_list_entry, event: build_absolute_uri(
event, 'presale:event.redeem' event, 'presale:event.redeem'
) + '?voucher=' + waiting_list_voucher.code, ) + '?voucher=' + waiting_list_entry.voucher.code,
lambda event: build_absolute_uri( lambda event: build_absolute_uri(
event, event,
'presale:event.redeem', 'presale:event.redeem',
@@ -593,7 +588,7 @@ def base_placeholders(sender, **kwargs):
_('Sample Admission Ticket') _('Sample Admission Ticket')
), ),
SimpleFunctionalMailTextPlaceholder( SimpleFunctionalMailTextPlaceholder(
'code', ['waiting_list_voucher'], lambda waiting_list_voucher: waiting_list_voucher.code, 'code', ['waiting_list_entry'], lambda waiting_list_entry: waiting_list_entry.voucher.code,
'68CYU2H6ZTP3WLK5' '68CYU2H6ZTP3WLK5'
), ),
SimpleFunctionalMailTextPlaceholder( SimpleFunctionalMailTextPlaceholder(
@@ -646,10 +641,6 @@ def base_placeholders(sender, **kwargs):
'attendee_name', ['position'], lambda position: position.attendee_name, 'attendee_name', ['position'], lambda position: position.attendee_name,
_('John Doe'), _('John Doe'),
), ),
SimpleFunctionalMailTextPlaceholder(
'positionid', ['position'], lambda position: str(position.positionid),
'1'
),
SimpleFunctionalMailTextPlaceholder( SimpleFunctionalMailTextPlaceholder(
'name', ['position_or_address'], 'name', ['position_or_address'],
get_best_name, get_best_name,

View File

@@ -36,7 +36,7 @@ import io
import tempfile import tempfile
from collections import OrderedDict, namedtuple from collections import OrderedDict, namedtuple
from decimal import Decimal from decimal import Decimal
from typing import Optional, Tuple from typing import Tuple
import pytz import pytz
from defusedcsv import csv from defusedcsv import csv
@@ -84,27 +84,6 @@ class BaseExporter:
""" """
raise NotImplementedError() # NOQA 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 @property
def identifier(self) -> str: def identifier(self) -> str:
""" """

View File

@@ -39,7 +39,7 @@ from zipfile import ZipFile
from django import forms from django import forms
from django.dispatch import receiver from django.dispatch import receiver
from django.utils.translation import gettext_lazy as _, pgettext_lazy from django.utils.translation import gettext_lazy as _
from pretix.base.models import QuestionAnswer from pretix.base.models import QuestionAnswer
@@ -49,10 +49,7 @@ from ..signals import register_data_exporters
class AnswerFilesExporter(BaseExporter): class AnswerFilesExporter(BaseExporter):
identifier = 'answerfiles' identifier = 'answerfiles'
verbose_name = _('Question answer file uploads') verbose_name = _('Answers to file upload questions')
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 @property
def export_form_fields(self): def export_form_fields(self):

View File

@@ -36,7 +36,7 @@ from collections import OrderedDict
from django.dispatch import receiver from django.dispatch import receiver
from django.utils.timezone import get_current_timezone from django.utils.timezone import get_current_timezone
from django.utils.translation import gettext as _, gettext_lazy, pgettext_lazy from django.utils.translation import gettext as _, gettext_lazy
from pretix.base.settings import PERSON_NAME_SCHEMES from pretix.base.settings import PERSON_NAME_SCHEMES
@@ -48,8 +48,6 @@ class CustomerListExporter(OrganizerLevelExportMixin, ListExporter):
identifier = 'customerlist' identifier = 'customerlist'
verbose_name = gettext_lazy('Customer accounts') verbose_name = gettext_lazy('Customer accounts')
organizer_required_permission = 'can_manage_customers' 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 @property
def additional_form_fields(self): def additional_form_fields(self):

View File

@@ -23,24 +23,22 @@ import json
from collections import OrderedDict from collections import OrderedDict
from decimal import Decimal from decimal import Decimal
import dateutil
from django import forms
from django.core.serializers.json import DjangoJSONEncoder from django.core.serializers.json import DjangoJSONEncoder
from django.dispatch import receiver from django.dispatch import receiver
from django.utils.timezone import now from django.utils.translation import gettext, gettext_lazy
from django.utils.translation import gettext, gettext_lazy, pgettext_lazy
from pretix.base.i18n import language from pretix.base.i18n import language
from pretix.base.models import Invoice, OrderPayment from pretix.base.models import Invoice, OrderPayment
from ..exporter import BaseExporter from ..exporter import BaseExporter
from ..signals import register_data_exporters from ..signals import register_data_exporters
from ..timeframes import DateFrameField, resolve_timeframe_to_dates_inclusive
class DekodiNREIExporter(BaseExporter): class DekodiNREIExporter(BaseExporter):
identifier = 'dekodi_nrei' identifier = 'dekodi_nrei'
verbose_name = 'dekodi NREI (JSON)' 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/ # Specification: http://manuals.dekodi.de/nexuspub/schnittstellenbuch/
@@ -115,7 +113,7 @@ class DekodiNREIExporter(BaseExporter):
'PTNo14': p.info_data.get('reference') or '', 'PTNo14': p.info_data.get('reference') or '',
'PTNo15': p.full_id or '', 'PTNo15': p.full_id or '',
}) })
elif p.provider and p.provider.startswith('stripe'): elif p.provider.startswith('stripe'):
src = p.info_data.get("source", p.info_data) src = p.info_data.get("source", p.info_data)
payments.append({ payments.append({
'PTID': '81', 'PTID': '81',
@@ -194,12 +192,17 @@ class DekodiNREIExporter(BaseExporter):
def render(self, form_data): def render(self, form_data):
qs = self.event.invoices.select_related('order').prefetch_related('lines', 'lines__subevent') qs = self.event.invoices.select_related('order').prefetch_related('lines', 'lines__subevent')
if form_data.get('date_range'): if form_data.get('date_from'):
d_start, d_end = resolve_timeframe_to_dates_inclusive(now(), form_data['date_range'], self.timezone) date_value = form_data.get('date_from')
if d_start: if isinstance(date_value, str):
qs = qs.filter(date__gte=d_start) date_value = dateutil.parser.parse(date_value).date()
if d_end: qs = qs.filter(date__gte=date_value)
qs = qs.filter(date__lte=d_end)
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)
jo = { jo = {
'Format': 'NREI', 'Format': 'NREI',
@@ -215,14 +218,22 @@ class DekodiNREIExporter(BaseExporter):
def export_form_fields(self): def export_form_fields(self):
return OrderedDict( return OrderedDict(
[ [
('date_range', ('date_from',
DateFrameField( forms.DateField(
label=gettext_lazy('Date range'), label=gettext_lazy('Start date'),
include_future_frames=False, widget=forms.DateInput(attrs={'class': 'datepickerfield'}),
required=False, required=False,
help_text=gettext_lazy('Only include invoices issued in this time frame. Note that the invoice date does ' help_text=gettext_lazy('Only include invoices issued on or after this date. Note that the invoice date does '
'not always correspond to the order or payment date.') '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.')
)),
] ]
) )

View File

@@ -35,7 +35,7 @@
from django.dispatch import receiver from django.dispatch import receiver
from django.utils.formats import date_format from django.utils.formats import date_format
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _, pgettext_lazy from django.utils.translation import gettext_lazy as _
from ...control.forms.filter import get_all_payment_providers from ...control.forms.filter import get_all_payment_providers
from ..exporter import ListExporter from ..exporter import ListExporter
@@ -45,8 +45,6 @@ from ..signals import register_multievent_data_exporters
class EventDataExporter(ListExporter): class EventDataExporter(ListExporter):
identifier = 'eventdata' identifier = 'eventdata'
verbose_name = _('Event data') 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 @cached_property
def providers(self): def providers(self):

View File

@@ -38,15 +38,13 @@ from collections import OrderedDict
from decimal import Decimal from decimal import Decimal
from zipfile import ZipFile from zipfile import ZipFile
import dateutil.parser
from django import forms from django import forms
from django.db.models import CharField, Exists, F, OuterRef, Q, Subquery, Sum from django.db.models import CharField, Exists, F, OuterRef, Q, Subquery, Sum
from django.dispatch import receiver from django.dispatch import receiver
from django.utils.formats import date_format from django.utils.formats import date_format
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.timezone import now from django.utils.translation import gettext, gettext_lazy as _, pgettext
from django.utils.translation import (
gettext, gettext_lazy as _, pgettext, pgettext_lazy,
)
from pretix.base.models import Invoice, InvoiceLine, OrderPayment from pretix.base.models import Invoice, InvoiceLine, OrderPayment
@@ -59,24 +57,30 @@ from ..services.invoices import invoice_pdf_task
from ..signals import ( from ..signals import (
register_data_exporters, register_multievent_data_exporters, register_data_exporters, register_multievent_data_exporters,
) )
from ..timeframes import DateFrameField, resolve_timeframe_to_dates_inclusive
class InvoiceExporterMixin: class InvoiceExporterMixin:
category = pgettext_lazy('export_category', 'Invoices')
@property @property
def invoice_exporter_form_fields(self): def invoice_exporter_form_fields(self):
return OrderedDict( return OrderedDict(
[ [
('date_range', ('date_from',
DateFrameField( forms.DateField(
label=_('Date range'), label=_('Start date'),
include_future_frames=False, widget=forms.DateInput(attrs={'class': 'datepickerfield'}),
required=False, required=False,
help_text=_('Only include invoices issued in this time frame. Note that the invoice date does ' help_text=_('Only include invoices issued on or after this date. Note that the invoice date does '
'not always correspond to the order or payment date.') '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', ('payment_provider',
forms.ChoiceField( forms.ChoiceField(
label=_('Payment provider'), label=_('Payment provider'),
@@ -108,12 +112,16 @@ class InvoiceExporterMixin:
) )
) )
qs = qs.filter(has_payment_with_provider=1) qs = qs.filter(has_payment_with_provider=1)
if form_data.get('date_range'): if form_data.get('date_from'):
d_start, d_end = resolve_timeframe_to_dates_inclusive(now(), form_data['date_range'], self.timezone) date_value = form_data.get('date_from')
if d_start: if isinstance(date_value, str):
qs = qs.filter(date__gte=d_start) date_value = dateutil.parser.parse(date_value).date()
if d_end: qs = qs.filter(date__gte=date_value)
qs = qs.filter(date__lte=d_end) 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)
return qs return qs
@@ -121,7 +129,6 @@ class InvoiceExporterMixin:
class InvoiceExporter(InvoiceExporterMixin, BaseExporter): class InvoiceExporter(InvoiceExporterMixin, BaseExporter):
identifier = 'invoices' identifier = 'invoices'
verbose_name = _('All 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): def render(self, form_data: dict, output_file=None):
qs = self.invoices_queryset(form_data).filter(shredded=False) qs = self.invoices_queryset(form_data).filter(shredded=False)
@@ -173,10 +180,6 @@ class InvoiceExporter(InvoiceExporterMixin, BaseExporter):
class InvoiceDataExporter(InvoiceExporterMixin, MultiSheetListExporter): class InvoiceDataExporter(InvoiceExporterMixin, MultiSheetListExporter):
identifier = 'invoicedata' identifier = 'invoicedata'
verbose_name = _('Invoice data') 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 @property
def additional_form_fields(self): def additional_form_fields(self):

View File

@@ -22,7 +22,7 @@
from django.db.models import Prefetch from django.db.models import Prefetch
from django.dispatch import receiver from django.dispatch import receiver
from django.utils.formats import date_format from django.utils.formats import date_format
from django.utils.translation import gettext_lazy as _, pgettext_lazy from django.utils.translation import gettext_lazy as _
from openpyxl.styles import Alignment from openpyxl.styles import Alignment
from openpyxl.utils import get_column_letter from openpyxl.utils import get_column_letter
@@ -48,8 +48,6 @@ def _min(a1, a2):
class ItemDataExporter(ListExporter): class ItemDataExporter(ListExporter):
identifier = 'itemdata' identifier = 'itemdata'
verbose_name = _('Product data') 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): def iterate_list(self, form_data):
locales = self.event.settings.locales locales = self.event.settings.locales
@@ -75,7 +73,6 @@ class ItemDataExporter(ListExporter):
_("Free price input"), _("Free price input"),
_("Sales tax"), _("Sales tax"),
_("Is an admission ticket"), _("Is an admission ticket"),
_("Personalized ticket"),
_("Generate tickets"), _("Generate tickets"),
_("Waiting list"), _("Waiting list"),
_("Available from"), _("Available from"),
@@ -147,7 +144,6 @@ class ItemDataExporter(ListExporter):
_("Yes") if i.free_price else "", _("Yes") if i.free_price else "",
str(i.tax_rule) if i.tax_rule else "", str(i.tax_rule) if i.tax_rule else "",
_("Yes") if i.admission 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.generate_tickets else (_("Default") if i.generate_tickets is None else ""),
_("Yes") if i.allow_waitinglist else "", _("Yes") if i.allow_waitinglist else "",
date_format(_max(i.available_from, v.available_from).astimezone(self.timezone), date_format(_max(i.available_from, v.available_from).astimezone(self.timezone),
@@ -191,7 +187,6 @@ class ItemDataExporter(ListExporter):
_("Yes") if i.free_price else "", _("Yes") if i.free_price else "",
str(i.tax_rule) if i.tax_rule else "", str(i.tax_rule) if i.tax_rule else "",
_("Yes") if i.admission 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.generate_tickets else (_("Default") if i.generate_tickets is None else ""),
_("Yes") if i.allow_waitinglist else "", _("Yes") if i.allow_waitinglist else "",
date_format(i.available_from.astimezone(self.timezone), date_format(i.available_from.astimezone(self.timezone),
@@ -218,9 +213,7 @@ class ItemDataExporter(ListExporter):
yield row yield row
def get_filename(self): def get_filename(self):
if self.is_multievent:
return '{}_products'.format(self.events.first().organizer.slug) return '{}_products'.format(self.events.first().organizer.slug)
return '{}_products'.format(self.event.slug)
def prepare_xlsx_sheet(self, ws): def prepare_xlsx_sheet(self, ws):
self.__ws = ws self.__ws = ws

View File

@@ -38,8 +38,6 @@ from decimal import Decimal
from django.core.serializers.json import DjangoJSONEncoder from django.core.serializers.json import DjangoJSONEncoder
from django.db.models import Prefetch from django.db.models import Prefetch
from django.dispatch import receiver 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 ..exporter import BaseExporter
from ..models import ItemMetaValue, ItemVariation, ItemVariationMetaValue from ..models import ItemMetaValue, ItemVariation, ItemVariationMetaValue
@@ -48,10 +46,7 @@ from ..signals import register_data_exporters
class JSONExporter(BaseExporter): class JSONExporter(BaseExporter):
identifier = 'json' identifier = 'json'
verbose_name = lazy(lambda *args: gettext('Order data') + ' (JSON)', str)() verbose_name = 'Order data (JSON)'
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): def render(self, form_data):
jo = { jo = {
@@ -83,7 +78,6 @@ class JSONExporter(BaseExporter):
'tax_rate': item.tax_rule.rate if item.tax_rule else Decimal('0.00'), '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, 'tax_name': str(item.tax_rule.name) if item.tax_rule else None,
'admission': item.admission, 'admission': item.admission,
'personalized': item.personalized,
'active': item.active, 'active': item.active,
'sales_channels': item.sales_channels, 'sales_channels': item.sales_channels,
'description': str(item.description), 'description': str(item.description),
@@ -109,8 +103,6 @@ class JSONExporter(BaseExporter):
'name': str(variation), 'name': str(variation),
'description': str(variation.description), 'description': str(variation.description),
'position': variation.position, 'position': variation.position,
'checkin_attention': variation.checkin_attention,
'require_approval': variation.require_approval,
'require_membership': variation.require_membership, 'require_membership': variation.require_membership,
'sales_channels': variation.sales_channels, 'sales_channels': variation.sales_channels,
'available_from': variation.available_from, 'available_from': variation.available_from,
@@ -195,9 +187,6 @@ class JSONExporter(BaseExporter):
'state': position.state, 'state': position.state,
'secret': position.secret, 'secret': position.secret,
'addon_to': position.addon_to_id, 'addon_to': position.addon_to_id,
'valid_from': position.valid_from,
'valid_until': position.valid_until,
'blocked': position.blocked,
'answers': [ 'answers': [
{ {
'question': answer.question_id, 'question': answer.question_id,

View File

@@ -36,7 +36,7 @@ from collections import OrderedDict
from django import forms from django import forms
from django.dispatch import receiver from django.dispatch import receiver
from django.utils.translation import gettext_lazy as _, pgettext_lazy from django.utils.translation import gettext_lazy as _
from pretix.base.models import OrderPosition from pretix.base.models import OrderPosition
@@ -50,8 +50,6 @@ from ..signals import (
class MailExporter(BaseExporter): class MailExporter(BaseExporter):
identifier = 'mailaddrs' identifier = 'mailaddrs'
verbose_name = _('Email addresses (text file)') 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): def render(self, form_data: dict):
qs = Order.objects.filter(event__in=self.events, status__in=form_data['status']).prefetch_related('event') qs = Order.objects.filter(event__in=self.events, status__in=form_data['status']).prefetch_related('event')

View File

@@ -33,8 +33,10 @@
# License for the specific language governing permissions and limitations under the License. # License for the specific language governing permissions and limitations under the License.
from collections import OrderedDict from collections import OrderedDict
from datetime import date, datetime, time
from decimal import Decimal from decimal import Decimal
import dateutil
import pytz import pytz
from django import forms from django import forms
from django.db.models import ( from django.db.models import (
@@ -43,12 +45,9 @@ from django.db.models import (
) )
from django.db.models.functions import Coalesce from django.db.models.functions import Coalesce
from django.dispatch import receiver from django.dispatch import receiver
from django.utils.formats import date_format
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.timezone import get_current_timezone, now from django.utils.timezone import get_current_timezone, make_aware, now
from django.utils.translation import ( from django.utils.translation import gettext as _, gettext_lazy, pgettext
gettext as _, gettext_lazy, pgettext, pgettext_lazy,
)
from pretix.base.models import ( from pretix.base.models import (
GiftCard, GiftCardTransaction, Invoice, InvoiceAddress, Order, GiftCard, GiftCardTransaction, Invoice, InvoiceAddress, Order,
@@ -64,24 +63,14 @@ from ...helpers.iter import chunked_iterable
from ..exporter import ( from ..exporter import (
ListExporter, MultiSheetListExporter, OrganizerLevelExportMixin, ListExporter, MultiSheetListExporter, OrganizerLevelExportMixin,
) )
from ..forms.widgets import SplitDateTimePickerWidget
from ..signals import ( from ..signals import (
register_data_exporters, register_multievent_data_exporters, register_data_exporters, register_multievent_data_exporters,
) )
from ..timeframes import (
DateFrameField,
resolve_timeframe_to_datetime_start_inclusive_end_exclusive,
)
class OrderListExporter(MultiSheetListExporter): class OrderListExporter(MultiSheetListExporter):
identifier = 'orderlist' identifier = 'orderlist'
verbose_name = gettext_lazy('Order data') verbose_name = gettext_lazy('Order data')
category = pgettext_lazy('export_category', 'Order data')
description = gettext_lazy('Download a spreadsheet of all orders. The spreadsheet will include three sheets, one '
'with a line for every order, one with a line for every order position, and one with '
'a line for every additional fee charged in an order.')
featured = True
@cached_property @cached_property
def providers(self): def providers(self):
@@ -116,25 +105,41 @@ class OrderListExporter(MultiSheetListExporter):
initial=False, initial=False,
required=False required=False
)), )),
('date_range', ('date_from',
DateFrameField( forms.DateField(
label=_('Date range'), label=_('Start date'),
include_future_frames=False, widget=forms.DateInput(attrs={'class': 'datepickerfield'}),
required=False, required=False,
help_text=_('Only include orders created within this date range.') help_text=_('Only include orders created on or after this date.')
)), )),
('event_date_range', ('date_to',
DateFrameField( forms.DateField(
label=_('Event date'), label=_('End date'),
include_future_frames=True, widget=forms.DateInput(attrs={'class': 'datepickerfield'}),
required=False, required=False,
help_text=_('Only include orders including at least one ticket for a date in this range. ' help_text=_('Only include orders created on or before this date.')
)),
('event_date_from',
forms.DateField(
label=_('Start event date'),
widget=forms.DateInput(attrs={'class': 'datepickerfield'}),
required=False,
help_text=_('Only include orders including at least one ticket for a date on or after this date. '
'Will also include other dates in case of mixed orders!')
)),
('event_date_to',
forms.DateField(
label=_('End event date'),
widget=forms.DateInput(attrs={'class': 'datepickerfield'}),
required=False,
help_text=_('Only include orders including at least one ticket for a date on or before this date. '
'Will also include other dates in case of mixed orders!') 'Will also include other dates in case of mixed orders!')
)), )),
] ]
d = OrderedDict(d) d = OrderedDict(d)
if not self.is_multievent and not self.event.has_subevents: if not self.is_multievent and not self.event.has_subevents:
del d['event_date_range'] del d['event_date_from']
del d['event_date_to']
return d return d
def _get_all_payment_methods(self, qs): def _get_all_payment_methods(self, qs):
@@ -177,27 +182,45 @@ class OrderListExporter(MultiSheetListExporter):
annotations = {} annotations = {}
filters = {} filters = {}
if form_data.get('date_range'): if form_data.get('date_from'):
dt_start, dt_end = resolve_timeframe_to_datetime_start_inclusive_end_exclusive(now(), form_data['date_range'], self.timezone) date_value = form_data.get('date_from')
if dt_start: if not isinstance(date_value, date):
filters[f'{rel}datetime__gte'] = dt_start date_value = dateutil.parser.parse(date_value).date()
if dt_end: datetime_value = make_aware(datetime.combine(date_value, time(0, 0, 0)), self.timezone)
filters[f'{rel}datetime__lt'] = dt_end
filters[f'{rel}datetime__gte'] = datetime_value
if form_data.get('date_to'):
date_value = form_data.get('date_to')
if not isinstance(date_value, date):
date_value = dateutil.parser.parse(date_value).date()
datetime_value = make_aware(datetime.combine(date_value, time(23, 59, 59, 999999)), self.timezone)
filters[f'{rel}datetime__lte'] = datetime_value
if form_data.get('event_date_from'):
date_value = form_data.get('event_date_from')
if not isinstance(date_value, date):
date_value = dateutil.parser.parse(date_value).date()
datetime_value = make_aware(datetime.combine(date_value, time(0, 0, 0)), self.timezone)
if form_data.get('event_date_range'):
dt_start, dt_end = resolve_timeframe_to_datetime_start_inclusive_end_exclusive(now(), form_data['event_date_range'], self.timezone)
if dt_start:
annotations['event_date_max'] = Case( annotations['event_date_max'] = Case(
When(**{f'{rel}event__has_subevents': True}, then=Max(f'{rel}all_positions__subevent__date_from')), When(**{f'{rel}event__has_subevents': True}, then=Max(f'{rel}all_positions__subevent__date_from')),
default=F(f'{rel}event__date_from'), default=F(f'{rel}event__date_from'),
) )
filters['event_date_max__gte'] = dt_start filters['event_date_max__gte'] = datetime_value
if dt_end:
if form_data.get('event_date_to'):
date_value = form_data.get('event_date_to')
if not isinstance(date_value, date):
date_value = dateutil.parser.parse(date_value).date()
datetime_value = make_aware(datetime.combine(date_value, time(23, 59, 59, 999999)), self.timezone)
annotations['event_date_min'] = Case( annotations['event_date_min'] = Case(
When(**{f'{rel}event__has_subevents': True}, then=Min(f'{rel}all_positions__subevent__date_from')), When(**{f'{rel}event__has_subevents': True}, then=Min(f'{rel}all_positions__subevent__date_from')),
default=F(f'{rel}event__date_from'), default=F(f'{rel}event__date_from'),
) )
filters['event_date_min__lt'] = dt_end filters['event_date_min__lte'] = datetime_value
if filters: if filters:
return qs.annotate(**annotations).filter(**filters) return qs.annotate(**annotations).filter(**filters)
@@ -570,9 +593,6 @@ class OrderListExporter(MultiSheetListExporter):
_('Seat zone'), _('Seat zone'),
_('Seat row'), _('Seat row'),
_('Seat number'), _('Seat number'),
_('Blocked'),
_('Valid from'),
_('Valid until'),
_('Order comment'), _('Order comment'),
_('Follow-up date'), _('Follow-up date'),
] ]
@@ -686,11 +706,6 @@ class OrderListExporter(MultiSheetListExporter):
else: else:
row += ['', '', '', '', ''] row += ['', '', '', '', '']
row += [
_('Yes') if op.blocked else '',
date_format(op.valid_from, 'SHORT_DATETIME_FORMAT') if op.valid_from else '',
date_format(op.valid_until, 'SHORT_DATETIME_FORMAT') if op.valid_until else '',
]
row.append(order.comment) row.append(order.comment)
row.append(order.custom_followup_at.strftime("%Y-%m-%d") if order.custom_followup_at else "") row.append(order.custom_followup_at.strftime("%Y-%m-%d") if order.custom_followup_at else "")
acache = {} acache = {}
@@ -761,28 +776,12 @@ class OrderListExporter(MultiSheetListExporter):
class PaymentListExporter(ListExporter): class PaymentListExporter(ListExporter):
identifier = 'paymentlist' identifier = 'paymentlist'
verbose_name = gettext_lazy('Payments and refunds') verbose_name = gettext_lazy('Order payments and refunds')
category = pgettext_lazy('export_category', 'Order data')
description = gettext_lazy('Download a spreadsheet of all payments or refunds of every order.')
featured = True
@property @property
def additional_form_fields(self): def additional_form_fields(self):
return OrderedDict( return OrderedDict(
[ [
('end_date_range',
DateFrameField(
label=_('Date range (payment date)'),
include_future_frames=False,
required=False,
help_text=_('Note that using this will exclude any non-confirmed payments or non-completed refunds.'),
)),
('start_date_range',
DateFrameField(
label=_('Date range (start of transaction)'),
include_future_frames=False,
required=False
)),
('payment_states', ('payment_states',
forms.MultipleChoiceField( forms.MultipleChoiceField(
label=_('Payment states'), label=_('Payment states'),
@@ -809,35 +808,17 @@ class PaymentListExporter(ListExporter):
payments = OrderPayment.objects.filter( payments = OrderPayment.objects.filter(
order__event__in=self.events, order__event__in=self.events,
state__in=form_data.get('payment_states', []) state__in=form_data.get('payment_states', [])
).select_related('order').prefetch_related('order__event').order_by('created') ).order_by('created')
refunds = OrderRefund.objects.filter( refunds = OrderRefund.objects.filter(
order__event__in=self.events, order__event__in=self.events,
state__in=form_data.get('refund_states', []) state__in=form_data.get('refund_states', [])
).select_related('order').prefetch_related('order__event').order_by('created') ).order_by('created')
if form_data.get('end_date_range'):
dt_start, dt_end = resolve_timeframe_to_datetime_start_inclusive_end_exclusive(now(), form_data['end_date_range'], self.timezone)
if dt_start:
payments = payments.filter(created__gte=dt_start)
refunds = refunds .filter(created__gte=dt_start)
if dt_end:
payments = payments.filter(created__lt=dt_end)
refunds = refunds .filter(created__lt=dt_end)
if form_data.get('start_end_date_range'):
dt_start, dt_end = resolve_timeframe_to_datetime_start_inclusive_end_exclusive(now(), form_data['start_date_range'], self.timezone)
if dt_start:
payments = payments.filter(payment_date__gte=dt_start)
refunds = refunds .filter(execution_date__gte=dt_start)
if dt_end:
payments = payments.filter(payment_date__lt=dt_end)
refunds = refunds.filter(execution_date__lt=dt_end)
objs = sorted(list(payments) + list(refunds), key=lambda o: o.created) objs = sorted(list(payments) + list(refunds), key=lambda o: o.created)
headers = [ headers = [
_('Event slug'), _('Order'), _('Payment ID'), _('Creation date'), _('Completion date'), _('Status'), _('Event slug'), _('Order'), _('Payment ID'), _('Creation date'), _('Completion date'), _('Status'),
_('Status code'), _('Amount'), _('Payment method'), _('Comment'), _('Matching ID'), _('Payment details'), _('Status code'), _('Amount'), _('Payment method'), _('Comment'),
] ]
yield headers yield headers
@@ -850,18 +831,6 @@ class PaymentListExporter(ListExporter):
d2 = obj.execution_date.astimezone(tz).date().strftime('%Y-%m-%d') d2 = obj.execution_date.astimezone(tz).date().strftime('%Y-%m-%d')
else: else:
d2 = '' d2 = ''
matching_id = ''
payment_details = ''
try:
if isinstance(obj, OrderPayment):
matching_id = obj.payment_provider.matching_id(obj) or ''
payment_details = obj.payment_provider.payment_control_render_short(obj)
elif isinstance(obj, OrderRefund):
matching_id = obj.payment_provider.refund_matching_id(obj) or ''
payment_details = obj.payment_provider.refund_control_render_short(obj)
except Exception:
pass
row = [ row = [
obj.order.event.slug, obj.order.event.slug,
obj.order.code, obj.order.code,
@@ -873,8 +842,6 @@ class PaymentListExporter(ListExporter):
obj.amount * (-1 if isinstance(obj, OrderRefund) else 1), obj.amount * (-1 if isinstance(obj, OrderRefund) else 1),
provider_names.get(obj.provider, obj.provider), provider_names.get(obj.provider, obj.provider),
obj.comment if isinstance(obj, OrderRefund) else "", obj.comment if isinstance(obj, OrderRefund) else "",
matching_id,
payment_details,
] ]
yield row yield row
@@ -888,8 +855,6 @@ class PaymentListExporter(ListExporter):
class QuotaListExporter(ListExporter): class QuotaListExporter(ListExporter):
identifier = 'quotalist' identifier = 'quotalist'
verbose_name = gettext_lazy('Quota availabilities') verbose_name = gettext_lazy('Quota availabilities')
category = pgettext_lazy('export_category', 'Product data')
description = gettext_lazy('Download a spreadsheet of all quotas including their current availability.')
def iterate_list(self, form_data): def iterate_list(self, form_data):
has_subevents = self.event.has_subevents has_subevents = self.event.has_subevents
@@ -943,17 +908,21 @@ class GiftcardTransactionListExporter(OrganizerLevelExportMixin, ListExporter):
identifier = 'giftcardtransactionlist' identifier = 'giftcardtransactionlist'
verbose_name = gettext_lazy('Gift card transactions') verbose_name = gettext_lazy('Gift card transactions')
organizer_required_permission = 'can_manage_gift_cards' organizer_required_permission = 'can_manage_gift_cards'
category = pgettext_lazy('export_category', 'Gift cards')
description = gettext_lazy('Download a spreadsheet of all gift card transactions.')
@property @property
def additional_form_fields(self): def additional_form_fields(self):
d = [ d = [
('date_range', ('date_from',
DateFrameField( forms.DateField(
label=_('Date range'), label=_('Start date'),
include_future_frames=False, widget=forms.DateInput(attrs={'class': 'datepickerfield'}),
required=False required=False,
)),
('date_to',
forms.DateField(
label=_('End date'),
widget=forms.DateInput(attrs={'class': 'datepickerfield'}),
required=False,
)), )),
] ]
d = OrderedDict(d) d = OrderedDict(d)
@@ -964,12 +933,22 @@ class GiftcardTransactionListExporter(OrganizerLevelExportMixin, ListExporter):
card__issuer=self.organizer, card__issuer=self.organizer,
).order_by('datetime').select_related('card', 'order', 'order__event') ).order_by('datetime').select_related('card', 'order', 'order__event')
if form_data.get('date_range'): if form_data.get('date_from'):
dt_start, dt_end = resolve_timeframe_to_datetime_start_inclusive_end_exclusive(now(), form_data['date_range'], self.timezone) date_value = form_data.get('date_from')
if dt_start: if isinstance(date_value, str):
qs = qs.filter(datetime__gte=dt_start) date_value = dateutil.parser.parse(date_value).date()
if dt_end: qs = qs.filter(
qs = qs.filter(datetime__lt=dt_end) datetime__gte=make_aware(datetime.combine(date_value, time(0, 0, 0)), self.timezone)
)
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(
datetime__lte=make_aware(datetime.combine(date_value, time(23, 59, 59, 999999)), self.timezone)
)
headers = [ headers = [
_('Gift card code'), _('Gift card code'),
@@ -999,8 +978,6 @@ class GiftcardTransactionListExporter(OrganizerLevelExportMixin, ListExporter):
class GiftcardRedemptionListExporter(ListExporter): class GiftcardRedemptionListExporter(ListExporter):
identifier = 'giftcardredemptionlist' identifier = 'giftcardredemptionlist'
verbose_name = gettext_lazy('Gift card redemptions') verbose_name = gettext_lazy('Gift card redemptions')
category = pgettext_lazy('export_category', 'Order data')
description = gettext_lazy('Download a spreadsheet of all payments or refunds that involve gift cards.')
def iterate_list(self, form_data): def iterate_list(self, form_data):
payments = OrderPayment.objects.filter( payments = OrderPayment.objects.filter(
@@ -1046,18 +1023,14 @@ class GiftcardListExporter(OrganizerLevelExportMixin, ListExporter):
identifier = 'giftcardlist' identifier = 'giftcardlist'
verbose_name = gettext_lazy('Gift cards') verbose_name = gettext_lazy('Gift cards')
organizer_required_permission = 'can_manage_gift_cards' organizer_required_permission = 'can_manage_gift_cards'
category = pgettext_lazy('export_category', 'Gift cards')
description = gettext_lazy('Download a spreadsheet of all gift cards including their current value.')
@property @property
def additional_form_fields(self): def additional_form_fields(self):
return OrderedDict( return OrderedDict(
[ [
('date', forms.SplitDateTimeField( ('date', forms.DateTimeField(
label=_('Show value at'), label=_('Show value at'),
required=False, initial=now(),
widget=SplitDateTimePickerWidget(),
help_text=_('Defaults to the time of report.')
)), )),
('testmode', forms.ChoiceField( ('testmode', forms.ChoiceField(
label=_('Test mode'), label=_('Test mode'),
@@ -1085,13 +1058,12 @@ class GiftcardListExporter(OrganizerLevelExportMixin, ListExporter):
) )
def iterate_list(self, form_data): def iterate_list(self, form_data):
d = form_data.get('date') or now()
s = GiftCardTransaction.objects.filter( s = GiftCardTransaction.objects.filter(
card=OuterRef('pk'), card=OuterRef('pk'),
datetime__lte=d datetime__lte=form_data['date']
).order_by().values('card').annotate(s=Sum('value')).values('s') ).order_by().values('card').annotate(s=Sum('value')).values('s')
qs = self.organizer.issued_gift_cards.filter( qs = self.organizer.issued_gift_cards.filter(
issuance__lte=d issuance__lte=form_data['date']
).annotate( ).annotate(
cached_value=Coalesce(Subquery(s), Decimal('0.00')), cached_value=Coalesce(Subquery(s), Decimal('0.00')),
).order_by('issuance').prefetch_related( ).order_by('issuance').prefetch_related(
@@ -1106,11 +1078,11 @@ class GiftcardListExporter(OrganizerLevelExportMixin, ListExporter):
if form_data.get('state') == 'empty': if form_data.get('state') == 'empty':
qs = qs.filter(cached_value=0) qs = qs.filter(cached_value=0)
elif form_data.get('state') == 'valid_value': elif form_data.get('state') == 'valid_value':
qs = qs.exclude(cached_value=0).filter(Q(expires__isnull=True) | Q(expires__gte=d)) qs = qs.exclude(cached_value=0).filter(Q(expires__isnull=True) | Q(expires__gte=form_data['date']))
elif form_data.get('state') == 'expired_value': elif form_data.get('state') == 'expired_value':
qs = qs.exclude(cached_value=0).filter(expires__lt=d) qs = qs.exclude(cached_value=0).filter(expires__lt=form_data['date'])
elif form_data.get('state') == 'expired': elif form_data.get('state') == 'expired':
qs = qs.filter(expires__lt=d) qs = qs.filter(expires__lt=form_data['date'])
headers = [ headers = [
_('Gift card code'), _('Gift card code'),

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