mirror of
https://github.com/pretix/pretix.git
synced 2025-12-13 12:42:26 +00:00
Compare commits
120 Commits
custom-man
...
v1.5.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a021ac6ecc | ||
|
|
e6eb4945a7 | ||
|
|
44b7b15b3e | ||
|
|
022ca8ad6c | ||
|
|
5e37fbb4ef | ||
|
|
5049da95b5 | ||
|
|
c78ca8bc00 | ||
|
|
c40e0bfdff | ||
|
|
ea79ebf105 | ||
|
|
d4b9906638 | ||
|
|
c1c4133ac6 | ||
|
|
ff3e127648 | ||
|
|
970b861947 | ||
|
|
5abe8a109c | ||
|
|
ee6d0af795 | ||
|
|
0dc82b6a28 | ||
|
|
a530eda9fc | ||
|
|
3fa39798e5 | ||
|
|
b7f8832633 | ||
|
|
2add8d671a | ||
|
|
d5d1fcf331 | ||
|
|
9007501d81 | ||
|
|
fc12a8f549 | ||
|
|
86f03b3399 | ||
|
|
80ad0e7f68 | ||
|
|
ea970be6f2 | ||
|
|
4bd0b96a2d | ||
|
|
c7f0436ec0 | ||
|
|
ecc788fd79 | ||
|
|
8ea9d5685d | ||
|
|
c2bd2f0672 | ||
|
|
424b7964b4 | ||
|
|
98394fdc63 | ||
|
|
625e90518e | ||
|
|
d446191cf4 | ||
|
|
ea2557274f | ||
|
|
1bcca566ab | ||
|
|
cf365635ef | ||
|
|
df6a93da5f | ||
|
|
433512a256 | ||
|
|
e0fe78b82e | ||
|
|
d3a84690f2 | ||
|
|
3df4b4dc57 | ||
|
|
b2801f3a40 | ||
|
|
e1bdfd7e4e | ||
|
|
5584c12d0a | ||
|
|
91bd02157c | ||
|
|
02d2b88a54 | ||
|
|
6ae5c5e6ce | ||
|
|
123d2f6120 | ||
|
|
3ada10c3f4 | ||
|
|
b21a928028 | ||
|
|
374cf08086 | ||
|
|
15a85bb873 | ||
|
|
4b12678fa4 | ||
|
|
b2d4bea1d0 | ||
|
|
6df3a7d4b5 | ||
|
|
6964bf7c8a | ||
|
|
965424799d | ||
|
|
f7de2ead40 | ||
|
|
260ba19e44 | ||
|
|
5caff5d28e | ||
|
|
8fa490c938 | ||
|
|
8e3cc0df0c | ||
|
|
1a5a4cf72d | ||
|
|
6c5b7bbed0 | ||
|
|
f9eee6a864 | ||
|
|
4ad0fe5653 | ||
|
|
a02823ca38 | ||
|
|
513f8e66f5 | ||
|
|
0cecc168b6 | ||
|
|
6f7281b0f5 | ||
|
|
c7022bd285 | ||
|
|
e89e3d2e1b | ||
|
|
574fb6804f | ||
|
|
caf75fafdf | ||
|
|
bc74fb63d1 | ||
|
|
b576a87d61 | ||
|
|
6e81d8acec | ||
|
|
c67a53d156 | ||
|
|
bf5ea81b40 | ||
|
|
1261b8670f | ||
|
|
17dad33f8b | ||
|
|
1d5c160e1d | ||
|
|
f04b7fa365 | ||
|
|
fa011fbdce | ||
|
|
759c5374d9 | ||
|
|
f631acdf18 | ||
|
|
b2dfd8ab11 | ||
|
|
43f4803da7 | ||
|
|
019d8220b8 | ||
|
|
b946010bdb | ||
|
|
c3097b12c3 | ||
|
|
12a53710c3 | ||
|
|
983326e610 | ||
|
|
b4eb707b38 | ||
|
|
e44f34f0a9 | ||
|
|
3c762adbf4 | ||
|
|
ebabd20d09 | ||
|
|
8694e1901a | ||
|
|
0f2875e89a | ||
|
|
74d9921be1 | ||
|
|
41e56adfdb | ||
|
|
4ff1d302d9 | ||
|
|
d6e213d51a | ||
|
|
2a4deeba55 | ||
|
|
24b1d2afcb | ||
|
|
5cea3d824a | ||
|
|
78a8a7d744 | ||
|
|
a3bf85754a | ||
|
|
006b6fd5e8 | ||
|
|
8ff2c42070 | ||
|
|
c5d18c6884 | ||
|
|
48b3621f1e | ||
|
|
fb716eb498 | ||
|
|
5d8e294350 | ||
|
|
8a96d8c24e | ||
|
|
6396d2f922 | ||
|
|
ed7e90451b | ||
|
|
f5990dd5c4 |
@@ -5,7 +5,6 @@ tests:
|
|||||||
- virtualenv env
|
- virtualenv env
|
||||||
- source env/bin/activate
|
- source env/bin/activate
|
||||||
- pip install -U pip wheel setuptools
|
- pip install -U pip wheel setuptools
|
||||||
- XDG_CACHE_HOME=/cache bash .travis.sh style
|
|
||||||
- XDG_CACHE_HOME=/cache bash .travis.sh tests
|
- XDG_CACHE_HOME=/cache bash .travis.sh tests
|
||||||
tags:
|
tags:
|
||||||
- python3
|
- python3
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ if [ "$1" == "style" ]; then
|
|||||||
XDG_CACHE_HOME=/cache pip3 install -Ur src/requirements.txt -r src/requirements/dev.txt -r src/requirements/py34.txt
|
XDG_CACHE_HOME=/cache pip3 install -Ur src/requirements.txt -r src/requirements/dev.txt -r src/requirements/py34.txt
|
||||||
cd src
|
cd src
|
||||||
flake8 .
|
flake8 .
|
||||||
isort -c -rc .
|
isort -c -rc -df .
|
||||||
fi
|
fi
|
||||||
if [ "$1" == "doctests" ]; then
|
if [ "$1" == "doctests" ]; then
|
||||||
XDG_CACHE_HOME=/cache pip3 install -Ur doc/requirements.txt -r src/requirements/py34.txt
|
XDG_CACHE_HOME=/cache pip3 install -Ur doc/requirements.txt -r src/requirements/py34.txt
|
||||||
|
|||||||
@@ -30,11 +30,11 @@ matrix:
|
|||||||
env: JOB=tests PRETIX_CONFIG_FILE=tests/travis_postgres.cfg
|
env: JOB=tests PRETIX_CONFIG_FILE=tests/travis_postgres.cfg
|
||||||
- python: 3.6
|
- python: 3.6
|
||||||
env: JOB=tests PRETIX_CONFIG_FILE=tests/travis_postgres.cfg
|
env: JOB=tests PRETIX_CONFIG_FILE=tests/travis_postgres.cfg
|
||||||
- python: 3.4
|
- python: 3.6
|
||||||
env: JOB=style
|
env: JOB=style
|
||||||
- python: 3.4
|
- python: 3.6
|
||||||
env: JOB=plugins
|
env: JOB=plugins
|
||||||
- python: 3.4
|
- python: 3.6
|
||||||
env: JOB=tests-cov
|
env: JOB=tests-cov
|
||||||
addons:
|
addons:
|
||||||
postgresql: "9.4"
|
postgresql: "9.4"
|
||||||
|
|||||||
13
Dockerfile
13
Dockerfile
@@ -1,11 +1,9 @@
|
|||||||
FROM debian:jessie
|
FROM python:3.6
|
||||||
|
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
apt-get install -y python3 git python3-pip \
|
apt-get install -y git libxml2-dev libxslt1-dev python-dev python-virtualenv locales \
|
||||||
libxml2-dev libxslt1-dev python-dev python-virtualenv locales libffi-dev \
|
libffi-dev build-essential python3-dev zlib1g-dev libssl-dev gettext libpq-dev \
|
||||||
build-essential python3-dev zlib1g-dev libssl-dev gettext \
|
libmysqlclient-dev libmemcached-dev libjpeg-dev supervisor nginx sudo \
|
||||||
libpq-dev libmysqlclient-dev libmemcached-dev libjpeg-dev \
|
|
||||||
aqbanking-tools supervisor nginx sudo \
|
|
||||||
--no-install-recommends && \
|
--no-install-recommends && \
|
||||||
apt-get clean && \
|
apt-get clean && \
|
||||||
rm -rf /var/lib/apt/lists/* && \
|
rm -rf /var/lib/apt/lists/* && \
|
||||||
@@ -33,8 +31,7 @@ RUN chmod +x /usr/local/bin/pretix && \
|
|||||||
cd /pretix/src && \
|
cd /pretix/src && \
|
||||||
rm -f pretix.cfg && \
|
rm -f pretix.cfg && \
|
||||||
pip3 install -r requirements.txt -r requirements/mysql.txt -r requirements/postgres.txt \
|
pip3 install -r requirements.txt -r requirements/mysql.txt -r requirements/postgres.txt \
|
||||||
-r requirements/memcached.txt -r requirements/redis.txt \
|
-r requirements/memcached.txt -r requirements/redis.txt gunicorn && \
|
||||||
-r requirements/py34.txt gunicorn && \
|
|
||||||
mkdir -p data && \
|
mkdir -p data && \
|
||||||
chown -R pretixuser:pretixuser /pretix /data data && \
|
chown -R pretixuser:pretixuser /pretix /data data && \
|
||||||
sudo -u pretixuser make production
|
sudo -u pretixuser make production
|
||||||
|
|||||||
160
doc/_templates/index.html
vendored
Normal file
160
doc/_templates/index.html
vendored
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
{% extends "layout.html" %}
|
||||||
|
{% set title = 'Overview' %}
|
||||||
|
{% block body %}
|
||||||
|
<h1>Welcome to pretix' documentation!</h1>
|
||||||
|
<p>
|
||||||
|
We work hard to make this website contain all information that you need to use, run, understand, and improve
|
||||||
|
pretix. Of course, this documentation will never be perfect or complete, but if there is anything unclear
|
||||||
|
or anything specific that you miss here, that's a bug and we'd be happy if you'd
|
||||||
|
<a href="https://github.com/pretix/pretix/issues/new">let us know</a>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Documentation structure</h2>
|
||||||
|
|
||||||
|
<div class="sectionbox">
|
||||||
|
<div class="icon">
|
||||||
|
<a href="user/index.html">
|
||||||
|
<span class="fa fa-user fa-fw"></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="text">
|
||||||
|
<a href="user/index.html">
|
||||||
|
<strong>User Guide</strong>
|
||||||
|
</a>
|
||||||
|
<p>Go here to find information on how to configure and use pretix as an event organizer.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="sectionbox">
|
||||||
|
<div class="icon">
|
||||||
|
<a href="admin/index.html">
|
||||||
|
<span class="fa fa-server fa-fw"></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="text">
|
||||||
|
<a href="admin/index.html">
|
||||||
|
<strong>Administrator docs</strong>
|
||||||
|
</a>
|
||||||
|
<p>Find out how to install pretix on your own server and how to maintain an installation of pretix.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="clearfix"></div>
|
||||||
|
<div class="sectionbox">
|
||||||
|
<div class="icon">
|
||||||
|
<a href="api/index.html">
|
||||||
|
<span class="fa fa-plug fa-fw"></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="text">
|
||||||
|
<a href="api/index.html">
|
||||||
|
<strong>REST API</strong>
|
||||||
|
</a>
|
||||||
|
<p>
|
||||||
|
Documentation and reference of the RESTful API exposed by pretix for interaction with external
|
||||||
|
components.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="sectionbox">
|
||||||
|
<div class="icon">
|
||||||
|
<a href="development/index.html">
|
||||||
|
<span class="fa fa-wrench fa-fw"></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="text">
|
||||||
|
<a href="development/index.html">
|
||||||
|
<strong>Developer docs</strong>
|
||||||
|
</a>
|
||||||
|
<p>Get information on how to contribute to pretix itself and how to build plugins that interact with
|
||||||
|
pretix.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="clearfix"></div>
|
||||||
|
<div class="sectionbox">
|
||||||
|
<div class="icon">
|
||||||
|
<a href="plugins/index.html">
|
||||||
|
<span class="fa fa-puzzle-piece fa-fw"></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="text">
|
||||||
|
<a href="plugins/index.html">
|
||||||
|
<strong>Plugin docs</strong>
|
||||||
|
</a>
|
||||||
|
<p>Documentation and details on plugins that ship with pretix or are officially supported.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="sectionbox">
|
||||||
|
<div class="icon">
|
||||||
|
<a href="contents.html">
|
||||||
|
<span class="fa fa-list fa-fw"></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="text">
|
||||||
|
<a href="contents.html">
|
||||||
|
<strong>Table of contents</strong>
|
||||||
|
</a>
|
||||||
|
<p>Detailled overview of everything contained in this documentation.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="clearfix"></div>
|
||||||
|
|
||||||
|
<h2>Useful links</h2>
|
||||||
|
|
||||||
|
<div class="sectionbox">
|
||||||
|
<div class="icon">
|
||||||
|
<a href="https://pretix.eu" target="_blank">
|
||||||
|
<span class="fa fa-globe fa-fw"></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="text">
|
||||||
|
<a href="https://pretix.eu" target="_blank">
|
||||||
|
<strong>Project website</strong>
|
||||||
|
</a>
|
||||||
|
<p>pretix.eu is the central entry-point to the pretix project and also the home of the commercial hosting
|
||||||
|
service available.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="sectionbox">
|
||||||
|
<div class="icon">
|
||||||
|
<a href="https://github.com/pretix/pretix" target="_blank">
|
||||||
|
<span class="fa fa-github fa-fw"></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="text">
|
||||||
|
<a href="https://github.com/pretix/pretix" target="_blank">
|
||||||
|
<strong>GitHub repository</strong>
|
||||||
|
</a>
|
||||||
|
<p>Our main source code repository contains all code that is part of pretix as well as some plugins and the
|
||||||
|
source for this documentation.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="clearfix"></div>
|
||||||
|
<div class="sectionbox">
|
||||||
|
<div class="icon">
|
||||||
|
<a href="https://pretix.eu/about/en/blog/" target="_blank">
|
||||||
|
<span class="fa fa-newspaper-o fa-fw"></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="text">
|
||||||
|
<a href="https://pretix.eu/about/en/blog/" target="_blank">
|
||||||
|
<strong>Project blog</strong>
|
||||||
|
</a>
|
||||||
|
<p>This important information source contains all release notes for all stable releases of pretix as well as
|
||||||
|
general news on pretix as a project.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="sectionbox">
|
||||||
|
<div class="icon">
|
||||||
|
<a href="https://twitter.com/pretixeu" target="_blank">
|
||||||
|
<span class="fa fa-twitter fa-fw"></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="text">
|
||||||
|
<a href="https://twitter.com/pretixeu" target="_blank">
|
||||||
|
<strong>Twitter</strong>
|
||||||
|
</a>
|
||||||
|
<p>Keep in touch and stay up to date by following our project account on Twitter.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="clearfix"></div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
16
doc/_themes/pretix_theme/__init__.py
vendored
Normal file
16
doc/_themes/pretix_theme/__init__.py
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
"""pretix sphinx theme.
|
||||||
|
|
||||||
|
Based on sphinx-rtd-theme
|
||||||
|
Based on https://github.com/ryan-roemer/sphinx-bootstrap-theme.
|
||||||
|
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
|
||||||
|
__version__ = '0.1.0'
|
||||||
|
__version_full__ = __version__
|
||||||
|
|
||||||
|
|
||||||
|
def get_html_theme_path():
|
||||||
|
"""Return list of HTML theme paths."""
|
||||||
|
cur_dir = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
|
||||||
|
return cur_dir
|
||||||
82
doc/_themes/pretix_theme/breadcrumbs.html
vendored
Normal file
82
doc/_themes/pretix_theme/breadcrumbs.html
vendored
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
{# Support for Sphinx 1.3+ page_source_suffix, but don't break old builds. #}
|
||||||
|
|
||||||
|
{% if page_source_suffix %}
|
||||||
|
{% set suffix = page_source_suffix %}
|
||||||
|
{% else %}
|
||||||
|
{% set suffix = source_suffix %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if meta is defined and meta is not none %}
|
||||||
|
{% set check_meta = True %}
|
||||||
|
{% else %}
|
||||||
|
{% set check_meta = False %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if check_meta and 'github_url' in meta %}
|
||||||
|
{% set display_github = True %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if check_meta and 'bitbucket_url' in meta %}
|
||||||
|
{% set display_bitbucket = True %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if check_meta and 'gitlab_url' in meta %}
|
||||||
|
{% set display_gitlab = True %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div role="navigation" aria-label="breadcrumbs navigation">
|
||||||
|
|
||||||
|
<ul class="wy-breadcrumbs">
|
||||||
|
{% block breadcrumbs %}
|
||||||
|
<li><a href="{{ pathto(master_doc) }}">{{ _('Docs') }}</a> »</li>
|
||||||
|
{% for doc in parents %}
|
||||||
|
<li><a href="{{ doc.link|e }}">{{ doc.title }}</a> »</li>
|
||||||
|
{% endfor %}
|
||||||
|
<li>{{ title }}</li>
|
||||||
|
{% endblock %}
|
||||||
|
{% block breadcrumbs_aside %}
|
||||||
|
<li class="wy-breadcrumbs-aside">
|
||||||
|
{% if pagename != "search" %}
|
||||||
|
{% if display_github %}
|
||||||
|
{% if check_meta and 'github_url' in meta %}
|
||||||
|
<!-- User defined GitHub URL -->
|
||||||
|
<a href="{{ meta['github_url'] }}" class="fa fa-github"> {{ _('Edit on GitHub') }}</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="https://{{ github_host|default("github.com") }}/{{ github_user }}/{{ github_repo }}/blob/{{ github_version }}{{ conf_py_path }}{{ pagename }}{{ suffix }}" class="fa fa-github"> {{ _('Edit on GitHub') }}</a>
|
||||||
|
{% endif %}
|
||||||
|
{% elif display_bitbucket %}
|
||||||
|
{% if check_meta and 'bitbucket_url' in meta %}
|
||||||
|
<!-- User defined Bitbucket URL -->
|
||||||
|
<a href="{{ meta['bitbucket_url'] }}" class="fa fa-bitbucket"> {{ _('Edit on Bitbucket') }}</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="https://bitbucket.org/{{ bitbucket_user }}/{{ bitbucket_repo }}/src/{{ bitbucket_version}}{{ conf_py_path }}{{ pagename }}{{ suffix }}" class="fa fa-bitbucket"> {{ _('Edit on Bitbucket') }}</a>
|
||||||
|
{% endif %}
|
||||||
|
{% elif display_gitlab %}
|
||||||
|
{% if check_meta and 'gitlab_url' in meta %}
|
||||||
|
<!-- User defined GitLab URL -->
|
||||||
|
<a href="{{ meta['gitlab_url'] }}" class="fa fa-gitlab"> {{ _('Edit on GitLab') }}</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="https://{{ gitlab_host|default("gitlab.com") }}/{{ gitlab_user }}/{{ gitlab_repo }}/blob/{{ gitlab_version }}{{ conf_py_path }}{{ pagename }}{{ suffix }}" class="fa fa-gitlab"> {{ _('Edit on GitLab') }}</a>
|
||||||
|
{% endif %}
|
||||||
|
{% elif show_source and source_url_prefix %}
|
||||||
|
<a href="{{ source_url_prefix }}{{ pagename }}{{ suffix }}">{{ _('View page source') }}</a>
|
||||||
|
{% elif show_source and has_source and sourcename %}
|
||||||
|
<a href="{{ pathto('_sources/' + sourcename, true)|e }}" rel="nofollow"> {{ _('View page source') }}</a>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
{% endblock %}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{% if (theme_prev_next_buttons_location == 'top' or theme_prev_next_buttons_location == 'both') and (next or prev) %}
|
||||||
|
<div class="rst-breadcrumbs-buttons" role="navigation" aria-label="breadcrumb navigation">
|
||||||
|
{% if next %}
|
||||||
|
<a href="{{ next.link|e }}" class="btn btn-neutral float-right" title="{{ next.title|striptags|e }}" accesskey="n">Next <span class="fa fa-arrow-circle-right"></span></a>
|
||||||
|
{% endif %}
|
||||||
|
{% if prev %}
|
||||||
|
<a href="{{ prev.link|e }}" class="btn btn-neutral" title="{{ prev.title|striptags|e }}" accesskey="p"><span class="fa fa-arrow-circle-left"></span> Previous</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<hr/>
|
||||||
|
</div>
|
||||||
52
doc/_themes/pretix_theme/footer.html
vendored
Normal file
52
doc/_themes/pretix_theme/footer.html
vendored
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
<footer>
|
||||||
|
{% if (theme_prev_next_buttons_location == 'bottom' or theme_prev_next_buttons_location == 'both') and (next or prev) %}
|
||||||
|
<div class="rst-footer-buttons" role="navigation" aria-label="footer navigation">
|
||||||
|
{% if next %}
|
||||||
|
<a href="{{ next.link|e }}" class="btn btn-neutral float-right" title="{{ next.title|striptags|e }}" accesskey="n" rel="next">{{ _('Next') }} <span class="fa fa-arrow-circle-right"></span></a>
|
||||||
|
{% endif %}
|
||||||
|
{% if prev %}
|
||||||
|
<a href="{{ prev.link|e }}" class="btn btn-neutral" title="{{ prev.title|striptags|e }}" accesskey="p" rel="prev"><span class="fa fa-arrow-circle-left"></span> {{ _('Previous') }}</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<div role="contentinfo">
|
||||||
|
<p>
|
||||||
|
{%- if show_copyright %}
|
||||||
|
{%- if hasdoc('copyright') %}
|
||||||
|
{% trans path=pathto('copyright'), copyright=copyright|e %}© <a href="{{ path }}">Copyright</a> {{ copyright }}.{% endtrans %}
|
||||||
|
{%- else %}
|
||||||
|
{% trans copyright=copyright|e %}© Copyright {{ copyright }}.{% endtrans %}
|
||||||
|
{%- endif %}
|
||||||
|
{%- endif %}
|
||||||
|
|
||||||
|
{%- if build_id and build_url %}
|
||||||
|
{% trans build_url=build_url, build_id=build_id %}
|
||||||
|
<span class="build">
|
||||||
|
Build
|
||||||
|
<a href="{{ build_url }}">{{ build_id }}</a>.
|
||||||
|
</span>
|
||||||
|
{% endtrans %}
|
||||||
|
{%- elif commit %}
|
||||||
|
{% trans commit=commit %}
|
||||||
|
<span class="commit">
|
||||||
|
Revision <code>{{ commit }}</code>.
|
||||||
|
</span>
|
||||||
|
{% endtrans %}
|
||||||
|
{%- elif last_updated %}
|
||||||
|
{% trans last_updated=last_updated|e %}Last updated on {{ last_updated }}.{% endtrans %}
|
||||||
|
{%- endif %}
|
||||||
|
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{%- if show_sphinx %}
|
||||||
|
<small>{% trans %}Built with <a href="http://sphinx-doc.org/">Sphinx</a> using a theme that is based on a <a href="https://github.com/snide/sphinx_rtd_theme">theme</a> provided by <a href="https://readthedocs.org">Read the Docs</a>{% endtrans %}.</small>
|
||||||
|
{%- endif %}
|
||||||
|
|
||||||
|
{%- block extrafooter %} {% endblock %}
|
||||||
|
|
||||||
|
</footer>
|
||||||
|
|
||||||
211
doc/_themes/pretix_theme/layout.html
vendored
Normal file
211
doc/_themes/pretix_theme/layout.html
vendored
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
{# TEMPLATE VAR SETTINGS #}
|
||||||
|
{%- set url_root = pathto('', 1) %}
|
||||||
|
{%- if url_root == '#' %}{% set url_root = '' %}{% endif %}
|
||||||
|
{%- if not embedded and docstitle %}
|
||||||
|
{%- set titlesuffix = " — "|safe + docstitle|e %}
|
||||||
|
{%- else %}
|
||||||
|
{%- set titlesuffix = "" %}
|
||||||
|
{%- endif %}
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<!--[if IE 8]><html class="no-js lt-ie9" lang="en" > <![endif]-->
|
||||||
|
<!--[if gt IE 8]><!--> <html class="no-js" lang="en" > <!--<![endif]-->
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
{{ metatags }}
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
{% block htmltitle %}
|
||||||
|
<title>{{ title|striptags|e }}{{ titlesuffix }}</title>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{# FAVICON #}
|
||||||
|
{% if favicon %}
|
||||||
|
<link rel="shortcut icon" href="{{ pathto('_static/' + favicon, 1) }}"/>
|
||||||
|
{% endif %}
|
||||||
|
{# CANONICAL URL #}
|
||||||
|
{% if theme_canonical_url %}
|
||||||
|
<link rel="canonical" href="{{ theme_canonical_url }}{{ pagename }}.html"/>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# CSS #}
|
||||||
|
|
||||||
|
{# OPENSEARCH #}
|
||||||
|
{% if not embedded %}
|
||||||
|
{% if use_opensearch %}
|
||||||
|
<link rel="search" type="application/opensearchdescription+xml" title="{% trans docstitle=docstitle|e %}Search within {{ docstitle }}{% endtrans %}" href="{{ pathto('_static/opensearch.xml', 1) }}"/>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# RTD hosts this file, so just load on non RTD builds #}
|
||||||
|
<link rel="stylesheet" href="{{ pathto('_static/' + style, 1) }}" type="text/css" />
|
||||||
|
|
||||||
|
{% for cssfile in css_files %}
|
||||||
|
<link rel="stylesheet" href="{{ pathto(cssfile, 1) }}" type="text/css" />
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% for cssfile in extra_css_files %}
|
||||||
|
<link rel="stylesheet" href="{{ pathto(cssfile, 1) }}" type="text/css" />
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{%- block linktags %}
|
||||||
|
{%- if hasdoc('about') %}
|
||||||
|
<link rel="author" title="{{ _('About these documents') }}"
|
||||||
|
href="{{ pathto('about') }}"/>
|
||||||
|
{%- endif %}
|
||||||
|
{%- if hasdoc('genindex') %}
|
||||||
|
<link rel="index" title="{{ _('Index') }}"
|
||||||
|
href="{{ pathto('genindex') }}"/>
|
||||||
|
{%- endif %}
|
||||||
|
{%- if hasdoc('search') %}
|
||||||
|
<link rel="search" title="{{ _('Search') }}" href="{{ pathto('search') }}"/>
|
||||||
|
{%- endif %}
|
||||||
|
{%- if hasdoc('copyright') %}
|
||||||
|
<link rel="copyright" title="{{ _('Copyright') }}" href="{{ pathto('copyright') }}"/>
|
||||||
|
{%- endif %}
|
||||||
|
<link rel="top" title="{{ docstitle|e }}" href="{{ pathto('index') }}"/>
|
||||||
|
{%- if parents %}
|
||||||
|
<link rel="up" title="{{ parents[-1].title|striptags|e }}" href="{{ parents[-1].link|e }}"/>
|
||||||
|
{%- endif %}
|
||||||
|
{%- if next %}
|
||||||
|
<link rel="next" title="{{ next.title|striptags|e }}" href="{{ next.link|e }}"/>
|
||||||
|
{%- endif %}
|
||||||
|
{%- if prev %}
|
||||||
|
<link rel="prev" title="{{ prev.title|striptags|e }}" href="{{ prev.link|e }}"/>
|
||||||
|
{%- endif %}
|
||||||
|
{%- endblock %}
|
||||||
|
{%- block extrahead %} {% endblock %}
|
||||||
|
|
||||||
|
{# Keep modernizr in head - http://modernizr.com/docs/#installing #}
|
||||||
|
<script src="{{ pathto('_static/js/modernizr.min.js', 1) }}"></script>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="wy-body-for-nav" role="document">
|
||||||
|
|
||||||
|
{% block extrabody %} {% endblock %}
|
||||||
|
<div class="wy-grid-for-nav">
|
||||||
|
|
||||||
|
{# SIDE NAV, TOGGLES ON MOBILE #}
|
||||||
|
<nav data-toggle="wy-nav-shift" class="wy-nav-side">
|
||||||
|
<div class="wy-side-scroll">
|
||||||
|
<div class="wy-side-nav-search">
|
||||||
|
{% block sidebartitle %}
|
||||||
|
|
||||||
|
{% if logo and theme_logo_only %}
|
||||||
|
<a href="{{ pathto('index') }}">
|
||||||
|
{% else %}
|
||||||
|
<a href="{{ pathto('index') }}" class="icon icon-home"> {{ project }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if logo %}
|
||||||
|
{# Not strictly valid HTML, but it's the only way to display/scale it properly, without weird scripting or heaps of work #}
|
||||||
|
<img src="{{ pathto('_static/' + logo, 1) }}" class="logo" />
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
|
|
||||||
|
{% include "searchbox.html" %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="main navigation">
|
||||||
|
{% block menu %}
|
||||||
|
{#
|
||||||
|
The singlehtml builder doesn't handle this toctree call when the
|
||||||
|
toctree is empty. Skip building this for now.
|
||||||
|
#}
|
||||||
|
{% if 'singlehtml' not in builder %}
|
||||||
|
{% set global_toc = toctree(maxdepth=theme_navigation_depth|int, collapse=theme_collapse_navigation, includehidden=True) %}
|
||||||
|
{% endif %}
|
||||||
|
{% if global_toc %}
|
||||||
|
{{ global_toc }}
|
||||||
|
{% else %}
|
||||||
|
<!-- Local TOC -->
|
||||||
|
<div class="local-toc">{{ toc }}</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if theme_display_version %}
|
||||||
|
{%- set nav_version = version %}
|
||||||
|
{% if READTHEDOCS and current_version %}
|
||||||
|
{%- set nav_version = current_version %}
|
||||||
|
{% endif %}
|
||||||
|
{% if nav_version %}
|
||||||
|
<div class="version">
|
||||||
|
{{ nav_version }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap">
|
||||||
|
|
||||||
|
{# MOBILE NAV, TRIGGLES SIDE NAV ON TOGGLE #}
|
||||||
|
<nav class="wy-nav-top" role="navigation" aria-label="top navigation">
|
||||||
|
{% block mobile_nav %}
|
||||||
|
<i data-toggle="wy-nav-top" class="fa fa-bars"></i>
|
||||||
|
<a href="{{ pathto('index') }}">{{ project }}</a>
|
||||||
|
{% endblock %}
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
|
||||||
|
{# PAGE CONTENT #}
|
||||||
|
<div class="wy-nav-content">
|
||||||
|
<div class="rst-content">
|
||||||
|
{% include "breadcrumbs.html" %}
|
||||||
|
<div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
|
||||||
|
<div itemprop="articleBody">
|
||||||
|
{% block body %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
<div class="articleComments">
|
||||||
|
{% block comments %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% include "footer.html" %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{% include "versions.html" %}
|
||||||
|
|
||||||
|
{% if not embedded %}
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
var DOCUMENTATION_OPTIONS = {
|
||||||
|
URL_ROOT:'{{ url_root }}',
|
||||||
|
VERSION:'{{ release|e }}',
|
||||||
|
COLLAPSE_INDEX:false,
|
||||||
|
FILE_SUFFIX:'{{ '' if no_search_suffix else file_suffix }}',
|
||||||
|
HAS_SOURCE: {{ has_source|lower }},
|
||||||
|
SOURCELINK_SUFFIX: '{{ sourcelink_suffix }}'
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
{%- for scriptfile in script_files %}
|
||||||
|
<script type="text/javascript" src="{{ pathto(scriptfile, 1) }}"></script>
|
||||||
|
{%- endfor %}
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# RTD hosts this file, so just load on non RTD builds #}
|
||||||
|
{% if not READTHEDOCS %}
|
||||||
|
<script type="text/javascript" src="{{ pathto('_static/js/theme.js', 1) }}"></script>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# STICKY NAVIGATION #}
|
||||||
|
{% if theme_sticky_navigation %}
|
||||||
|
<script type="text/javascript">
|
||||||
|
jQuery(function () {
|
||||||
|
SphinxRtdTheme.StickyNav.enable();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{%- block footer %} {% endblock %}
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
209
doc/_themes/pretix_theme/layout_old.html
vendored
Normal file
209
doc/_themes/pretix_theme/layout_old.html
vendored
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
{#
|
||||||
|
basic/layout.html
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Master layout template for Sphinx themes.
|
||||||
|
|
||||||
|
:copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS.
|
||||||
|
:license: BSD, see LICENSE for details.
|
||||||
|
#}
|
||||||
|
{%- block doctype -%}
|
||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
||||||
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
|
{%- endblock %}
|
||||||
|
{%- set reldelim1 = reldelim1 is not defined and ' »' or reldelim1 %}
|
||||||
|
{%- set reldelim2 = reldelim2 is not defined and ' |' or reldelim2 %}
|
||||||
|
{%- set render_sidebar = (not embedded) and (not theme_nosidebar|tobool) and
|
||||||
|
(sidebars != []) %}
|
||||||
|
{%- set url_root = pathto('', 1) %}
|
||||||
|
{# XXX necessary? #}
|
||||||
|
{%- if url_root == '#' %}{% set url_root = '' %}{% endif %}
|
||||||
|
{%- if not embedded and docstitle %}
|
||||||
|
{%- set titlesuffix = " — "|safe + docstitle|e %}
|
||||||
|
{%- else %}
|
||||||
|
{%- set titlesuffix = "" %}
|
||||||
|
{%- endif %}
|
||||||
|
|
||||||
|
{%- macro relbar() %}
|
||||||
|
<div class="related">
|
||||||
|
<h3>{{ _('Navigation') }}</h3>
|
||||||
|
<ul>
|
||||||
|
{%- for rellink in rellinks %}
|
||||||
|
<li class="right" {% if loop.first %}style="margin-right: 10px"{% endif %}>
|
||||||
|
<a href="{{ pathto(rellink[0]) }}" title="{{ rellink[1]|striptags|e }}"
|
||||||
|
{{ accesskey(rellink[2]) }}>{{ rellink[3] }}</a>
|
||||||
|
{%- if not loop.first %}{{ reldelim2 }}{% endif %}</li>
|
||||||
|
{%- endfor %}
|
||||||
|
{%- block rootrellink %}
|
||||||
|
<li><a href="{{ pathto(master_doc) }}">{{ shorttitle|e }}</a>{{ reldelim1 }}</li>
|
||||||
|
{%- endblock %}
|
||||||
|
{%- for parent in parents %}
|
||||||
|
<li><a href="{{ parent.link|e }}" {% if loop.last %}{{ accesskey("U") }}{% endif %}>{{ parent.title }}</a>{{ reldelim1 }}</li>
|
||||||
|
{%- endfor %}
|
||||||
|
{%- block relbaritems %} {% endblock %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{%- endmacro %}
|
||||||
|
|
||||||
|
{%- macro sidebar() %}
|
||||||
|
{%- if render_sidebar %}
|
||||||
|
<div class="sphinxsidebar">
|
||||||
|
<div class="sphinxsidebarwrapper">
|
||||||
|
{%- block sidebarlogo %}
|
||||||
|
{%- if logo %}
|
||||||
|
<p class="logo"><a href="{{ pathto(master_doc) }}">
|
||||||
|
<img class="logo" src="{{ pathto('_static/' + logo, 1) }}" alt="Logo"/>
|
||||||
|
</a></p>
|
||||||
|
{%- endif %}
|
||||||
|
{%- endblock %}
|
||||||
|
{%- if sidebars != None %}
|
||||||
|
{#- new style sidebar: explicitly include/exclude templates #}
|
||||||
|
{%- for sidebartemplate in sidebars %}
|
||||||
|
{%- include sidebartemplate %}
|
||||||
|
{%- endfor %}
|
||||||
|
{%- else %}
|
||||||
|
{#- old style sidebars: using blocks -- should be deprecated #}
|
||||||
|
{%- block sidebartoc %}
|
||||||
|
{%- include "localtoc.html" %}
|
||||||
|
{%- endblock %}
|
||||||
|
{%- block sidebarrel %}
|
||||||
|
{%- include "relations.html" %}
|
||||||
|
{%- endblock %}
|
||||||
|
{%- block sidebarsourcelink %}
|
||||||
|
{%- include "sourcelink.html" %}
|
||||||
|
{%- endblock %}
|
||||||
|
{%- if customsidebar %}
|
||||||
|
{%- include customsidebar %}
|
||||||
|
{%- endif %}
|
||||||
|
{%- block sidebarsearch %}
|
||||||
|
{%- include "searchbox.html" %}
|
||||||
|
{%- endblock %}
|
||||||
|
{%- endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{%- endif %}
|
||||||
|
{%- endmacro %}
|
||||||
|
|
||||||
|
{%- macro script() %}
|
||||||
|
<script type="text/javascript">
|
||||||
|
var DOCUMENTATION_OPTIONS = {
|
||||||
|
URL_ROOT: '{{ url_root }}',
|
||||||
|
VERSION: '{{ release|e }}',
|
||||||
|
COLLAPSE_INDEX: false,
|
||||||
|
FILE_SUFFIX: '{{ '' if no_search_suffix else file_suffix }}',
|
||||||
|
HAS_SOURCE: {{ has_source|lower }},
|
||||||
|
SOURCELINK_SUFFIX: '{{ sourcelink_suffix }}'
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
{%- for scriptfile in script_files %}
|
||||||
|
<script type="text/javascript" src="{{ pathto(scriptfile, 1) }}"></script>
|
||||||
|
{%- endfor %}
|
||||||
|
{%- endmacro %}
|
||||||
|
|
||||||
|
{%- macro css() %}
|
||||||
|
<link rel="stylesheet" href="{{ pathto('_static/' + style, 1) }}" type="text/css" />
|
||||||
|
<link rel="stylesheet" href="{{ pathto('_static/pygments.css', 1) }}" type="text/css" />
|
||||||
|
{%- for cssfile in css_files %}
|
||||||
|
<link rel="stylesheet" href="{{ pathto(cssfile, 1) }}" type="text/css" />
|
||||||
|
{%- endfor %}
|
||||||
|
{%- endmacro %}
|
||||||
|
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset={{ encoding }}" />
|
||||||
|
{{ metatags }}
|
||||||
|
{%- block htmltitle %}
|
||||||
|
<title>{{ title|striptags|e }}{{ titlesuffix }}</title>
|
||||||
|
{%- endblock %}
|
||||||
|
{{ css() }}
|
||||||
|
{%- if not embedded %}
|
||||||
|
{{ script() }}
|
||||||
|
{%- if use_opensearch %}
|
||||||
|
<link rel="search" type="application/opensearchdescription+xml"
|
||||||
|
title="{% trans docstitle=docstitle|e %}Search within {{ docstitle }}{% endtrans %}"
|
||||||
|
href="{{ pathto('_static/opensearch.xml', 1) }}"/>
|
||||||
|
{%- endif %}
|
||||||
|
{%- if favicon %}
|
||||||
|
<link rel="shortcut icon" href="{{ pathto('_static/' + favicon, 1) }}"/>
|
||||||
|
{%- endif %}
|
||||||
|
{%- if theme_canonical_url %}
|
||||||
|
<link rel="canonical" href="{{ theme_canonical_url }}{{ pagename }}.html"/>
|
||||||
|
{%- endif %}
|
||||||
|
{%- endif %}
|
||||||
|
{%- block linktags %}
|
||||||
|
{%- if hasdoc('about') %}
|
||||||
|
<link rel="author" title="{{ _('About these documents') }}" href="{{ pathto('about') }}" />
|
||||||
|
{%- endif %}
|
||||||
|
{%- if hasdoc('genindex') %}
|
||||||
|
<link rel="index" title="{{ _('Index') }}" href="{{ pathto('genindex') }}" />
|
||||||
|
{%- endif %}
|
||||||
|
{%- if hasdoc('search') %}
|
||||||
|
<link rel="search" title="{{ _('Search') }}" href="{{ pathto('search') }}" />
|
||||||
|
{%- endif %}
|
||||||
|
{%- if hasdoc('copyright') %}
|
||||||
|
<link rel="copyright" title="{{ _('Copyright') }}" href="{{ pathto('copyright') }}" />
|
||||||
|
{%- endif %}
|
||||||
|
<link rel="top" title="{{ docstitle|e }}" href="{{ pathto('index') }}" />
|
||||||
|
{%- if parents %}
|
||||||
|
<link rel="up" title="{{ parents[-1].title|striptags|e }}" href="{{ parents[-1].link|e }}" />
|
||||||
|
{%- endif %}
|
||||||
|
{%- if next %}
|
||||||
|
<link rel="next" title="{{ next.title|striptags|e }}" href="{{ next.link|e }}" />
|
||||||
|
{%- endif %}
|
||||||
|
{%- if prev %}
|
||||||
|
<link rel="prev" title="{{ prev.title|striptags|e }}" href="{{ prev.link|e }}" />
|
||||||
|
{%- endif %}
|
||||||
|
{%- endblock %}
|
||||||
|
{%- block extrahead %} {% endblock %}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{%- block header %}{% endblock %}
|
||||||
|
|
||||||
|
{%- block relbar1 %}{{ relbar() }}{% endblock %}
|
||||||
|
|
||||||
|
{%- block content %}
|
||||||
|
{%- block sidebar1 %} {# possible location for sidebar #} {% endblock %}
|
||||||
|
|
||||||
|
<div class="document">
|
||||||
|
{%- block document %}
|
||||||
|
<div class="documentwrapper">
|
||||||
|
{%- if render_sidebar %}
|
||||||
|
<div class="bodywrapper">
|
||||||
|
{%- endif %}
|
||||||
|
<div class="body">
|
||||||
|
{% block body %} {% endblock %}
|
||||||
|
</div>
|
||||||
|
{%- if render_sidebar %}
|
||||||
|
</div>
|
||||||
|
{%- endif %}
|
||||||
|
</div>
|
||||||
|
{%- endblock %}
|
||||||
|
|
||||||
|
{%- block sidebar2 %}{{ sidebar() }}{% endblock %}
|
||||||
|
<div class="clearer"></div>
|
||||||
|
</div>
|
||||||
|
{%- endblock %}
|
||||||
|
|
||||||
|
{%- block relbar2 %}{{ relbar() }}{% endblock %}
|
||||||
|
|
||||||
|
{%- block footer %}
|
||||||
|
<div class="footer">
|
||||||
|
{%- if show_copyright %}
|
||||||
|
{%- if hasdoc('copyright') %}
|
||||||
|
{% trans path=pathto('copyright'), copyright=copyright|e %}© <a href="{{ path }}">Copyright</a> {{ copyright }}.{% endtrans %}
|
||||||
|
{%- else %}
|
||||||
|
{% trans copyright=copyright|e %}© Copyright {{ copyright }}.{% endtrans %}
|
||||||
|
{%- endif %}
|
||||||
|
{%- endif %}
|
||||||
|
{%- if last_updated %}
|
||||||
|
{% trans last_updated=last_updated|e %}Last updated on {{ last_updated }}.{% endtrans %}
|
||||||
|
{%- endif %}
|
||||||
|
{%- if show_sphinx %}
|
||||||
|
{% trans sphinx_version=sphinx_version|e %}Created using <a href="http://sphinx-doc.org/">Sphinx</a> {{ sphinx_version }}.{% endtrans %}
|
||||||
|
{%- endif %}
|
||||||
|
</div>
|
||||||
|
<p>asdf asdf asdf asdf 22</p>
|
||||||
|
{%- endblock %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
50
doc/_themes/pretix_theme/search.html
vendored
Normal file
50
doc/_themes/pretix_theme/search.html
vendored
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
{#
|
||||||
|
basic/search.html
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Template for the search page.
|
||||||
|
|
||||||
|
:copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS.
|
||||||
|
:license: BSD, see LICENSE for details.
|
||||||
|
#}
|
||||||
|
{%- extends "layout.html" %}
|
||||||
|
{% set title = _('Search') %}
|
||||||
|
{% set script_files = script_files + ['_static/searchtools.js'] %}
|
||||||
|
{% block footer %}
|
||||||
|
<script type="text/javascript">
|
||||||
|
jQuery(function() { Search.loadIndex("{{ pathto('searchindex.js', 1) }}"); });
|
||||||
|
</script>
|
||||||
|
{# this is used when loading the search index using $.ajax fails,
|
||||||
|
such as on Chrome for documents on localhost #}
|
||||||
|
<script type="text/javascript" id="searchindexloader"></script>
|
||||||
|
{{ super() }}
|
||||||
|
{% endblock %}
|
||||||
|
{% block body %}
|
||||||
|
<noscript>
|
||||||
|
<div id="fallback" class="admonition warning">
|
||||||
|
<p class="last">
|
||||||
|
{% trans %}Please activate JavaScript to enable the search
|
||||||
|
functionality.{% endtrans %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</noscript>
|
||||||
|
|
||||||
|
{% if search_performed %}
|
||||||
|
<h2>{{ _('Search Results') }}</h2>
|
||||||
|
{% if not search_results %}
|
||||||
|
<p>{{ _('Your search did not match any documents. Please make sure that all words are spelled correctly and that you\'ve selected enough categories.') }}</p>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
<div id="search-results">
|
||||||
|
{% if search_results %}
|
||||||
|
<ul>
|
||||||
|
{% for href, caption, context in search_results %}
|
||||||
|
<li>
|
||||||
|
<a href="{{ pathto(item.href) }}">{{ caption }}</a>
|
||||||
|
<p class="context">{{ context|e }}</p>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
9
doc/_themes/pretix_theme/searchbox.html
vendored
Normal file
9
doc/_themes/pretix_theme/searchbox.html
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{%- if builder != 'singlehtml' %}
|
||||||
|
<div role="search">
|
||||||
|
<form id="rtd-search-form" class="wy-form" action="{{ pathto('search') }}" method="get">
|
||||||
|
<input type="text" name="q" placeholder="{{ _('Search docs') }}" />
|
||||||
|
<input type="hidden" name="check_keywords" value="yes" />
|
||||||
|
<input type="hidden" name="area" value="default" />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{%- endif %}
|
||||||
2
doc/_themes/pretix_theme/static/css/badge_only.css
vendored
Normal file
2
doc/_themes/pretix_theme/static/css/badge_only.css
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
.fa:before{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-weight:normal;font-style:normal;src:url("../font/fontawesome_webfont.eot");src:url("../font/fontawesome_webfont.eot?#iefix") format("embedded-opentype"),url("../font/fontawesome_webfont.woff") format("woff"),url("../font/fontawesome_webfont.ttf") format("truetype"),url("../font/fontawesome_webfont.svg#FontAwesome") format("svg")}.fa:before{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;text-decoration:inherit}a .fa{display:inline-block;text-decoration:inherit}li .fa{display:inline-block}li .fa-large:before,li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-0.8em}ul.fas li .fa{width:0.8em}ul.fas li .fa-large:before,ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before{content:""}.icon-book:before{content:""}.fa-caret-down:before{content:""}.icon-caret-down:before{content:""}.fa-caret-up:before{content:""}.icon-caret-up:before{content:""}.fa-caret-left:before{content:""}.icon-caret-left:before{content:""}.fa-caret-right:before{content:""}.icon-caret-right:before{content:""}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;border-top:solid 10px #343131;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;z-index:400}.rst-versions a{color:#2980B9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27AE60;*zoom:1}.rst-versions .rst-current-version:before,.rst-versions .rst-current-version:after{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book{float:left}.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#E74C3C;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#F1C40F;color:#000}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:gray;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:solid 1px #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px}.rst-versions.rst-badge .icon-book{float:none}.rst-versions.rst-badge .fa-book{float:none}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book{float:left}.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge .rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width: 768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}
|
||||||
|
/*# sourceMappingURL=badge_only.css.map */
|
||||||
6065
doc/_themes/pretix_theme/static/css/pretix.css
vendored
Normal file
6065
doc/_themes/pretix_theme/static/css/pretix.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
4
doc/_themes/pretix_theme/static/js/modernizr.min.js
vendored
Normal file
4
doc/_themes/pretix_theme/static/js/modernizr.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
169
doc/_themes/pretix_theme/static/js/theme.js
vendored
Normal file
169
doc/_themes/pretix_theme/static/js/theme.js
vendored
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({"sphinx-rtd-theme":[function(require,module,exports){
|
||||||
|
var jQuery = (typeof(window) != 'undefined') ? window.jQuery : require('jquery');
|
||||||
|
|
||||||
|
// Sphinx theme nav state
|
||||||
|
function ThemeNav () {
|
||||||
|
|
||||||
|
var nav = {
|
||||||
|
navBar: null,
|
||||||
|
win: null,
|
||||||
|
winScroll: false,
|
||||||
|
winResize: false,
|
||||||
|
linkScroll: false,
|
||||||
|
winPosition: 0,
|
||||||
|
winHeight: null,
|
||||||
|
docHeight: null,
|
||||||
|
isRunning: false
|
||||||
|
};
|
||||||
|
|
||||||
|
nav.enable = function () {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (!self.isRunning) {
|
||||||
|
self.isRunning = true;
|
||||||
|
jQuery(function ($) {
|
||||||
|
self.init($);
|
||||||
|
|
||||||
|
self.reset();
|
||||||
|
self.win.on('hashchange', self.reset);
|
||||||
|
|
||||||
|
// Set scroll monitor
|
||||||
|
self.win.on('scroll', function () {
|
||||||
|
if (!self.linkScroll) {
|
||||||
|
self.winScroll = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setInterval(function () { if (self.winScroll) self.onScroll(); }, 25);
|
||||||
|
|
||||||
|
// Set resize monitor
|
||||||
|
self.win.on('resize', function () {
|
||||||
|
self.winResize = true;
|
||||||
|
});
|
||||||
|
setInterval(function () { if (self.winResize) self.onResize(); }, 25);
|
||||||
|
self.onResize();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
nav.init = function ($) {
|
||||||
|
var doc = $(document),
|
||||||
|
self = this;
|
||||||
|
|
||||||
|
this.navBar = $('div.wy-side-scroll:first');
|
||||||
|
this.win = $(window);
|
||||||
|
|
||||||
|
// Set up javascript UX bits
|
||||||
|
$(document)
|
||||||
|
// Shift nav in mobile when clicking the menu.
|
||||||
|
.on('click', "[data-toggle='wy-nav-top']", function() {
|
||||||
|
$("[data-toggle='wy-nav-shift']").toggleClass("shift");
|
||||||
|
$("[data-toggle='rst-versions']").toggleClass("shift");
|
||||||
|
})
|
||||||
|
|
||||||
|
// Nav menu link click operations
|
||||||
|
.on('click', ".wy-menu-vertical .current ul li a", function() {
|
||||||
|
var target = $(this);
|
||||||
|
// Close menu when you click a link.
|
||||||
|
$("[data-toggle='wy-nav-shift']").removeClass("shift");
|
||||||
|
$("[data-toggle='rst-versions']").toggleClass("shift");
|
||||||
|
// Handle dynamic display of l3 and l4 nav lists
|
||||||
|
self.toggleCurrent(target);
|
||||||
|
self.hashChange();
|
||||||
|
})
|
||||||
|
.on('click', "[data-toggle='rst-current-version']", function() {
|
||||||
|
$("[data-toggle='rst-versions']").toggleClass("shift-up");
|
||||||
|
})
|
||||||
|
|
||||||
|
// Make tables responsive
|
||||||
|
$("table.docutils:not(.field-list)")
|
||||||
|
.wrap("<div class='wy-table-responsive'></div>");
|
||||||
|
|
||||||
|
// Add expand links to all parents of nested ul
|
||||||
|
$('.wy-menu-vertical ul').not('.simple').siblings('a').each(function () {
|
||||||
|
var link = $(this);
|
||||||
|
expand = $('<span class="toctree-expand"></span>');
|
||||||
|
expand.on('click', function (ev) {
|
||||||
|
self.toggleCurrent(link);
|
||||||
|
ev.stopPropagation();
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
link.prepend(expand);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
nav.reset = function () {
|
||||||
|
// Get anchor from URL and open up nested nav
|
||||||
|
var anchor = encodeURI(window.location.hash);
|
||||||
|
if (anchor) {
|
||||||
|
try {
|
||||||
|
var link = $('.wy-menu-vertical')
|
||||||
|
.find('[href="' + anchor + '"]');
|
||||||
|
// If we didn't find a link, it may be because we clicked on
|
||||||
|
// something that is not in the sidebar (eg: when using
|
||||||
|
// sphinxcontrib.httpdomain it generates headerlinks but those
|
||||||
|
// aren't picked up and placed in the toctree). So let's find
|
||||||
|
// the closest header in the document and try with that one.
|
||||||
|
if (link.length === 0) {
|
||||||
|
var doc_link = $('.document a[href="' + anchor + '"]');
|
||||||
|
var closest_section = doc_link.closest('div.section');
|
||||||
|
// Try again with the closest section entry.
|
||||||
|
link = $('.wy-menu-vertical')
|
||||||
|
.find('[href="#' + closest_section.attr("id") + '"]');
|
||||||
|
|
||||||
|
}
|
||||||
|
$('.wy-menu-vertical li.toctree-l1 li.current')
|
||||||
|
.removeClass('current');
|
||||||
|
link.closest('li.toctree-l2').addClass('current');
|
||||||
|
link.closest('li.toctree-l3').addClass('current');
|
||||||
|
link.closest('li.toctree-l4').addClass('current');
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.log("Error expanding nav for anchor", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
nav.onScroll = function () {
|
||||||
|
this.winScroll = false;
|
||||||
|
var newWinPosition = this.win.scrollTop(),
|
||||||
|
winBottom = newWinPosition + this.winHeight,
|
||||||
|
navPosition = this.navBar.scrollTop(),
|
||||||
|
newNavPosition = navPosition + (newWinPosition - this.winPosition);
|
||||||
|
if (newWinPosition < 0 || winBottom > this.docHeight) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.navBar.scrollTop(newNavPosition);
|
||||||
|
this.winPosition = newWinPosition;
|
||||||
|
};
|
||||||
|
|
||||||
|
nav.onResize = function () {
|
||||||
|
this.winResize = false;
|
||||||
|
this.winHeight = this.win.height();
|
||||||
|
this.docHeight = $(document).height();
|
||||||
|
};
|
||||||
|
|
||||||
|
nav.hashChange = function () {
|
||||||
|
this.linkScroll = true;
|
||||||
|
this.win.one('hashchange', function () {
|
||||||
|
this.linkScroll = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
nav.toggleCurrent = function (elem) {
|
||||||
|
var parent_li = elem.closest('li');
|
||||||
|
parent_li.siblings('li.current').removeClass('current');
|
||||||
|
parent_li.siblings().find('li.current').removeClass('current');
|
||||||
|
parent_li.find('> ul li.current').removeClass('current');
|
||||||
|
parent_li.toggleClass('current');
|
||||||
|
}
|
||||||
|
|
||||||
|
return nav;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.ThemeNav = ThemeNav();
|
||||||
|
|
||||||
|
if (typeof(window) != 'undefined') {
|
||||||
|
window.SphinxRtdTheme = { StickyNav: module.exports.ThemeNav };
|
||||||
|
}
|
||||||
|
|
||||||
|
},{"jquery":"jquery"}]},{},["sphinx-rtd-theme"]);
|
||||||
14
doc/_themes/pretix_theme/theme.conf
vendored
Normal file
14
doc/_themes/pretix_theme/theme.conf
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
[theme]
|
||||||
|
inherit = basic
|
||||||
|
stylesheet = css/pretix.css
|
||||||
|
|
||||||
|
[options]
|
||||||
|
typekit_id = hiw1hhg
|
||||||
|
analytics_id =
|
||||||
|
sticky_navigation = False
|
||||||
|
logo_only =
|
||||||
|
collapse_navigation = False
|
||||||
|
display_version = True
|
||||||
|
navigation_depth = 4
|
||||||
|
prev_next_buttons_location = bottom
|
||||||
|
canonical_url =
|
||||||
37
doc/_themes/pretix_theme/versions.html
vendored
Normal file
37
doc/_themes/pretix_theme/versions.html
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
{% if READTHEDOCS %}
|
||||||
|
{# Add rst-badge after rst-versions for small badge style. #}
|
||||||
|
<div class="rst-versions" data-toggle="rst-versions" role="note" aria-label="versions">
|
||||||
|
<span class="rst-current-version" data-toggle="rst-current-version">
|
||||||
|
<span class="fa fa-book"> Read the Docs</span>
|
||||||
|
v: {{ current_version }}
|
||||||
|
<span class="fa fa-caret-down"></span>
|
||||||
|
</span>
|
||||||
|
<div class="rst-other-versions">
|
||||||
|
<dl>
|
||||||
|
<dt>{{ _('Versions') }}</dt>
|
||||||
|
{% for slug, url in versions %}
|
||||||
|
<dd><a href="{{ url }}">{{ slug }}</a></dd>
|
||||||
|
{% endfor %}
|
||||||
|
</dl>
|
||||||
|
<dl>
|
||||||
|
<dt>{{ _('Downloads') }}</dt>
|
||||||
|
{% for type, url in downloads %}
|
||||||
|
<dd><a href="{{ url }}">{{ type }}</a></dd>
|
||||||
|
{% endfor %}
|
||||||
|
</dl>
|
||||||
|
<dl>
|
||||||
|
<dt>{{ _('On Read the Docs') }}</dt>
|
||||||
|
<dd>
|
||||||
|
<a href="//{{ PRODUCTION_DOMAIN }}/projects/{{ slug }}/?fromdocs={{ slug }}">{{ _('Project Home') }}</a>
|
||||||
|
</dd>
|
||||||
|
<dd>
|
||||||
|
<a href="//{{ PRODUCTION_DOMAIN }}/builds/{{ slug }}/?fromdocs={{ slug }}">{{ _('Builds') }}</a>
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
<hr/>
|
||||||
|
{% trans %}Free document hosting provided by <a href="http://www.readthedocs.org">Read the Docs</a>.{% endtrans %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
|
.. _`admindocs`:
|
||||||
|
|
||||||
Administrator documentation
|
Administrator documentation
|
||||||
===========================
|
===========================
|
||||||
|
|
||||||
This documentation is for everyone who wants to install pretix on a server.
|
This documentation is for everyone who wants to install pretix on a server.
|
||||||
|
|
||||||
Contents:
|
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ If there is a problem, a status code in the ``5xx`` range will be returned.
|
|||||||
Performance monitoring
|
Performance monitoring
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
If you to generate detailled performance statistics of your pretix installation, there is an
|
If you want to generate detailed performance statistics of your pretix installation, there is an
|
||||||
endpoint at ``https://pretix.mydomain.com/metrics`` (no slash at the end) which returns a
|
endpoint at ``https://pretix.mydomain.com/metrics`` (no slash at the end) which returns a
|
||||||
number of values in the text format understood by monitoring tools like Prometheus_. This data
|
number of values in the text format understood by monitoring tools like Prometheus_. This data
|
||||||
is only collected and exposed if you enable it in the :ref:`metrics-settings` section of your
|
is only collected and exposed if you enable it in the :ref:`metrics-settings` section of your
|
||||||
|
|||||||
145
doc/api/fundamentals.rst
Normal file
145
doc/api/fundamentals.rst
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
Basic concepts
|
||||||
|
==============
|
||||||
|
|
||||||
|
This page describes basic concepts and definition that you need to know to interact
|
||||||
|
with pretix' REST API, such as authentication, pagination and similar definitions.
|
||||||
|
|
||||||
|
Obtaining an API token
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
To authenticate your API requests, you need to obtain an API token. You can create a
|
||||||
|
token in the pretix web interface on the level of organizer teams. Create a new team
|
||||||
|
or choose an existing team that has the level of permissions the token should have and
|
||||||
|
create a new token using the form below the list of team members:
|
||||||
|
|
||||||
|
.. image:: img/token_form.png
|
||||||
|
|
||||||
|
You can enter a description for the token to distinguish from other tokens later on.
|
||||||
|
Once you click "Add", you will be provided with an API token in the success message.
|
||||||
|
Copy this token, as you won't be able to retrieve it again.
|
||||||
|
|
||||||
|
.. image:: img/token_success.png
|
||||||
|
|
||||||
|
Authentication
|
||||||
|
--------------
|
||||||
|
|
||||||
|
You need to include the API token with every request to pretix' API in the ``Authorization`` header
|
||||||
|
like the following:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
:emphasize-lines: 3
|
||||||
|
|
||||||
|
GET /api/v1/organizers/ HTTP/1.1
|
||||||
|
Host: pretix.eu
|
||||||
|
Authorization: Token e1l6gq2ye72thbwkacj7jbri7a7tvxe614ojv8ybureain92ocub46t5gab5966k
|
||||||
|
|
||||||
|
.. note:: The API currently also supports authentication via browser sessions, i.e. the
|
||||||
|
same way that you authenticate with pretix when using the browser interface.
|
||||||
|
Using this type of authentication is *not* officially supported for use by
|
||||||
|
third-party clients and might change or be removed at any time. We plan on
|
||||||
|
adding OAuth2 support in the future for user-level authentication. If you want
|
||||||
|
to use session authentication, be sure to comply with Django's `CSRF policies`_.
|
||||||
|
|
||||||
|
Compatibility
|
||||||
|
-------------
|
||||||
|
|
||||||
|
We currently see pretix' API as a beta-stage feature. We therefore do not give any guarantees
|
||||||
|
for compatibility between feature releases of pretix (such as 1.5 and 1.6). However, as always,
|
||||||
|
we try not to break things when we don't need to. Any backwards-incompatible changes will be
|
||||||
|
prominently noted in the release notes.
|
||||||
|
|
||||||
|
We treat the following types of changes as *backwards-compatible* so we ask you to make sure
|
||||||
|
that your clients can deal with them properly:
|
||||||
|
|
||||||
|
* Support of new API endpoints
|
||||||
|
* Support of new HTTP methods for a given API endpoint
|
||||||
|
* Support of new query parameters for a given API endpoint
|
||||||
|
* New fields contained in API responses
|
||||||
|
|
||||||
|
We treat the following types of changes as *backwards-incompatible*:
|
||||||
|
|
||||||
|
* Type changes of fields in API responses
|
||||||
|
* New required input fields for an API endpoint
|
||||||
|
* New required type for input fields of an API endpoint
|
||||||
|
* Removal of endpoints, API methods or fields
|
||||||
|
|
||||||
|
Pagination
|
||||||
|
----------
|
||||||
|
|
||||||
|
Most lists of objects returned by pretix' API will be paginated. The response will take
|
||||||
|
the form of:
|
||||||
|
|
||||||
|
.. sourcecode:: javascript
|
||||||
|
|
||||||
|
{
|
||||||
|
"count": 117,
|
||||||
|
"next": "https://pretix.eu/api/v1/organizers/?page=2",
|
||||||
|
"previous": null,
|
||||||
|
"results": […],
|
||||||
|
}
|
||||||
|
|
||||||
|
As you can see, the response contains the total number of results in the field ``count``.
|
||||||
|
The fields ``next`` and ``previous`` contain links to the next and previous page of results,
|
||||||
|
respectively, or ``null`` if there is no such page. You can use those URLs to retrieve the
|
||||||
|
respective page.
|
||||||
|
|
||||||
|
The field ``results`` contains a list of objects representing the first results. For most
|
||||||
|
objects, every page contains 50 results.
|
||||||
|
|
||||||
|
Errors
|
||||||
|
------
|
||||||
|
|
||||||
|
Error responses (of type 400-499) are returned in one of the following forms, depending on
|
||||||
|
the type of error. General errors look like:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
HTTP/1.1 405 Method Not Allowed
|
||||||
|
Content-Type: application/json
|
||||||
|
Content-Length: 42
|
||||||
|
|
||||||
|
{"detail": "Method 'DELETE' not allowed."}
|
||||||
|
|
||||||
|
Field specific input errors include the name of the offending fields as keys in the response:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
HTTP/1.1 400 Bad Request
|
||||||
|
Content-Type: application/json
|
||||||
|
Content-Length: 94
|
||||||
|
|
||||||
|
{"amount": ["A valid integer is required."], "description": ["This field may not be blank."]}
|
||||||
|
|
||||||
|
|
||||||
|
Data types
|
||||||
|
----------
|
||||||
|
|
||||||
|
All structured API responses are returned in JSON format using standard JSON data types such
|
||||||
|
as integers, floating point numbers, strings, lists, objects and booleans. Most fields can
|
||||||
|
be ``null`` as well.
|
||||||
|
|
||||||
|
The following table shows some data types that have no native JSON representation and how
|
||||||
|
we serialize them to JSON.
|
||||||
|
|
||||||
|
===================== ============================ ===================================
|
||||||
|
Internal pretix type JSON representation Examples
|
||||||
|
===================== ============================ ===================================
|
||||||
|
Datetime String in ISO 8601 format ``"2017-12-27T10:00:00Z"``
|
||||||
|
with timezone (normally UTC) ``"2017-12-27T10:00:00.596934Z"``,
|
||||||
|
``"2017-12-27T10:00:00+02:00"``
|
||||||
|
Date String in ISO 8601 format ``2017-12-27``
|
||||||
|
Multi-lingual string Object of strings ``{"en": "red", "de": "rot", "de_Informal": "rot"}``
|
||||||
|
Money String with decimal number ``"23.42"``
|
||||||
|
Currency String with ISO 4217 code ``"EUR"``, ``"USD"``
|
||||||
|
===================== ============================ ===================================
|
||||||
|
|
||||||
|
Query parameters
|
||||||
|
^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Most list endpoints allow a filtering of the results using query parameters. In this case, booleans should be passed
|
||||||
|
as the string values ``true`` and ``false``.
|
||||||
|
|
||||||
|
If the ``ordering`` parameter is documented for a resource, you can use it to sort the result set by one of the allowed
|
||||||
|
fields. Prepend a ``-`` to the field name to reverse the sort order.
|
||||||
|
|
||||||
|
.. _CSRF policies: https://docs.djangoproject.com/en/1.11/ref/csrf/#ajax
|
||||||
BIN
doc/api/img/token_form.png
Normal file
BIN
doc/api/img/token_form.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.6 KiB |
BIN
doc/api/img/token_success.png
Normal file
BIN
doc/api/img/token_success.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
17
doc/api/index.rst
Normal file
17
doc/api/index.rst
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
.. _`rest-api`:
|
||||||
|
|
||||||
|
REST API
|
||||||
|
========
|
||||||
|
|
||||||
|
This part of the documentation contains information about the REST-style API
|
||||||
|
exposed by pretix since version 1.5 that can be used by third-party programs
|
||||||
|
to interact with pretix and its data structures.
|
||||||
|
|
||||||
|
Currently, the API provides mostly read-only capabilities, but it will be extended
|
||||||
|
in functionality over time.
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
fundamentals
|
||||||
|
resources/index
|
||||||
108
doc/api/resources/categories.rst
Normal file
108
doc/api/resources/categories.rst
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
Item categories
|
||||||
|
===============
|
||||||
|
|
||||||
|
Resource description
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
Categories provide grouping for items (better known as products).
|
||||||
|
The category resource contains the following public fields:
|
||||||
|
|
||||||
|
.. rst-class:: rest-resource-table
|
||||||
|
|
||||||
|
===================================== ========================== =======================================================
|
||||||
|
Field Type Description
|
||||||
|
===================================== ========================== =======================================================
|
||||||
|
id integer Internal ID of the category
|
||||||
|
name multi-lingual string The category's visible name
|
||||||
|
description multi-lingual string A public description (might include markdown, can
|
||||||
|
be ``null``)
|
||||||
|
position integer An integer, used for sorting the categories
|
||||||
|
is_addon boolean If ``True``, items within this category are not on sale
|
||||||
|
on their own but the category provides a source for
|
||||||
|
defining add-ons for other products.
|
||||||
|
===================================== ========================== =======================================================
|
||||||
|
|
||||||
|
|
||||||
|
Endpoints
|
||||||
|
---------
|
||||||
|
|
||||||
|
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/categories/
|
||||||
|
|
||||||
|
Returns a list of all categories within a given event.
|
||||||
|
|
||||||
|
**Example request**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
GET /api/v1/organizers/bigevents/events/sampleconf/categories/ HTTP/1.1
|
||||||
|
Host: pretix.eu
|
||||||
|
Accept: application/json, text/javascript
|
||||||
|
|
||||||
|
**Example response**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Vary: Accept
|
||||||
|
Content-Type: text/javascript
|
||||||
|
|
||||||
|
{
|
||||||
|
"count": 1,
|
||||||
|
"next": null,
|
||||||
|
"previous": null,
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": {"en": "Tickets"},
|
||||||
|
"description": {"en": "Tickets are what you need to get in."},
|
||||||
|
"position": 1,
|
||||||
|
"is_addon": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
:query integer page: The page number in case of a multi-page result set, default is 1
|
||||||
|
:query boolean is_addon: If set to ``true`` or ``false``, only categories with this value for the field ``is_addon`` will be
|
||||||
|
returned.
|
||||||
|
:query string ordering: Manually set the ordering of results. Valid fields to be used are ``id`` and ``position``.
|
||||||
|
Default: ``position``
|
||||||
|
:param organizer: The ``slug`` field of the organizer to fetch
|
||||||
|
:param event: The ``slug`` field of the event to fetch
|
||||||
|
: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.
|
||||||
|
|
||||||
|
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/categories/(id)/
|
||||||
|
|
||||||
|
Returns information on one category, identified by its ID.
|
||||||
|
|
||||||
|
**Example request**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
GET /api/v1/organizers/bigevents/events/sampleconf/categories/1/ HTTP/1.1
|
||||||
|
Host: pretix.eu
|
||||||
|
Accept: application/json, text/javascript
|
||||||
|
|
||||||
|
**Example response**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Vary: Accept
|
||||||
|
Content-Type: text/javascript
|
||||||
|
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": {"en": "Tickets"},
|
||||||
|
"description": {"en": "Tickets are what you need to get in."},
|
||||||
|
"position": 1,
|
||||||
|
"is_addon": false
|
||||||
|
}
|
||||||
|
|
||||||
|
:param organizer: The ``slug`` field of the organizer to fetch
|
||||||
|
:param event: The ``slug`` field of the event to fetch
|
||||||
|
:param id: The ``id`` field of the category to fetch
|
||||||
|
: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.
|
||||||
118
doc/api/resources/events.rst
Normal file
118
doc/api/resources/events.rst
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
Events
|
||||||
|
======
|
||||||
|
|
||||||
|
Resource description
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
The event resource contains the following public fields:
|
||||||
|
|
||||||
|
.. rst-class:: rest-resource-table
|
||||||
|
|
||||||
|
===================================== ========================== =======================================================
|
||||||
|
Field Type Description
|
||||||
|
===================================== ========================== =======================================================
|
||||||
|
name multi-lingual string The event's full name
|
||||||
|
slug string A short form of the name, used e.g. in URLs.
|
||||||
|
live boolean If ``true``, the event ticket shop is publicly
|
||||||
|
available.
|
||||||
|
currency string The currency this event is handled in.
|
||||||
|
date_from datetime The event's start date
|
||||||
|
date_to datetime The event's end date (or ``null``)
|
||||||
|
date_admission datetime The event's admission date (or ``null``)
|
||||||
|
is_public boolean If ``true``, the event shows up in places like the
|
||||||
|
organizer's public list of events
|
||||||
|
presale_start datetime The date at which the ticket shop opens (or ``null``)
|
||||||
|
presale_end datetime The date at which the ticket shop closes (or ``null``)
|
||||||
|
location multi-lingual string The event location (or ``null``)
|
||||||
|
===================================== ========================== =======================================================
|
||||||
|
|
||||||
|
|
||||||
|
Endpoints
|
||||||
|
---------
|
||||||
|
|
||||||
|
.. http:get:: /api/v1/organizers/(organizer)/events/
|
||||||
|
|
||||||
|
Returns a list of all events within a given organizer the authenticated user/token has access to.
|
||||||
|
|
||||||
|
**Example request**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
GET /api/v1/organizers/bigevents/events/ HTTP/1.1
|
||||||
|
Host: pretix.eu
|
||||||
|
Accept: application/json, text/javascript
|
||||||
|
|
||||||
|
**Example response**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Vary: Accept
|
||||||
|
Content-Type: text/javascript
|
||||||
|
|
||||||
|
{
|
||||||
|
"count": 1,
|
||||||
|
"next": null,
|
||||||
|
"previous": null,
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"name": {"en": "Sample Conference"},
|
||||||
|
"slug": "sampleconf",
|
||||||
|
"live": false,
|
||||||
|
"currency": "EUR",
|
||||||
|
"date_from": "2017-12-27T10:00:00Z",
|
||||||
|
"date_to": null,
|
||||||
|
"date_admission": null,
|
||||||
|
"is_public": null,
|
||||||
|
"presale_start": null,
|
||||||
|
"presale_end": null,
|
||||||
|
"location": null,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
:query page: The page number in case of a multi-page result set, default is 1
|
||||||
|
:param organizer: The ``slug`` field of a valid organizer
|
||||||
|
:statuscode 200: no error
|
||||||
|
:statuscode 401: Authentication failure
|
||||||
|
:statuscode 403: The requested organizer does not exist **or** you have no permission to view it.
|
||||||
|
|
||||||
|
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/
|
||||||
|
|
||||||
|
Returns information on one event, identified by its slug.
|
||||||
|
|
||||||
|
**Example request**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
GET /api/v1/organizers/bigevents/events/sampleconf/ HTTP/1.1
|
||||||
|
Host: pretix.eu
|
||||||
|
Accept: application/json, text/javascript
|
||||||
|
|
||||||
|
**Example response**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Vary: Accept
|
||||||
|
Content-Type: text/javascript
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": {"en": "Sample Conference"},
|
||||||
|
"slug": "sampleconf",
|
||||||
|
"live": false,
|
||||||
|
"currency": "EUR",
|
||||||
|
"date_from": "2017-12-27T10:00:00Z",
|
||||||
|
"date_to": null,
|
||||||
|
"date_admission": null,
|
||||||
|
"is_public": false,
|
||||||
|
"presale_start": null,
|
||||||
|
"presale_end": null,
|
||||||
|
"location": null,
|
||||||
|
}
|
||||||
|
|
||||||
|
:param organizer: The ``slug`` field of the organizer to fetch
|
||||||
|
:param event: The ``slug`` field of the event to fetch
|
||||||
|
:statuscode 200: no error
|
||||||
|
:statuscode 401: Authentication failure
|
||||||
|
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view it.
|
||||||
16
doc/api/resources/index.rst
Normal file
16
doc/api/resources/index.rst
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
Resources and endpoints
|
||||||
|
=======================
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
organizers
|
||||||
|
events
|
||||||
|
categories
|
||||||
|
items
|
||||||
|
questions
|
||||||
|
quotas
|
||||||
|
orders
|
||||||
|
invoices
|
||||||
|
vouchers
|
||||||
|
waitinglist
|
||||||
187
doc/api/resources/invoices.rst
Normal file
187
doc/api/resources/invoices.rst
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
Invoices
|
||||||
|
========
|
||||||
|
|
||||||
|
Resource description
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
The invoice resource contains the following public fields:
|
||||||
|
|
||||||
|
.. rst-class:: rest-resource-table
|
||||||
|
|
||||||
|
===================================== ========================== =======================================================
|
||||||
|
Field Type Description
|
||||||
|
===================================== ========================== =======================================================
|
||||||
|
invoice_no string Invoice number (without prefix)
|
||||||
|
order string Order code of the order this invoice belongs to
|
||||||
|
is_cancellation boolean ``True``, if this invoice is the cancellation of a
|
||||||
|
different invoice.
|
||||||
|
invoice_from string Sender address
|
||||||
|
invoice_to string Receiver address
|
||||||
|
date date Invoice date
|
||||||
|
refers string Invoice number of an invoice this invoice refers to
|
||||||
|
(for example a cancellation refers to the invoice it
|
||||||
|
cancels) or ``null``.
|
||||||
|
locale string Invoice locale
|
||||||
|
introductory_text string Text to be printed above 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 information
|
||||||
|
footer_text string Text to be printed in the page footer area
|
||||||
|
lines list of objects The actual invoice contents
|
||||||
|
├ description string Text representing the invoice line (e.g. product name)
|
||||||
|
├ gross_value money (string) Price including VAT
|
||||||
|
├ tax_value money (string) VAT amount
|
||||||
|
└ tax_rate decimal (string) Used VAT rate
|
||||||
|
===================================== ========================== =======================================================
|
||||||
|
|
||||||
|
|
||||||
|
Endpoints
|
||||||
|
---------
|
||||||
|
|
||||||
|
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/invoices/
|
||||||
|
|
||||||
|
Returns a list of all invoices within a given event.
|
||||||
|
|
||||||
|
**Example request**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
GET /api/v1/organizers/bigevents/events/sampleconf/invoices/ HTTP/1.1
|
||||||
|
Host: pretix.eu
|
||||||
|
Accept: application/json, text/javascript
|
||||||
|
|
||||||
|
**Example response**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Vary: Accept
|
||||||
|
Content-Type: text/javascript
|
||||||
|
|
||||||
|
{
|
||||||
|
"count": 1,
|
||||||
|
"next": null,
|
||||||
|
"previous": null,
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"invoice_no": "00001",
|
||||||
|
"order": "ABC12",
|
||||||
|
"is_cancellation": false,
|
||||||
|
"invoice_from": "Big Events LLC\nDemo street 12\nDemo town",
|
||||||
|
"invoice_to": "Sample company\nJohn Doe\nTest street 12\n12345 Testington\nTestikistan\nVAT ID: EU123456789",
|
||||||
|
"date": "2017-12-01",
|
||||||
|
"refers": null,
|
||||||
|
"locale": "en",
|
||||||
|
"introductory_text": "thank you for your purchase of the following items:",
|
||||||
|
"additional_text": "We are looking forward to see you on our conference!",
|
||||||
|
"payment_provider_text": "Please transfer the money to our account ABC…",
|
||||||
|
"footer_text": "Big Events LLC - Registration No. 123456 - VAT ID: EU0987654321",
|
||||||
|
"lines": [
|
||||||
|
{
|
||||||
|
"description": "Budget Ticket",
|
||||||
|
"gross_value": "23.00",
|
||||||
|
"tax_value": "0.00",
|
||||||
|
"tax_rate": "0.00"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
:query integer page: The page number in case of a multi-page result set, default is 1
|
||||||
|
:query boolean is_cancellation: If set to ``true`` or ``false``, only invoices with this value for the field
|
||||||
|
``is_cancellation`` will be returned.
|
||||||
|
:query string order: If set, only invoices belonging to the order with the given order code will be returned.
|
||||||
|
:query string refers: If set, only invoices refering to the given invoice will be returned.
|
||||||
|
:query string locale: If set, only invoices with the given locale will be returned.
|
||||||
|
:query string ordering: Manually set the ordering of results. Valid fields to be used are ``date`` and
|
||||||
|
``invoice_no``. Default: ``invoice_no``
|
||||||
|
:param organizer: The ``slug`` field of the organizer to fetch
|
||||||
|
:param event: The ``slug`` field of the event to fetch
|
||||||
|
: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.
|
||||||
|
|
||||||
|
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/invoices/(invoice_no)/
|
||||||
|
|
||||||
|
Returns information on one invoice, identified by its invoice number.
|
||||||
|
|
||||||
|
**Example request**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
GET /api/v1/organizers/bigevents/events/sampleconf/invoices/00001/ HTTP/1.1
|
||||||
|
Host: pretix.eu
|
||||||
|
Accept: application/json, text/javascript
|
||||||
|
|
||||||
|
**Example response**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Vary: Accept
|
||||||
|
Content-Type: text/javascript
|
||||||
|
|
||||||
|
{
|
||||||
|
"invoice_no": "00001",
|
||||||
|
"order": "ABC12",
|
||||||
|
"is_cancellation": false,
|
||||||
|
"invoice_from": "Big Events LLC\nDemo street 12\nDemo town",
|
||||||
|
"invoice_to": "Sample company\nJohn Doe\nTest street 12\n12345 Testington\nTestikistan\nVAT ID: EU123456789",
|
||||||
|
"date": "2017-12-01",
|
||||||
|
"refers": null,
|
||||||
|
"locale": "en",
|
||||||
|
"introductory_text": "thank you for your purchase of the following items:",
|
||||||
|
"additional_text": "We are looking forward to see you on our conference!",
|
||||||
|
"payment_provider_text": "Please transfer the money to our account ABC…",
|
||||||
|
"footer_text": "Big Events LLC - Registration No. 123456 - VAT ID: EU0987654321",
|
||||||
|
"lines": [
|
||||||
|
{
|
||||||
|
"description": "Budget Ticket",
|
||||||
|
"gross_value": "23.00",
|
||||||
|
"tax_value": "0.00",
|
||||||
|
"tax_rate": "0.00"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
:param organizer: The ``slug`` field of the organizer to fetch
|
||||||
|
:param event: The ``slug`` field of the event to fetch
|
||||||
|
:param invoice_no: The ``invoice_no`` field of the invoice to fetch
|
||||||
|
: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.
|
||||||
|
|
||||||
|
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/invoices/(invoice_no)/download/
|
||||||
|
|
||||||
|
Download an invoice in PDF format.
|
||||||
|
|
||||||
|
Note that in some cases the PDF file might not yet have been created. In that case, you will receive a status
|
||||||
|
code :http:statuscode:`409` and you are expected to retry the request after a short period of waiting.
|
||||||
|
|
||||||
|
**Example request**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
GET /api/v1/organizers/bigevents/events/sampleconf/invoices/00001/download/ 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/pdf
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
:param organizer: The ``slug`` field of the organizer to fetch
|
||||||
|
:param event: The ``slug`` field of the event to fetch
|
||||||
|
:param invoice_no: The ``invoice_no`` field of the invoice to fetch
|
||||||
|
: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.
|
||||||
|
:statuscode 409: The file is not yet ready and will now be prepared. Retry the request after waiting vor a few
|
||||||
|
seconds.
|
||||||
228
doc/api/resources/items.rst
Normal file
228
doc/api/resources/items.rst
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
Items
|
||||||
|
=====
|
||||||
|
|
||||||
|
Resource description
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
Items (better known as products) are the things that can be sold using pretix.
|
||||||
|
The item resource contains the following public fields:
|
||||||
|
|
||||||
|
.. rst-class:: rest-resource-table
|
||||||
|
|
||||||
|
===================================== ========================== =======================================================
|
||||||
|
Field Type Description
|
||||||
|
===================================== ========================== =======================================================
|
||||||
|
id integer Internal ID of the item
|
||||||
|
name multi-lingual string The item's visible name
|
||||||
|
default_price money (string) The item price that is applied if the price is not
|
||||||
|
overwritten by variations or other options.
|
||||||
|
category integer The ID of the category this item belongs to
|
||||||
|
(or ``null``).
|
||||||
|
active boolean If ``False``, the item is hidden from all public lists
|
||||||
|
and will not be sold.
|
||||||
|
description multi-lingual string A public description of the item. May contain Markdown
|
||||||
|
syntax or can be ``null``.
|
||||||
|
free_price boolean If ``True``, customers can change the price at which
|
||||||
|
they buy the product (however, the price can't be set
|
||||||
|
lower than the price defined by ``default_price`` or
|
||||||
|
otherwise).
|
||||||
|
tax_rate decimal (string) The VAT rate to be applied for this item.
|
||||||
|
admission boolean ``True`` for items that grant admission to the event
|
||||||
|
(such as primary tickets) and ``False`` for others
|
||||||
|
(such as add-ons or merchandise).
|
||||||
|
position integer An integer, used for sorting
|
||||||
|
picture string A product picture to be displayed in the shop
|
||||||
|
available_from datetime The first date time at which this item can be bought
|
||||||
|
(or ``null``).
|
||||||
|
available_until datetime The last date time at which this item can be bought
|
||||||
|
(or ``null``).
|
||||||
|
require_voucher boolean If ``True``, this item can only be bought using a
|
||||||
|
voucher that is specifically assigned to this item.
|
||||||
|
hide_without_voucher boolean If ``True``, this item is only shown during the voucher
|
||||||
|
redemption process, but not in the normal shop
|
||||||
|
frontend.
|
||||||
|
allow_cancel boolean If ``False``, customers cannot cancel orders containing
|
||||||
|
this item.
|
||||||
|
min_per_order integer This product can only be bought if it is included at
|
||||||
|
least this many times in the order (or ``null`` for no
|
||||||
|
limitation).
|
||||||
|
max_per_order integer This product can only be bought if it is included at
|
||||||
|
most this many times in the order (or ``null`` for no
|
||||||
|
limitation).
|
||||||
|
has_variations boolean Shows whether or not this item has variations
|
||||||
|
(read-only).
|
||||||
|
variations list of objects A list with one object for each variation of this item.
|
||||||
|
Can be empty.
|
||||||
|
├ id integer Internal ID of the variation
|
||||||
|
├ default_price money (string) The price set directly for this variation or ``null``
|
||||||
|
├ price money (string) The price used for this variation. This is either the
|
||||||
|
same as ``default_price`` if that value is set or equal
|
||||||
|
to the item's ``default_price``.
|
||||||
|
├ active boolean If ``False``, this variation will not be sold or shown.
|
||||||
|
├ description multi-lingual string A public description of the variation. May contain
|
||||||
|
Markdown syntax or can be ``null``.
|
||||||
|
└ position integer An integer, used for sorting
|
||||||
|
addons list of objects Definition of add-ons that can be chosen for this item
|
||||||
|
├ addon_category integer Internal ID of the item category the add-on can be
|
||||||
|
chosen from.
|
||||||
|
├ min_count integer The minimal number of add-ons that need to be chosen.
|
||||||
|
├ max_count integer The maxima number of add-ons that can be chosen.
|
||||||
|
└ position integer An integer, used for sorting
|
||||||
|
===================================== ========================== =======================================================
|
||||||
|
|
||||||
|
|
||||||
|
Endpoints
|
||||||
|
---------
|
||||||
|
|
||||||
|
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/items/
|
||||||
|
|
||||||
|
Returns a list of all items within a given event.
|
||||||
|
|
||||||
|
**Example request**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
GET /api/v1/organizers/bigevents/events/sampleconf/items/ HTTP/1.1
|
||||||
|
Host: pretix.eu
|
||||||
|
Accept: application/json, text/javascript
|
||||||
|
|
||||||
|
**Example response**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Vary: Accept
|
||||||
|
Content-Type: text/javascript
|
||||||
|
|
||||||
|
{
|
||||||
|
"count": 1,
|
||||||
|
"next": null,
|
||||||
|
"previous": null,
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": {"en": "Standard ticket"},
|
||||||
|
"default_price": "23.00",
|
||||||
|
"category": null,
|
||||||
|
"active": true,
|
||||||
|
"description": null,
|
||||||
|
"free_price": false,
|
||||||
|
"tax_rate": "0.00",
|
||||||
|
"admission": false,
|
||||||
|
"position": 0,
|
||||||
|
"picture": null,
|
||||||
|
"available_from": null,
|
||||||
|
"available_until": null,
|
||||||
|
"require_voucher": false,
|
||||||
|
"hide_without_voucher": false,
|
||||||
|
"allow_cancel": true,
|
||||||
|
"min_per_order": null,
|
||||||
|
"max_per_order": null,
|
||||||
|
"has_variations": false,
|
||||||
|
"variations": [
|
||||||
|
{
|
||||||
|
"value": {"en": "Student"},
|
||||||
|
"default_price": "10.00",
|
||||||
|
"price": "10.00",
|
||||||
|
"active": true,
|
||||||
|
"description": null,
|
||||||
|
"position": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": {"en": "Regular"},
|
||||||
|
"default_price": null,
|
||||||
|
"price": "23.00",
|
||||||
|
"active": true,
|
||||||
|
"description": null,
|
||||||
|
"position": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"addons": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
:query integer page: The page number in case of a multi-page result set, default is 1
|
||||||
|
:query boolean active: If set to ``true`` or ``false``, only items with this value for the field ``active`` will be
|
||||||
|
returned.
|
||||||
|
:query integer category: If set to the ID of a category, only items within that category will be returned.
|
||||||
|
:query boolean admission: If set to ``true`` or ``false``, only items with this value for the field ``admission``
|
||||||
|
will be returned.
|
||||||
|
:query string tax_rate: If set to a decimal value, only items with this tax rate will be returned.
|
||||||
|
:query boolean free_price: If set to ``true`` or ``false``, only items with this value for the field ``free_price``
|
||||||
|
will be returned.
|
||||||
|
:query string ordering: Manually set the ordering of results. Valid fields to be used are ``id`` and ``position``.
|
||||||
|
Default: ``position``
|
||||||
|
:param organizer: The ``slug`` field of the organizer to fetch
|
||||||
|
:param event: The ``slug`` field of the event to fetch
|
||||||
|
: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.
|
||||||
|
|
||||||
|
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/items/(id)/
|
||||||
|
|
||||||
|
Returns information on one item, identified by its ID.
|
||||||
|
|
||||||
|
**Example request**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
GET /api/v1/organizers/bigevents/events/sampleconf/items/1/ HTTP/1.1
|
||||||
|
Host: pretix.eu
|
||||||
|
Accept: application/json, text/javascript
|
||||||
|
|
||||||
|
**Example response**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Vary: Accept
|
||||||
|
Content-Type: text/javascript
|
||||||
|
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": {"en": "Standard ticket"},
|
||||||
|
"default_price": "23.00",
|
||||||
|
"category": null,
|
||||||
|
"active": true,
|
||||||
|
"description": null,
|
||||||
|
"free_price": false,
|
||||||
|
"tax_rate": "0.00",
|
||||||
|
"admission": false,
|
||||||
|
"position": 0,
|
||||||
|
"picture": null,
|
||||||
|
"available_from": null,
|
||||||
|
"available_until": null,
|
||||||
|
"require_voucher": false,
|
||||||
|
"hide_without_voucher": false,
|
||||||
|
"allow_cancel": true,
|
||||||
|
"min_per_order": null,
|
||||||
|
"max_per_order": null,
|
||||||
|
"has_variations": false,
|
||||||
|
"variations": [
|
||||||
|
{
|
||||||
|
"value": {"en": "Student"},
|
||||||
|
"default_price": "10.00",
|
||||||
|
"price": "10.00",
|
||||||
|
"active": true,
|
||||||
|
"description": null,
|
||||||
|
"position": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": {"en": "Regular"},
|
||||||
|
"default_price": null,
|
||||||
|
"price": "23.00",
|
||||||
|
"active": true,
|
||||||
|
"description": null,
|
||||||
|
"position": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"addons": []
|
||||||
|
}
|
||||||
|
|
||||||
|
:param organizer: The ``slug`` field of the organizer to fetch
|
||||||
|
:param event: The ``slug`` field of the event to fetch
|
||||||
|
:param id: The ``id`` field of the item to fetch
|
||||||
|
: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.
|
||||||
517
doc/api/resources/orders.rst
Normal file
517
doc/api/resources/orders.rst
Normal file
@@ -0,0 +1,517 @@
|
|||||||
|
Orders
|
||||||
|
======
|
||||||
|
|
||||||
|
Order resource
|
||||||
|
--------------
|
||||||
|
|
||||||
|
The order resource contains the following public fields:
|
||||||
|
|
||||||
|
.. rst-class:: rest-resource-table
|
||||||
|
|
||||||
|
===================================== ========================== =======================================================
|
||||||
|
Field Type Description
|
||||||
|
===================================== ========================== =======================================================
|
||||||
|
code string Order code
|
||||||
|
status string Order status, one of:
|
||||||
|
|
||||||
|
* ``n`` – pending
|
||||||
|
* ``p`` – paid
|
||||||
|
* ``e`` – expired
|
||||||
|
* ``c`` – canceled
|
||||||
|
* ``r`` – refunded
|
||||||
|
secret string The secret contained in the link sent to the customer
|
||||||
|
email string The customer email address
|
||||||
|
locale string The locale used for communication with this customer
|
||||||
|
datetime datetime Time of order creation
|
||||||
|
expires datetime The order will expire, if it is still pending by this time
|
||||||
|
payment_date date Date of payment receival
|
||||||
|
payment_provider string Payment provider used for this order
|
||||||
|
payment_fee money (string) Payment fee included in this order's total
|
||||||
|
payment_fee_tax_rate decimal (string) VAT rate applied to the payment fee
|
||||||
|
payment_fee_tax_value money (string) VAT value included in the payment fee
|
||||||
|
total money (string) Total value of this order
|
||||||
|
comment string Internal comment on this order
|
||||||
|
invoice_address object Invoice address information (can be ``null``)
|
||||||
|
├ last_modified datetime Last modification date of the address
|
||||||
|
├ company string Customer company name
|
||||||
|
├ name string Customer name
|
||||||
|
├ street string Customer street
|
||||||
|
├ zipcode string Customer ZIP code
|
||||||
|
├ city string Customer city
|
||||||
|
├ country string Customer country
|
||||||
|
└ vat_id string Customer VAT ID
|
||||||
|
position list of objects List of order positions (see below)
|
||||||
|
downloads list of objects List of ticket download options for order-wise ticket
|
||||||
|
downloading. This might be a multi-page PDF or a ZIP
|
||||||
|
file of tickets for outputs that do not support
|
||||||
|
multiple tickets natively. See also order position
|
||||||
|
download options.
|
||||||
|
├ output string Ticket output provider (e.g. ``pdf``, ``passbook``)
|
||||||
|
└ url string Download URL
|
||||||
|
===================================== ========================== =======================================================
|
||||||
|
|
||||||
|
Order position resource
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
.. rst-class:: rest-resource-table
|
||||||
|
|
||||||
|
===================================== ========================== =======================================================
|
||||||
|
Field Type Description
|
||||||
|
===================================== ========================== =======================================================
|
||||||
|
id integer Internal ID of the order positon
|
||||||
|
code string Order code of the order the position belongs to
|
||||||
|
positionid integer Number of the position within the order
|
||||||
|
item integer ID of the purchased item
|
||||||
|
variation integer ID of the purchased variation (or ``null``)
|
||||||
|
price money (string) Price of this position
|
||||||
|
attendee_name string Specified attendee name for this position (or ``null``)
|
||||||
|
attendee_email string Specified attendee email address for this position (or ``null``)
|
||||||
|
voucher integer Internal ID of the voucher used for this position (or ``null``)
|
||||||
|
tax_rate decimal (string) VAT rate applied for this position
|
||||||
|
tax_value money (string) VAT included in this position
|
||||||
|
secret string Secret code printed on the tickets for validation
|
||||||
|
addon_to integer Internal ID of the position this position is an add-on for (or ``null``)
|
||||||
|
checkins list of objects List of check-ins with this ticket
|
||||||
|
└ datetime datetime Time of check-in
|
||||||
|
downloads list of objects List of ticket download options
|
||||||
|
├ output string Ticket output provider (e.g. ``pdf``, ``passbook``)
|
||||||
|
└ url string Download URL
|
||||||
|
answers list of objects Answers to user-defined questions
|
||||||
|
├ question integer Internal ID of the answered question
|
||||||
|
├ answer string Text representation of the answer
|
||||||
|
└ options list of integers Internal IDs of selected option(s)s (only for choice types)
|
||||||
|
===================================== ========================== =======================================================
|
||||||
|
|
||||||
|
|
||||||
|
Order endpoints
|
||||||
|
---------------
|
||||||
|
|
||||||
|
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/orders/
|
||||||
|
|
||||||
|
Returns a list of all orders within a given event.
|
||||||
|
|
||||||
|
**Example request**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
GET /api/v1/organizers/bigevents/events/sampleconf/orders/ HTTP/1.1
|
||||||
|
Host: pretix.eu
|
||||||
|
Accept: application/json, text/javascript
|
||||||
|
|
||||||
|
**Example response**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Vary: Accept
|
||||||
|
Content-Type: text/javascript
|
||||||
|
|
||||||
|
{
|
||||||
|
"count": 1,
|
||||||
|
"next": null,
|
||||||
|
"previous": null,
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"code": "ABC12",
|
||||||
|
"status": "p",
|
||||||
|
"secret": "k24fiuwvu8kxz3y1",
|
||||||
|
"email": "tester@example.org",
|
||||||
|
"locale": "en",
|
||||||
|
"datetime": "2017-12-01T10:00:00Z",
|
||||||
|
"expires": "2017-12-10T10:00:00Z",
|
||||||
|
"payment_date": "2017-12-05",
|
||||||
|
"payment_provider": "banktransfer",
|
||||||
|
"payment_fee": "0.00",
|
||||||
|
"payment_fee_tax_rate": "0.00",
|
||||||
|
"payment_fee_tax_value": "0.00",
|
||||||
|
"total": "23.00",
|
||||||
|
"comment": "",
|
||||||
|
"invoice_address": {
|
||||||
|
"last_modified": "2017-12-01T10:00:00Z",
|
||||||
|
"company": "Sample company",
|
||||||
|
"name": "John Doe",
|
||||||
|
"street": "Test street 12",
|
||||||
|
"zipcode": "12345",
|
||||||
|
"city": "Testington",
|
||||||
|
"country": "Testikistan",
|
||||||
|
"vat_id": "EU123456789"
|
||||||
|
},
|
||||||
|
"positions": [
|
||||||
|
{
|
||||||
|
"id": 23442,
|
||||||
|
"order": "ABC12",
|
||||||
|
"positionid": 1,
|
||||||
|
"item": 1345,
|
||||||
|
"variation": null,
|
||||||
|
"price": "23.00",
|
||||||
|
"attendee_name": "Peter",
|
||||||
|
"attendee_email": null,
|
||||||
|
"voucher": null,
|
||||||
|
"tax_rate": "0.00",
|
||||||
|
"tax_value": "0.00",
|
||||||
|
"secret": "z3fsn8jyufm5kpk768q69gkbyr5f4h6w",
|
||||||
|
"addon_to": null,
|
||||||
|
"checkins": [
|
||||||
|
{
|
||||||
|
"datetime": "2017-12-25T12:45:23Z"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"answers": [
|
||||||
|
{
|
||||||
|
"question": 12,
|
||||||
|
"answer": "Foo",
|
||||||
|
"options": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"downloads": [
|
||||||
|
{
|
||||||
|
"output": "pdf",
|
||||||
|
"url": "https://pretix.eu/api/v1/organizers/bigevents/events/sampleconf/orderpositions/23442/download/pdf/"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"downloads": [
|
||||||
|
{
|
||||||
|
"output": "pdf",
|
||||||
|
"url": "https://pretix.eu/api/v1/organizers/bigevents/events/sampleconf/orders/ABC12/download/pdf/"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
:query integer page: The page number in case of a multi-page result set, default is 1
|
||||||
|
:query string ordering: Manually set the ordering of results. Valid fields to be used are ``datetime``, ``code`` and
|
||||||
|
``status``. Default: ``datetime``
|
||||||
|
:query string code: Only return orders that match the given order code
|
||||||
|
:query string status: Only return orders in the given order status (see above)
|
||||||
|
:query string email: Only return orders created with the given email address
|
||||||
|
:query string locale: Only return orders with the given customer locale
|
||||||
|
:param organizer: The ``slug`` field of the organizer to fetch
|
||||||
|
:param event: The ``slug`` field of the event to fetch
|
||||||
|
: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.
|
||||||
|
|
||||||
|
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/orders/(code)/
|
||||||
|
|
||||||
|
Returns information on one order, identified by its order code.
|
||||||
|
|
||||||
|
**Example request**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
GET /api/v1/organizers/bigevents/events/sampleconf/orders/ABC12/ HTTP/1.1
|
||||||
|
Host: pretix.eu
|
||||||
|
Accept: application/json, text/javascript
|
||||||
|
|
||||||
|
**Example response**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Vary: Accept
|
||||||
|
Content-Type: text/javascript
|
||||||
|
|
||||||
|
{
|
||||||
|
"code": "ABC12",
|
||||||
|
"status": "p",
|
||||||
|
"secret": "k24fiuwvu8kxz3y1",
|
||||||
|
"email": "tester@example.org",
|
||||||
|
"locale": "en",
|
||||||
|
"datetime": "2017-12-01T10:00:00Z",
|
||||||
|
"expires": "2017-12-10T10:00:00Z",
|
||||||
|
"payment_date": "2017-12-05",
|
||||||
|
"payment_provider": "banktransfer",
|
||||||
|
"payment_fee": "0.00",
|
||||||
|
"payment_fee_tax_rate": "0.00",
|
||||||
|
"payment_fee_tax_value": "0.00",
|
||||||
|
"total": "23.00",
|
||||||
|
"comment": "",
|
||||||
|
"invoice_address": {
|
||||||
|
"last_modified": "2017-12-01T10:00:00Z",
|
||||||
|
"company": "Sample company",
|
||||||
|
"name": "John Doe",
|
||||||
|
"street": "Test street 12",
|
||||||
|
"zipcode": "12345",
|
||||||
|
"city": "Testington",
|
||||||
|
"country": "Testikistan",
|
||||||
|
"vat_id": "EU123456789"
|
||||||
|
},
|
||||||
|
"positions": [
|
||||||
|
{
|
||||||
|
"id": 23442,
|
||||||
|
"order": "ABC12",
|
||||||
|
"positionid": 1,
|
||||||
|
"item": 1345,
|
||||||
|
"variation": null,
|
||||||
|
"price": "23.00",
|
||||||
|
"attendee_name": "Peter",
|
||||||
|
"attendee_email": null,
|
||||||
|
"voucher": null,
|
||||||
|
"tax_rate": "0.00",
|
||||||
|
"tax_value": "0.00",
|
||||||
|
"secret": "z3fsn8jyufm5kpk768q69gkbyr5f4h6w",
|
||||||
|
"addon_to": null,
|
||||||
|
"checkins": [
|
||||||
|
{
|
||||||
|
"datetime": "2017-12-25T12:45:23Z"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"answers": [
|
||||||
|
{
|
||||||
|
"question": 12,
|
||||||
|
"answer": "Foo",
|
||||||
|
"options": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"downloads": [
|
||||||
|
{
|
||||||
|
"output": "pdf",
|
||||||
|
"url": "https://pretix.eu/api/v1/organizers/bigevents/events/sampleconf/orderpositions/23442/download/pdf/"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"downloads": [
|
||||||
|
{
|
||||||
|
"output": "pdf",
|
||||||
|
"url": "https://pretix.eu/api/v1/organizers/bigevents/events/sampleconf/orders/ABC12/download/pdf/"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
:param organizer: The ``slug`` field of the organizer to fetch
|
||||||
|
:param event: The ``slug`` field of the event to fetch
|
||||||
|
:param code: The ``code`` field of the order to fetch
|
||||||
|
: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.
|
||||||
|
|
||||||
|
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/orders/(code)/download/(output)/
|
||||||
|
|
||||||
|
Download tickets for an order, identified by its order code. Depending on the chosen output, the response might
|
||||||
|
be a ZIP file, PDF file or something else. The order details response contains a list of output options for this
|
||||||
|
partictular order.
|
||||||
|
|
||||||
|
Tickets can be only downloaded if the order is paid and if ticket downloads are active. Note that in some cases the
|
||||||
|
ticket file might not yet have been created. In that case, you will receive a status code :http:statuscode:`409` and
|
||||||
|
you are expected to retry the request after a short period of waiting.
|
||||||
|
|
||||||
|
**Example request**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
GET /api/v1/organizers/bigevents/events/sampleconf/orders/ABC12/download/pdf/ 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/pdf
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
:param organizer: The ``slug`` field of the organizer to fetch
|
||||||
|
:param event: The ``slug`` field of the event to fetch
|
||||||
|
:param code: The ``code`` field of the order to fetch
|
||||||
|
:param output: The internal name of the output provider to use
|
||||||
|
: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
|
||||||
|
**or** downlodas are not available for this order at this time. The response content will
|
||||||
|
contain more details.
|
||||||
|
:statuscode 409: The file is not yet ready and will now be prepared. Retry the request after waiting vor a few
|
||||||
|
seconds.
|
||||||
|
|
||||||
|
|
||||||
|
Order position endpoints
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/orderpositions/
|
||||||
|
|
||||||
|
Returns a list of all order positions within a given event.
|
||||||
|
|
||||||
|
**Example request**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
GET /api/v1/organizers/bigevents/events/sampleconf/orderpositions/ HTTP/1.1
|
||||||
|
Host: pretix.eu
|
||||||
|
Accept: application/json, text/javascript
|
||||||
|
|
||||||
|
**Example response**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Vary: Accept
|
||||||
|
Content-Type: text/javascript
|
||||||
|
|
||||||
|
{
|
||||||
|
"count": 1,
|
||||||
|
"next": null,
|
||||||
|
"previous": null,
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"id": 23442,
|
||||||
|
"order": "ABC12",
|
||||||
|
"positionid": 1,
|
||||||
|
"item": 1345,
|
||||||
|
"variation": null,
|
||||||
|
"price": "23.00",
|
||||||
|
"attendee_name": "Peter",
|
||||||
|
"attendee_email": null,
|
||||||
|
"voucher": null,
|
||||||
|
"tax_rate": "0.00",
|
||||||
|
"tax_value": "0.00",
|
||||||
|
"secret": "z3fsn8jyufm5kpk768q69gkbyr5f4h6w",
|
||||||
|
"addon_to": null,
|
||||||
|
"checkins": [
|
||||||
|
{
|
||||||
|
"datetime": "2017-12-25T12:45:23Z"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"answers": [
|
||||||
|
{
|
||||||
|
"question": 12,
|
||||||
|
"answer": "Foo",
|
||||||
|
"options": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"downloads": [
|
||||||
|
{
|
||||||
|
"output": "pdf",
|
||||||
|
"url": "https://pretix.eu/api/v1/organizers/bigevents/events/sampleconf/orderpositions/23442/download/pdf/"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
:query integer page: The page number in case of a multi-page result set, default is 1
|
||||||
|
:query string ordering: Manually set the ordering of results. Valid fields to be used are ``order__code``,
|
||||||
|
``order__datetime``, ``positionid``, ``attendee_name``, and ``order__status``. Default:
|
||||||
|
``order__datetime,positionid``
|
||||||
|
:query string order: Only return positions of the order with the given order code
|
||||||
|
:query integer item: Only return positions with the purchased item matching the given ID.
|
||||||
|
:query integer variation: Only return positions with the purchased item variation matching the given ID.
|
||||||
|
:query string attendee_name: Only return positions with the given value in the attendee_name field. Also, add-on
|
||||||
|
products positions are shown if they refer to an attendee with the given name.
|
||||||
|
:query string secret: Only return positions with the given ticket secret.
|
||||||
|
:query string order__status: Only return positions with the given order status.
|
||||||
|
:query bollean has_checkin: If set to ``true`` or ``false``, only return positions that have or have not been
|
||||||
|
checked in already.
|
||||||
|
:query integer addon_to: Only return positions that are add-ons to the position with the given ID.
|
||||||
|
:param organizer: The ``slug`` field of the organizer to fetch
|
||||||
|
:param event: The ``slug`` field of the event to fetch
|
||||||
|
: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.
|
||||||
|
|
||||||
|
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/orderpositions/(id)/
|
||||||
|
|
||||||
|
Returns information on one order position, identified by its internal ID.
|
||||||
|
|
||||||
|
**Example request**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
GET /api/v1/organizers/bigevents/events/sampleconf/orderpositions/23442/ HTTP/1.1
|
||||||
|
Host: pretix.eu
|
||||||
|
Accept: application/json, text/javascript
|
||||||
|
|
||||||
|
**Example response**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Vary: Accept
|
||||||
|
Content-Type: text/javascript
|
||||||
|
|
||||||
|
{
|
||||||
|
"id": 23442,
|
||||||
|
"order": "ABC12",
|
||||||
|
"positionid": 1,
|
||||||
|
"item": 1345,
|
||||||
|
"variation": null,
|
||||||
|
"price": "23.00",
|
||||||
|
"attendee_name": "Peter",
|
||||||
|
"attendee_email": null,
|
||||||
|
"voucher": null,
|
||||||
|
"tax_rate": "0.00",
|
||||||
|
"tax_value": "0.00",
|
||||||
|
"secret": "z3fsn8jyufm5kpk768q69gkbyr5f4h6w",
|
||||||
|
"addon_to": null,
|
||||||
|
"checkins": [
|
||||||
|
{
|
||||||
|
"datetime": "2017-12-25T12:45:23Z"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"answers": [
|
||||||
|
{
|
||||||
|
"question": 12,
|
||||||
|
"answer": "Foo",
|
||||||
|
"options": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"downloads": [
|
||||||
|
{
|
||||||
|
"output": "pdf",
|
||||||
|
"url": "https://pretix.eu/api/v1/organizers/bigevents/events/sampleconf/orderpositions/23442/download/pdf/"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
:param organizer: The ``slug`` field of the organizer to fetch
|
||||||
|
:param event: The ``slug`` field of the event to fetch
|
||||||
|
:param id: The ``id`` field of the order position to fetch
|
||||||
|
: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.
|
||||||
|
|
||||||
|
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/orderpositions/(id)/download/(output)/
|
||||||
|
|
||||||
|
Download tickets for one order position, identified by its internal ID.
|
||||||
|
Depending on the chosen output, the response might be a ZIP file, PDF file or something else. The order details
|
||||||
|
response contains a list of output options for this partictular order position.
|
||||||
|
|
||||||
|
Tickets can be only downloaded if the order is paid and if ticket downloads are active. Also, depending on event
|
||||||
|
configuration downloads might be only unavailable for add-on products or non-admission products.
|
||||||
|
Note that in some cases the ticket file might not yet have been created. In that case, you will receive a status
|
||||||
|
code :http:statuscode:`409` and you are expected to retry the request after a short period of waiting.
|
||||||
|
|
||||||
|
**Example request**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
GET /api/v1/organizers/bigevents/events/sampleconf/orderpositions/23442/download/pdf/ 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/pdf
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
:param organizer: The ``slug`` field of the organizer to fetch
|
||||||
|
:param event: The ``slug`` field of the event to fetch
|
||||||
|
:param id: The ``id`` field of the order position to fetch
|
||||||
|
:param output: The internal name of the output provider to use
|
||||||
|
: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
|
||||||
|
**or** downlodas are not available for this order position at this time. The response content will
|
||||||
|
contain more details.
|
||||||
|
:statuscode 409: The file is not yet ready and will now be prepared. Retry the request after waiting vor a few
|
||||||
|
seconds.
|
||||||
90
doc/api/resources/organizers.rst
Normal file
90
doc/api/resources/organizers.rst
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
Organizers
|
||||||
|
==========
|
||||||
|
|
||||||
|
Resource description
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
An organizers is an entity running any number of events. In pretix, every event belongs to one
|
||||||
|
organizer and various settings, such as teams and permissions, are managed on organizer level.
|
||||||
|
|
||||||
|
The organizer resource contains the following public fields:
|
||||||
|
|
||||||
|
.. rst-class:: rest-resource-table
|
||||||
|
|
||||||
|
===================================== ========================== =======================================================
|
||||||
|
Field Type Description
|
||||||
|
===================================== ========================== =======================================================
|
||||||
|
name string The organizer's full name, i.e. the name of an
|
||||||
|
organization or company.
|
||||||
|
slug string A short form of the name, used e.g. in URLs.
|
||||||
|
===================================== ========================== =======================================================
|
||||||
|
|
||||||
|
|
||||||
|
Endpoints
|
||||||
|
---------
|
||||||
|
|
||||||
|
.. http:get:: /api/v1/organizers/
|
||||||
|
|
||||||
|
Returns a list of all organizers the authenticated user/token has access to.
|
||||||
|
|
||||||
|
**Example request**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
GET /api/v1/organizers/ HTTP/1.1
|
||||||
|
Host: pretix.eu
|
||||||
|
Accept: application/json, text/javascript
|
||||||
|
|
||||||
|
**Example response**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Vary: Accept
|
||||||
|
Content-Type: text/javascript
|
||||||
|
|
||||||
|
{
|
||||||
|
"count": 1,
|
||||||
|
"next": null,
|
||||||
|
"previous": null,
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"name": "Big Events LLC",
|
||||||
|
"slug": "Big Events",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
:query page: The page number in case of a multi-page result set, default is 1
|
||||||
|
:statuscode 200: no error
|
||||||
|
:statuscode 401: Authentication failure
|
||||||
|
|
||||||
|
.. http:get:: /api/v1/organizers/(organizer)/
|
||||||
|
|
||||||
|
Returns information on one organizer account, identified by its slug.
|
||||||
|
|
||||||
|
**Example request**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
GET /api/v1/organizers/bigevents/ HTTP/1.1
|
||||||
|
Host: pretix.eu
|
||||||
|
Accept: application/json, text/javascript
|
||||||
|
|
||||||
|
**Example response**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Vary: Accept
|
||||||
|
Content-Type: text/javascript
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Big Events LLC",
|
||||||
|
"slug": "Big Events",
|
||||||
|
}
|
||||||
|
|
||||||
|
:param organizer: The ``slug`` field of the organizer to fetch
|
||||||
|
:statuscode 200: no error
|
||||||
|
:statuscode 401: Authentication failure
|
||||||
|
:statuscode 403: The requested organizer does not exist **or** you have no permission to view it.
|
||||||
145
doc/api/resources/questions.rst
Normal file
145
doc/api/resources/questions.rst
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
Questions
|
||||||
|
=========
|
||||||
|
|
||||||
|
Resource description
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
Questions define additional fields that need to be filled out by customers during checkout.
|
||||||
|
The question resource contains the following public fields:
|
||||||
|
|
||||||
|
.. rst-class:: rest-resource-table
|
||||||
|
|
||||||
|
===================================== ========================== =======================================================
|
||||||
|
Field Type Description
|
||||||
|
===================================== ========================== =======================================================
|
||||||
|
id integer Internal ID of the question
|
||||||
|
question multi-lingual string The field label shown to the customer
|
||||||
|
type string The expected type of answer. Valid options:
|
||||||
|
|
||||||
|
* ``N`` – number
|
||||||
|
* ``S`` – one-line string
|
||||||
|
* ``T`` – multi-line string
|
||||||
|
* ``B`` – boolean
|
||||||
|
* ``C`` – choice from a list
|
||||||
|
* ``M`` – multiple choice from a list
|
||||||
|
required boolean If ``True``, the question needs to be filled out.
|
||||||
|
position integer An integer, used for sorting
|
||||||
|
items list of integers List of item IDs this question is assigned to.
|
||||||
|
options list of objects In case of question type ``C`` or ``M``, this lists the
|
||||||
|
available objects.
|
||||||
|
├ id integer Internal ID of the option
|
||||||
|
└ answer multi-lingual string The displayed value of this option
|
||||||
|
===================================== ========================== =======================================================
|
||||||
|
|
||||||
|
|
||||||
|
Endpoints
|
||||||
|
---------
|
||||||
|
|
||||||
|
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/questions/
|
||||||
|
|
||||||
|
Returns a list of all questions within a given event.
|
||||||
|
|
||||||
|
**Example request**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
GET /api/v1/organizers/bigevents/events/sampleconf/questions/ HTTP/1.1
|
||||||
|
Host: pretix.eu
|
||||||
|
Accept: application/json, text/javascript
|
||||||
|
|
||||||
|
**Example response**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Vary: Accept
|
||||||
|
Content-Type: text/javascript
|
||||||
|
|
||||||
|
{
|
||||||
|
"count": 1,
|
||||||
|
"next": null,
|
||||||
|
"previous": null,
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"question": {"en": "T-Shirt size"},
|
||||||
|
"type": "C",
|
||||||
|
"required": false,
|
||||||
|
"items": [1, 2],
|
||||||
|
"position": 1,
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"answer": {"en": "S"}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"answer": {"en": "M"}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"answer": {"en": "L"}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
:query integer page: The page number in case of a multi-page result set, default is 1
|
||||||
|
:query string ordering: Manually set the ordering of results. Valid fields to be used are ``id`` and ``position``.
|
||||||
|
Default: ``position``
|
||||||
|
:param organizer: The ``slug`` field of the organizer to fetch
|
||||||
|
:param event: The ``slug`` field of the event to fetch
|
||||||
|
: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.
|
||||||
|
|
||||||
|
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/questions/(id)/
|
||||||
|
|
||||||
|
Returns information on one question, identified by its ID.
|
||||||
|
|
||||||
|
**Example request**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
GET /api/v1/organizers/bigevents/events/sampleconf/questions/1/ HTTP/1.1
|
||||||
|
Host: pretix.eu
|
||||||
|
Accept: application/json, text/javascript
|
||||||
|
|
||||||
|
**Example response**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Vary: Accept
|
||||||
|
Content-Type: text/javascript
|
||||||
|
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"question": {"en": "T-Shirt size"},
|
||||||
|
"type": "C",
|
||||||
|
"required": false,
|
||||||
|
"items": [1, 2],
|
||||||
|
"position": 1,
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"answer": {"en": "S"}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"answer": {"en": "M"}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"answer": {"en": "L"}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
:param organizer: The ``slug`` field of the organizer to fetch
|
||||||
|
:param event: The ``slug`` field of the event to fetch
|
||||||
|
:param id: The ``id`` field of the question to fetch
|
||||||
|
: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.
|
||||||
143
doc/api/resources/quotas.rst
Normal file
143
doc/api/resources/quotas.rst
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
Quotas
|
||||||
|
======
|
||||||
|
|
||||||
|
Resource description
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
Questions define how many times an item can be sold.
|
||||||
|
The quota resource contains the following public fields:
|
||||||
|
|
||||||
|
.. rst-class:: rest-resource-table
|
||||||
|
|
||||||
|
===================================== ========================== =======================================================
|
||||||
|
Field Type Description
|
||||||
|
===================================== ========================== =======================================================
|
||||||
|
id integer Internal ID of the quota
|
||||||
|
name string The internal name of the quota
|
||||||
|
size integer The size of the quota or ``null`` for unlimited
|
||||||
|
items list of integers List of item IDs this quota acts on.
|
||||||
|
variations list of integers List of item variation IDs this quota acts on.
|
||||||
|
===================================== ========================== =======================================================
|
||||||
|
|
||||||
|
|
||||||
|
Endpoints
|
||||||
|
---------
|
||||||
|
|
||||||
|
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/quotas/
|
||||||
|
|
||||||
|
Returns a list of all quotas within a given event.
|
||||||
|
|
||||||
|
**Example request**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
GET /api/v1/organizers/bigevents/events/sampleconf/quotas/ HTTP/1.1
|
||||||
|
Host: pretix.eu
|
||||||
|
Accept: application/json, text/javascript
|
||||||
|
|
||||||
|
**Example response**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Vary: Accept
|
||||||
|
Content-Type: text/javascript
|
||||||
|
|
||||||
|
{
|
||||||
|
"count": 1,
|
||||||
|
"next": null,
|
||||||
|
"previous": null,
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "Ticket Quota",
|
||||||
|
"size": 200,
|
||||||
|
"items": [1, 2],
|
||||||
|
"variations": [1, 4, 5, 7]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
:query integer page: The page number in case of a multi-page result set, default is 1
|
||||||
|
:query string ordering: Manually set the ordering of results. Valid fields to be used are ``id`` and ``position``.
|
||||||
|
Default: ``position``
|
||||||
|
:param organizer: The ``slug`` field of the organizer to fetch
|
||||||
|
:param event: The ``slug`` field of the event to fetch
|
||||||
|
: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.
|
||||||
|
|
||||||
|
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/quotas/(id)/
|
||||||
|
|
||||||
|
Returns information on one quota, identified by its ID.
|
||||||
|
|
||||||
|
**Example request**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
GET /api/v1/organizers/bigevents/events/sampleconf/quotas/1/ HTTP/1.1
|
||||||
|
Host: pretix.eu
|
||||||
|
Accept: application/json, text/javascript
|
||||||
|
|
||||||
|
**Example response**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Vary: Accept
|
||||||
|
Content-Type: text/javascript
|
||||||
|
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "Ticket Quota",
|
||||||
|
"size": 200,
|
||||||
|
"items": [1, 2],
|
||||||
|
"variations": [1, 4, 5, 7]
|
||||||
|
}
|
||||||
|
|
||||||
|
:param organizer: The ``slug`` field of the organizer to fetch
|
||||||
|
:param event: The ``slug`` field of the event to fetch
|
||||||
|
:param id: The ``id`` field of the quota to fetch
|
||||||
|
: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.
|
||||||
|
|
||||||
|
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/quotas/(id)/availability/
|
||||||
|
|
||||||
|
Returns availability information on one quota, identified by its ID.
|
||||||
|
|
||||||
|
**Example request**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
GET /api/v1/organizers/bigevents/events/sampleconf/quotas/1/availability/ HTTP/1.1
|
||||||
|
Host: pretix.eu
|
||||||
|
Accept: application/json, text/javascript
|
||||||
|
|
||||||
|
**Example response**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Vary: Accept
|
||||||
|
Content-Type: text/javascript
|
||||||
|
|
||||||
|
{
|
||||||
|
"available": true,
|
||||||
|
"available_number": 419,
|
||||||
|
"total_size": 1000,
|
||||||
|
"pending_orders": 25,
|
||||||
|
"paid_orders": 423,
|
||||||
|
"cart_positions": 7,
|
||||||
|
"blocking_vouchers": 126,
|
||||||
|
"waiting_list": 0
|
||||||
|
}
|
||||||
|
|
||||||
|
Note that ``total_size`` and ``available_number`` are ``null`` in case of unlimited quotas.
|
||||||
|
|
||||||
|
:param organizer: The ``slug`` field of the organizer to fetch
|
||||||
|
:param event: The ``slug`` field of the event to fetch
|
||||||
|
:param id: The ``id`` field of the quota to fetch
|
||||||
|
: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.
|
||||||
160
doc/api/resources/vouchers.rst
Normal file
160
doc/api/resources/vouchers.rst
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
Vouchers
|
||||||
|
========
|
||||||
|
|
||||||
|
Resource description
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
The voucher resource contains the following public fields:
|
||||||
|
|
||||||
|
.. rst-class:: rest-resource-table
|
||||||
|
|
||||||
|
===================================== ========================== =======================================================
|
||||||
|
Field Type Description
|
||||||
|
===================================== ========================== =======================================================
|
||||||
|
id integer Internal ID of the voucher
|
||||||
|
code string The voucher code that is required to redeem the voucher
|
||||||
|
max_usages integer The maximum number of times this voucher can be
|
||||||
|
redeemed (default: 1).
|
||||||
|
redeemed integer The number of times this voucher already has been
|
||||||
|
redeemed.
|
||||||
|
valid_until datetime The voucher expiration date (or ``null``).
|
||||||
|
block_quota boolean If ``True``, quota is blocked for this voucher.
|
||||||
|
allow_ignore_quota boolean If ``True``, this voucher can be redeemed even if a
|
||||||
|
product is sold out and even if quota is not blocked
|
||||||
|
for this voucher.
|
||||||
|
price_mode string Determines how this voucher affects product prices.
|
||||||
|
Possible values:
|
||||||
|
|
||||||
|
* ``none`` – No effect on price
|
||||||
|
* ``set`` – The product price is set to the given ``value``
|
||||||
|
* ``subtract`` – The product price is determined by the original price *minus* the given ``value``
|
||||||
|
* ``percent`` – The product price is determined by the original price reduced by the percentage given in ``value``
|
||||||
|
value decimal (string) The value (see ``price_mode``)
|
||||||
|
item integer An ID of an item this voucher is restricted to (or ``null``)
|
||||||
|
variation integer An ID of a variation this voucher is restricted to (or ``null``)
|
||||||
|
quota integer An ID of a quota this voucher is restricted to (or
|
||||||
|
``null``). This is an exclusive alternative to
|
||||||
|
``item`` and ``variation``: A voucher can be
|
||||||
|
attached either to a specific product or to all
|
||||||
|
products within one quota or it can be available
|
||||||
|
for all items without restriction.
|
||||||
|
tag string A string that is used for grouping vouchers
|
||||||
|
comment string An internal comment on the voucher
|
||||||
|
===================================== ========================== =======================================================
|
||||||
|
|
||||||
|
|
||||||
|
Endpoints
|
||||||
|
---------
|
||||||
|
|
||||||
|
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/vouchers/
|
||||||
|
|
||||||
|
Returns a list of all vouchers within a given event.
|
||||||
|
|
||||||
|
**Example request**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
GET /api/v1/organizers/bigevents/events/sampleconf/vouchers/ HTTP/1.1
|
||||||
|
Host: pretix.eu
|
||||||
|
Accept: application/json, text/javascript
|
||||||
|
|
||||||
|
**Example response**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Vary: Accept
|
||||||
|
Content-Type: text/javascript
|
||||||
|
|
||||||
|
{
|
||||||
|
"count": 1,
|
||||||
|
"next": null,
|
||||||
|
"previous": null,
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"code": "43K6LKM37FBVR2YG",
|
||||||
|
"max_usages": 1,
|
||||||
|
"redeemed": 0,
|
||||||
|
"valid_until": null,
|
||||||
|
"block_quota": false,
|
||||||
|
"allow_ignore_quota": false,
|
||||||
|
"price_mode": "set",
|
||||||
|
"value": "12.00",
|
||||||
|
"item": 1,
|
||||||
|
"variation": null,
|
||||||
|
"quota": null,
|
||||||
|
"tag": "testvoucher",
|
||||||
|
"comment": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
:query integer page: The page number in case of a multi-page result set, default is 1
|
||||||
|
:query string code: Only show the voucher with the given voucher code.
|
||||||
|
:query integer max_usages: Only show vouchers with the given maximal number of usages.
|
||||||
|
:query integer redeemed: Only show vouchers with the given number of redemptions. Note that this doesn't tell you if
|
||||||
|
the voucher can still be redeemed, as this also depends on ``max_usages``. See the
|
||||||
|
``active`` query parameter as well.
|
||||||
|
:query boolean block_quota: If set to ``true`` or ``false``, only vouchers with this value in the field
|
||||||
|
``block_quota`` will be shown.
|
||||||
|
:query boolean allow_ignore_quota: If set to ``true`` or ``false``, only vouchers with this value in the field
|
||||||
|
``allow_ignore_quota`` will be shown.
|
||||||
|
:query string price_mode: If set, only vouchers with this value in the field ``price_mode`` will be shown (see
|
||||||
|
above).
|
||||||
|
:query string value: If set, only vouchers with this value in the field ``value`` will be shown.
|
||||||
|
:query integer item: If set, only vouchers attached to the item with the given ID will be shown.
|
||||||
|
:query integer variation: If set, only vouchers attached to the variation with the given ID will be shown.
|
||||||
|
:query integer quota: If set, only vouchers attached to the quota with the given ID will be shown.
|
||||||
|
:query string tag: If set, only vouchers with the given tag will be shown.
|
||||||
|
:query string ordering: Manually set the ordering of results. Valid fields to be used are ``id``, ``code``,
|
||||||
|
``max_usages``, ``valid_until``, and ``value``. Default: ``id``
|
||||||
|
:param organizer: The ``slug`` field of the organizer to fetch
|
||||||
|
:param event: The ``slug`` field of the event to fetch
|
||||||
|
: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.
|
||||||
|
|
||||||
|
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/vouchers/(id)/
|
||||||
|
|
||||||
|
Returns information on one voucher, identified by its internal ID.
|
||||||
|
|
||||||
|
**Example request**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
GET /api/v1/organizers/bigevents/events/sampleconf/vouchers/1/ HTTP/1.1
|
||||||
|
Host: pretix.eu
|
||||||
|
Accept: application/json, text/javascript
|
||||||
|
|
||||||
|
**Example response**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Vary: Accept
|
||||||
|
Content-Type: text/javascript
|
||||||
|
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"code": "43K6LKM37FBVR2YG",
|
||||||
|
"max_usages": 1,
|
||||||
|
"redeemed": 0,
|
||||||
|
"valid_until": null,
|
||||||
|
"block_quota": false,
|
||||||
|
"allow_ignore_quota": false,
|
||||||
|
"price_mode": "set",
|
||||||
|
"value": "12.00",
|
||||||
|
"item": 1,
|
||||||
|
"variation": null,
|
||||||
|
"quota": null,
|
||||||
|
"tag": "testvoucher",
|
||||||
|
"comment": ""
|
||||||
|
}
|
||||||
|
|
||||||
|
:param organizer: The ``slug`` field of the organizer to fetch
|
||||||
|
:param event: The ``slug`` field of the event to fetch
|
||||||
|
:param id: The ``id`` field of the voucher to fetch
|
||||||
|
: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.
|
||||||
119
doc/api/resources/waitinglist.rst
Normal file
119
doc/api/resources/waitinglist.rst
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
Waiting list entries
|
||||||
|
====================
|
||||||
|
|
||||||
|
Resource description
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
The waiting list entry resource contains the following public fields:
|
||||||
|
|
||||||
|
.. rst-class:: rest-resource-table
|
||||||
|
|
||||||
|
===================================== ========================== =======================================================
|
||||||
|
Field Type Description
|
||||||
|
===================================== ========================== =======================================================
|
||||||
|
id integer Internal ID of the waiting list entry
|
||||||
|
created datetime Creation date of the waiting list entry
|
||||||
|
email string Email address of the user on the waiting list
|
||||||
|
voucher integer Internal ID of the voucher sent to this user. If
|
||||||
|
this field is set, the user has been sent a voucher
|
||||||
|
and is no longer waiting. If it is ``null``, the
|
||||||
|
user is still waiting.
|
||||||
|
item integer An ID of an item the user is waiting to be available
|
||||||
|
again
|
||||||
|
variation integer An ID of a variation the user is waiting to be
|
||||||
|
available again (or ``null``)
|
||||||
|
locale string Locale of the waiting user
|
||||||
|
===================================== ========================== =======================================================
|
||||||
|
|
||||||
|
|
||||||
|
Endpoints
|
||||||
|
---------
|
||||||
|
|
||||||
|
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/waitinglistentries/
|
||||||
|
|
||||||
|
Returns a list of all waiting list entries within a given event.
|
||||||
|
|
||||||
|
**Example request**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
GET /api/v1/organizers/bigevents/events/sampleconf/waitinglistentries/ HTTP/1.1
|
||||||
|
Host: pretix.eu
|
||||||
|
Accept: application/json, text/javascript
|
||||||
|
|
||||||
|
**Example response**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Vary: Accept
|
||||||
|
Content-Type: text/javascript
|
||||||
|
|
||||||
|
{
|
||||||
|
"count": 1,
|
||||||
|
"next": null,
|
||||||
|
"previous": null,
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"created": "2017-12-01T10:00:00Z",
|
||||||
|
"email": "waiting@example.org",
|
||||||
|
"voucher": null,
|
||||||
|
"item": 2,
|
||||||
|
"variation": null,
|
||||||
|
"locale": "en"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
:query integer page: The page number in case of a multi-page result set, default is 1
|
||||||
|
:query string email: Only show waiting list entries created with the given email address.
|
||||||
|
:query string locale: Only show waiting list entries created with the given locale.
|
||||||
|
:query boolean has_voucher: If set to ``true`` or ``false``, only waiting list entries are returned that have or
|
||||||
|
have not been sent a voucher.
|
||||||
|
:query integer item: If set, only entries of users waiting for the item with the given ID will be shown.
|
||||||
|
:query integer variation: If set, only entries of users waiting for the variation with the given ID will be shown.
|
||||||
|
:query string ordering: Manually set the ordering of results. Valid fields to be used are ``id``, ``created``,
|
||||||
|
``email``, ``item``. Default: ``created``
|
||||||
|
:param organizer: The ``slug`` field of the organizer to fetch
|
||||||
|
:param event: The ``slug`` field of the event to fetch
|
||||||
|
: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.
|
||||||
|
|
||||||
|
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/waitinglistentries/(id)/
|
||||||
|
|
||||||
|
Returns information on one waiting list entry, identified by its internal ID.
|
||||||
|
|
||||||
|
**Example request**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
GET /api/v1/organizers/bigevents/events/sampleconf/waitinglistentries/1/ HTTP/1.1
|
||||||
|
Host: pretix.eu
|
||||||
|
Accept: application/json, text/javascript
|
||||||
|
|
||||||
|
**Example response**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Vary: Accept
|
||||||
|
Content-Type: text/javascript
|
||||||
|
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"created": "2017-12-01T10:00:00Z",
|
||||||
|
"email": "waiting@example.org",
|
||||||
|
"voucher": null,
|
||||||
|
"item": 2,
|
||||||
|
"variation": null,
|
||||||
|
"locale": "en"
|
||||||
|
}
|
||||||
|
|
||||||
|
:param organizer: The ``slug`` field of the organizer to fetch
|
||||||
|
:param event: The ``slug`` field of the event to fetch
|
||||||
|
:param id: The ``id`` field of the waiting list entry to fetch
|
||||||
|
: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.
|
||||||
35
doc/conf.py
35
doc/conf.py
@@ -19,10 +19,12 @@ import os
|
|||||||
# If extensions (or modules to document with autodoc) are in another directory,
|
# If extensions (or modules to document with autodoc) are in another directory,
|
||||||
# add these directories to sys.path here. If the directory is relative to the
|
# add these directories to sys.path here. If the directory is relative to the
|
||||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||||
|
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.settings")
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "pretix.testutils.settings")
|
||||||
django.setup()
|
django.setup()
|
||||||
|
|
||||||
# -- General configuration ------------------------------------------------
|
# -- General configuration ------------------------------------------------
|
||||||
@@ -51,11 +53,11 @@ source_suffix = '.rst'
|
|||||||
#source_encoding = 'utf-8-sig'
|
#source_encoding = 'utf-8-sig'
|
||||||
|
|
||||||
# The master toctree document.
|
# The master toctree document.
|
||||||
master_doc = 'index'
|
master_doc = 'contents'
|
||||||
|
|
||||||
# General information about the project.
|
# General information about the project.
|
||||||
project = 'pretix'
|
project = 'pretix'
|
||||||
copyright = '2014-2017, Raphael Michel'
|
copyright = '2014-{}, Raphael Michel'.format(date.today().year)
|
||||||
|
|
||||||
# The version info for the project you're documenting, acts as replacement for
|
# The version info for the project you're documenting, acts as replacement for
|
||||||
# |version| and |release|, also used in various other places throughout the
|
# |version| and |release|, also used in various other places throughout the
|
||||||
@@ -115,7 +117,9 @@ pygments_style = 'sphinx'
|
|||||||
# Theme options are theme-specific and customize the look and feel of a theme
|
# Theme options are theme-specific and customize the look and feel of a theme
|
||||||
# further. For a list of options available for each theme, see the
|
# further. For a list of options available for each theme, see the
|
||||||
# documentation.
|
# documentation.
|
||||||
#html_theme_options = {}
|
html_theme_options = {
|
||||||
|
'logo_only': True,
|
||||||
|
}
|
||||||
|
|
||||||
# Add any paths that contain custom themes here, relative to this directory.
|
# Add any paths that contain custom themes here, relative to this directory.
|
||||||
#html_theme_path = []
|
#html_theme_path = []
|
||||||
@@ -129,7 +133,7 @@ pygments_style = 'sphinx'
|
|||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top
|
# The name of an image file (relative to this directory) to place at the top
|
||||||
# of the sidebar.
|
# of the sidebar.
|
||||||
#html_logo = None
|
html_logo = 'images/logo-white.svg'
|
||||||
|
|
||||||
# The name of an image file (within the static path) to use as favicon of the
|
# The name of an image file (within the static path) to use as favicon of the
|
||||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||||
@@ -139,7 +143,11 @@ pygments_style = 'sphinx'
|
|||||||
# Add any paths that contain custom static files (such as style sheets) here,
|
# Add any paths that contain custom static files (such as style sheets) here,
|
||||||
# relative to this directory. They are copied after the builtin static files,
|
# relative to this directory. They are copied after the builtin static files,
|
||||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||||
html_static_path = ['_static']
|
html_static_path = [
|
||||||
|
'_static',
|
||||||
|
os.path.abspath('../src/pretix/static/fonts/'),
|
||||||
|
os.path.abspath('../src/pretix/static/fontawesome/fonts/'),
|
||||||
|
]
|
||||||
|
|
||||||
# Add any extra paths that contain custom files (such as robots.txt or
|
# Add any extra paths that contain custom files (such as robots.txt or
|
||||||
# .htaccess) here, relative to this directory. These files are copied
|
# .htaccess) here, relative to this directory. These files are copied
|
||||||
@@ -159,7 +167,9 @@ html_static_path = ['_static']
|
|||||||
|
|
||||||
# Additional templates that should be rendered to pages, maps page names to
|
# Additional templates that should be rendered to pages, maps page names to
|
||||||
# template names.
|
# template names.
|
||||||
#html_additional_pages = {}
|
html_additional_pages = {
|
||||||
|
'index': 'index.html'
|
||||||
|
}
|
||||||
|
|
||||||
# If false, no module index is generated.
|
# If false, no module index is generated.
|
||||||
html_domain_indices = False
|
html_domain_indices = False
|
||||||
@@ -171,7 +181,7 @@ html_use_index = False
|
|||||||
#html_split_index = False
|
#html_split_index = False
|
||||||
|
|
||||||
# If true, links to the reST sources are added to the pages.
|
# If true, links to the reST sources are added to the pages.
|
||||||
#html_show_sourcelink = True
|
html_show_sourcelink = True
|
||||||
|
|
||||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||||
#html_show_sphinx = True
|
#html_show_sphinx = True
|
||||||
@@ -190,11 +200,8 @@ html_use_index = False
|
|||||||
# Output file base name for HTML help builder.
|
# Output file base name for HTML help builder.
|
||||||
htmlhelp_basename = 'pretixdoc'
|
htmlhelp_basename = 'pretixdoc'
|
||||||
|
|
||||||
on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
|
html_theme = 'pretix_theme'
|
||||||
if not on_rtd: # only import and set the theme if we're building docs locally
|
html_theme_path = [os.path.abspath('_themes')]
|
||||||
import sphinx_rtd_theme
|
|
||||||
html_theme = 'sphinx_rtd_theme'
|
|
||||||
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
|
|
||||||
|
|
||||||
|
|
||||||
# -- Options for LaTeX output ---------------------------------------------
|
# -- Options for LaTeX output ---------------------------------------------
|
||||||
@@ -214,7 +221,7 @@ latex_elements = {
|
|||||||
# (source start file, target name, title,
|
# (source start file, target name, title,
|
||||||
# author, documentclass [howto, manual, or own class]).
|
# author, documentclass [howto, manual, or own class]).
|
||||||
latex_documents = [
|
latex_documents = [
|
||||||
('index', 'pretix.tex', 'pretix Documentation',
|
('contents', 'pretix.tex', 'pretix Documentation',
|
||||||
'Raphael Michel', 'manual'),
|
'Raphael Michel', 'manual'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
12
doc/contents.rst
Normal file
12
doc/contents.rst
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
Table of contents
|
||||||
|
=================
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 3
|
||||||
|
|
||||||
|
user/index
|
||||||
|
admin/index
|
||||||
|
api/index
|
||||||
|
development/index
|
||||||
|
plugins/index
|
||||||
|
|
||||||
@@ -96,3 +96,52 @@ correctly ensure that:
|
|||||||
* The ``request.event`` attribute contains the correct ``Event`` object
|
* The ``request.event`` attribute contains the correct ``Event`` object
|
||||||
* The ``request.organizer`` attribute contains the correct ``Organizer`` object
|
* The ``request.organizer`` attribute contains the correct ``Organizer`` object
|
||||||
* The locale is set correctly
|
* The locale is set correctly
|
||||||
|
|
||||||
|
REST API viewsets
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
Our REST API is built upon `Django REST Framework`_ (DRF). DRF has two important concepts that are different from
|
||||||
|
standard Django request handling: There are `ViewSets`_ to group related views in a single class and `Routers`_ to
|
||||||
|
automatically build URL configurations from them.
|
||||||
|
|
||||||
|
To integrate a custom viewset with pretix' REST API, you can just register with one of our routers within the
|
||||||
|
``urls.py`` module of your plugin::
|
||||||
|
|
||||||
|
|
||||||
|
from pretix.api.urls import event_router, router, orga_router
|
||||||
|
|
||||||
|
router.register('global_viewset', MyViewSet)
|
||||||
|
orga_router.register('orga_level_viewset', MyViewSet)
|
||||||
|
event_router.register('event_level_viewset', MyViewSet)
|
||||||
|
|
||||||
|
Routes registered with ``router`` are inserted into the global API space at ``/api/v1/``. Routes registered with
|
||||||
|
``orga_router`` will be included at ``/api/v1/organizers/(organizer)/`` and routes registered with ``event_router``
|
||||||
|
will be included at ``/api/v1/organizers/(organizer)/events/(event)/``.
|
||||||
|
|
||||||
|
In case of ``orga_router`` and ``event_router``, permission checking is done for you similarly as with custom views
|
||||||
|
in the control panel. However, you need to make sure on your own only to return the correct subset of data! ``request
|
||||||
|
.event`` and ``request.organizer`` are available as usual.
|
||||||
|
|
||||||
|
To require a special permission like ``can_view_orders``, you do not need to inherit from a special ViewSet base
|
||||||
|
class, you can just set the ``permission`` attribute on your viewset::
|
||||||
|
|
||||||
|
class MyViewSet(ModelViewSet):
|
||||||
|
permission = 'can_view_orders'
|
||||||
|
...
|
||||||
|
|
||||||
|
If you want to check the permission only for some methods of your viewset, you have to do it yourself. Note here that
|
||||||
|
API authentications can be done via user sessions or API tokens and you should therefore check something like the
|
||||||
|
following::
|
||||||
|
|
||||||
|
|
||||||
|
perm_holder = (request.auth if isinstance(request.auth, TeamAPIToken) else request.user)
|
||||||
|
if perm_holder.has_event_permission(request.event.organizer, request.event, 'can_view_orders'):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
.. warning:: It is important that you do this in the ``yourplugin.urls`` module, otherwise pretix will not find your
|
||||||
|
routes early enough during system startup.
|
||||||
|
|
||||||
|
.. _Django REST Framework: http://www.django-rest-framework.org/
|
||||||
|
.. _ViewSets: http://www.django-rest-framework.org/api-guide/viewsets/
|
||||||
|
.. _Routers: http://www.django-rest-framework.org/api-guide/routers/
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ Frontend
|
|||||||
--------
|
--------
|
||||||
|
|
||||||
.. automodule:: pretix.presale.signals
|
.. automodule:: pretix.presale.signals
|
||||||
:members: html_head, html_footer, footer_links, front_page_top, front_page_bottom, checkout_confirm_messages
|
:members: html_head, html_footer, footer_links, front_page_top, front_page_bottom, contact_form_fields, checkout_confirm_messages
|
||||||
|
|
||||||
|
|
||||||
.. automodule:: pretix.presale.signals
|
.. automodule:: pretix.presale.signals
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
Plugin hooks
|
Plugin development
|
||||||
============
|
==================
|
||||||
|
|
||||||
Contents:
|
Contents:
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
.. highlight:: python
|
.. highlight:: python
|
||||||
:linenothreshold: 5
|
:linenothreshold: 5
|
||||||
|
|
||||||
Plugin basics
|
.. _`pluginsetup`:
|
||||||
=============
|
|
||||||
|
Creating a plugin
|
||||||
|
=================
|
||||||
|
|
||||||
It is possible to extend pretix with custom Python code using the official plugin
|
It is possible to extend pretix with custom Python code using the official plugin
|
||||||
API. Every plugin has to be implemented as an independent Django 'app' living
|
API. Every plugin has to be implemented as an independent Django 'app' living
|
||||||
@@ -14,10 +16,15 @@ The communication between pretix and the plugins happens mostly using Django's
|
|||||||
``pretix.control`` and ``pretix.presale`` expose a number of signals which are documented
|
``pretix.control`` and ``pretix.presale`` expose a number of signals which are documented
|
||||||
on the next pages.
|
on the next pages.
|
||||||
|
|
||||||
.. _`pluginsetup`:
|
|
||||||
|
|
||||||
To create a new plugin, create a new python package which must be a valid `Django app`_
|
To create a new plugin, create a new python package which must be a valid `Django app`_
|
||||||
and must contain plugin metadata, as described below.
|
and must contain plugin metadata, as described below.
|
||||||
|
There is some boilerplate that you will need for every plugin to get started. To save your
|
||||||
|
time, we created a `cookiecutter`_ template that you can use like this::
|
||||||
|
|
||||||
|
$ pip install cookiecutter
|
||||||
|
$ cookiecutter https://github.com/pretix/pretix-plugin-cookiecutter
|
||||||
|
|
||||||
|
This will ask you some questions and then create a project folder for your plugin.
|
||||||
|
|
||||||
The following pages go into detail about the several types of plugins currently
|
The following pages go into detail about the several types of plugins currently
|
||||||
supported. While these instructions don't assume that you know a lot about pretix,
|
supported. While these instructions don't assume that you know a lot about pretix,
|
||||||
@@ -30,35 +37,29 @@ Plugin metadata
|
|||||||
The plugin metadata lives inside a ``PretixPluginMeta`` class inside your app's
|
The plugin metadata lives inside a ``PretixPluginMeta`` class inside your app's
|
||||||
configuration class. The metadata class must define the following attributes:
|
configuration class. The metadata class must define the following attributes:
|
||||||
|
|
||||||
``name`` (``str``):
|
.. rst-class:: rest-resource-table
|
||||||
The human-readable name of your plugin
|
|
||||||
|
|
||||||
``author`` (``str``):
|
================== ==================== ===========================================================
|
||||||
Your name
|
Attribute Type Description
|
||||||
|
================== ==================== ===========================================================
|
||||||
``version`` (``str``):
|
name string The human-readable name of your plugin
|
||||||
A human-readable version code of your plugin
|
author string Your name
|
||||||
|
version string A human-readable version code of your plugin
|
||||||
``description`` (``str``):
|
description string A more verbose description of what your plugin does.
|
||||||
A more verbose description of what your plugin does.
|
visible boolean (optional) ``True`` by default, can hide a plugin so it cannot be normally activated.
|
||||||
|
restricted boolean (optional) ``False`` by default, restricts a plugin such that it can only be enabled
|
||||||
``visible`` (``bool``):
|
for an event by system administrators / superusers.
|
||||||
``True`` by default, can hide a plugin so it cannot be normally activated.
|
================== ==================== ===========================================================
|
||||||
|
|
||||||
``restricted`` (``bool``):
|
|
||||||
``False`` by default, restricts a plugin such that it can only be enabled for an event
|
|
||||||
by system administrators / superusers.
|
|
||||||
|
|
||||||
A working example would be::
|
A working example would be::
|
||||||
|
|
||||||
# file: pretix/plugins/timerestriction/__init__.py
|
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class PaypalApp(AppConfig):
|
class PaypalApp(AppConfig):
|
||||||
name = 'pretix.plugins.paypal'
|
name = 'pretix_paypal'
|
||||||
verbose_name = _("Stripe")
|
verbose_name = _("PayPal")
|
||||||
|
|
||||||
class PretixPluginMeta:
|
class PretixPluginMeta:
|
||||||
name = _("PayPal")
|
name = _("PayPal")
|
||||||
@@ -69,8 +70,7 @@ A working example would be::
|
|||||||
description = _("This plugin allows you to receive payments via PayPal")
|
description = _("This plugin allows you to receive payments via PayPal")
|
||||||
|
|
||||||
|
|
||||||
default_app_config = 'pretix.plugins.paypal.PaypalApp'
|
default_app_config = 'pretix_paypal.PaypalApp'
|
||||||
|
|
||||||
|
|
||||||
The ``AppConfig`` class may implement a property ``compatiblity_errors``, that checks
|
The ``AppConfig`` class may implement a property ``compatiblity_errors``, that checks
|
||||||
whether the pretix installation meets all requirements of the plugin. If so,
|
whether the pretix installation meets all requirements of the plugin. If so,
|
||||||
@@ -87,11 +87,10 @@ make use of the `entry point`_ feature of setuptools. To register a plugin that
|
|||||||
in a separate python package, your ``setup.py`` should contain something like this::
|
in a separate python package, your ``setup.py`` should contain something like this::
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
…
|
args...,
|
||||||
|
|
||||||
entry_points="""
|
entry_points="""
|
||||||
[pretix.plugin]
|
[pretix.plugin]
|
||||||
sampleplugin=sampleplugin:PretixPluginMeta
|
pretix_paypal=pretix_paypal:PretixPluginMeta
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -109,7 +108,7 @@ pages. We suggest that you put your signal receivers into a ``signals`` submodul
|
|||||||
of your plugin. You should extend your ``AppConfig`` (see above) by the following
|
of your plugin. You should extend your ``AppConfig`` (see above) by the following
|
||||||
method to make your receivers available::
|
method to make your receivers available::
|
||||||
|
|
||||||
class TimeRestrictionApp(AppConfig):
|
class PaypalApp(AppConfig):
|
||||||
…
|
…
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
@@ -131,3 +130,4 @@ your Django app label.
|
|||||||
.. _signal dispatcher: https://docs.djangoproject.com/en/1.7/topics/signals/
|
.. _signal dispatcher: https://docs.djangoproject.com/en/1.7/topics/signals/
|
||||||
.. _namespace packages: http://legacy.python.org/dev/peps/pep-0420/
|
.. _namespace packages: http://legacy.python.org/dev/peps/pep-0420/
|
||||||
.. _entry point: https://pythonhosted.org/setuptools/setuptools.html#dynamic-discovery-of-services-and-plugins
|
.. _entry point: https://pythonhosted.org/setuptools/setuptools.html#dynamic-discovery-of-services-and-plugins
|
||||||
|
.. _cookiecutter: https://cookiecutter.readthedocs.io/en/latest/
|
||||||
|
|||||||
@@ -1,18 +1,16 @@
|
|||||||
Implementation concepts
|
Concepts and Terminology
|
||||||
=======================
|
========================
|
||||||
|
|
||||||
Basic terminology
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
The components
|
The components
|
||||||
^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^
|
||||||
|
|
||||||
The project pretix is split into several components. The main three of them are:
|
The project pretix is split into several components. The main components are:
|
||||||
|
|
||||||
**base**
|
**base**
|
||||||
This is the foundation below all other components. It is primarily
|
This is the foundation below all other components. It is primarily
|
||||||
responsible for the data structures and database communication. It also hosts
|
responsible for the data structures and database communication. It also hosts
|
||||||
several utilities which are used by multiple other components.
|
several utilities which are used by multiple other components and important parts of
|
||||||
|
the business logic.
|
||||||
|
|
||||||
**control**
|
**control**
|
||||||
This is the web-based backend software which allows organizers to
|
This is the web-based backend software which allows organizers to
|
||||||
@@ -20,7 +18,13 @@ The project pretix is split into several components. The main three of them are:
|
|||||||
|
|
||||||
**presale**
|
**presale**
|
||||||
This is the ticket-shop itself, containing all of the parts visible to the
|
This is the ticket-shop itself, containing all of the parts visible to the
|
||||||
end user.
|
end user. Also called "frontend" in parts of this documentation.
|
||||||
|
|
||||||
|
**api**
|
||||||
|
A RESTful API exposed to integrate with third-party software.
|
||||||
|
|
||||||
|
**plugins**
|
||||||
|
A set of pretix plugins that ship bundled with pretix.
|
||||||
|
|
||||||
Users and events
|
Users and events
|
||||||
^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^
|
||||||
@@ -61,6 +65,7 @@ limit:
|
|||||||
* The number of orders placed for an item that are either already paid or within their granted payment period
|
* The number of orders placed for an item that are either already paid or within their granted payment period
|
||||||
* The number of non-expired items currently in the shopping cart of users
|
* The number of non-expired items currently in the shopping cart of users
|
||||||
* The number of vouchers defined as "quota blocking" (see blow)
|
* The number of vouchers defined as "quota blocking" (see blow)
|
||||||
|
* The number of people on the waiting list
|
||||||
|
|
||||||
The quota system tries very hard to be as friendly as possible to your event attendees while still making sure
|
The quota system tries very hard to be as friendly as possible to your event attendees while still making sure
|
||||||
your limit is never exceeded. For example, when the payment period of an order expires without the order being
|
your limit is never exceeded. For example, when the payment period of an order expires without the order being
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ constructive and friendly feedback on your changes.
|
|||||||
First of all, you'll need pretix running locally on your machine. Head over to :ref:`devsetup` to learn how to do this.
|
First of all, you'll need pretix running locally on your machine. Head over to :ref:`devsetup` to learn how to do this.
|
||||||
If you run into any problems on your way, please do not hesitate to ask us anytime!
|
If you run into any problems on your way, please do not hesitate to ask us anytime!
|
||||||
|
|
||||||
Please note that we have a :ref:`coc` in place that applies to all communication around the project.
|
Please note that we bound ourselves to a :ref:`coc` that applies to all communication around the project. You can be
|
||||||
|
assured that we will not tolerate any form of harassment.
|
||||||
|
|
||||||
Sending a patch
|
Sending a patch
|
||||||
---------------
|
---------------
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
Contribution guide
|
Contributing to pretix
|
||||||
==================
|
======================
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|||||||
@@ -1,39 +1,33 @@
|
|||||||
Coding style
|
Coding style and quality
|
||||||
============
|
========================
|
||||||
|
|
||||||
Python code
|
* Basically, we want all python code to follow the `PEP 8`_ standard. There are a few exceptions where
|
||||||
-----------
|
we see things differently or just aren't that strict. The ``setup.cfg`` file in the project's source
|
||||||
|
folder contains definitions that allow `flake8`_ to check for violations automatically. See :ref:`checksandtests`
|
||||||
|
for more information. Use four spaces for indentation.
|
||||||
|
|
||||||
* Basically: Follow `PEP 8`_.
|
* We sort our imports by a certain schema, but you don't have to do this by hand. Again, ``setup.cfg`` contains
|
||||||
|
some definitions that allow the command ``isort -rc <directory>`` to automatically sort the imports in your source
|
||||||
|
files.
|
||||||
|
|
||||||
Use `flake8`_ to check for conformance problems. The project includes a setup.cfg file
|
* For templates and models, please take a look at the `Django Coding Style`_. We like Django's `class-based views`_ and
|
||||||
with a default configuration for flake8 that excludes migrations and other non-relevant
|
kindly ask you to use them where appropriate.
|
||||||
code parts. It also silences a few checks, e.g. ``N802`` (function names should be lowercase)
|
|
||||||
and increases the maximum line length to more than 79 characters. **However** you should
|
|
||||||
still name all your functions lowercase [#f1]_ and keep your lines short when possible.
|
|
||||||
|
|
||||||
* Our build server will reject all code violating other flake8 checks than the following:
|
* Please remember to always mark all strings ever displayed to any user for `translation`_.
|
||||||
|
|
||||||
* E123: closing bracket does not match indentation of opening bracket’s line
|
* We expect all new code to come with proper tests. When writing new tests, please write them using `pytest-style`_
|
||||||
* F403: ``from module import *`` used; unable to detect undefined names
|
test functions and raw ``assert`` statements. Use `fixtures`_ to prevent repetitive code. Some old parts of pretix'
|
||||||
* F401: module imported but unused
|
test suite are in the style of Python's unit test module. If you extend those files, you might continue in this style,
|
||||||
* N802: function names should be lowercase
|
but please use pytest style for any new test files.
|
||||||
|
|
||||||
So please make sure that you *always* follow all other rules and break these rules *only when
|
* Please keep the first line of your commit messages short. When referencing an issue, please phrase it like
|
||||||
it makes sense*.
|
``Fix #123 -- Problems with order creation`` or ``Refs #123 -- Fix this part of that bug``.
|
||||||
|
|
||||||
* Use ``isort -rc pretix`` in the source directory to order your imports.
|
|
||||||
|
|
||||||
* Indent your code with four spaces.
|
|
||||||
|
|
||||||
* For templates and models, follow the `Django Coding Style`_.
|
|
||||||
|
|
||||||
* Use Django's class-based views
|
|
||||||
|
|
||||||
* Always mark all strings ever displayed to any user for translation.
|
|
||||||
|
|
||||||
|
|
||||||
.. _PEP 8: http://legacy.python.org/dev/peps/pep-0008/
|
.. _PEP 8: http://legacy.python.org/dev/peps/pep-0008/
|
||||||
.. _flake8: https://pypi.python.org/pypi/flake8
|
.. _flake8: https://pypi.python.org/pypi/flake8
|
||||||
.. _Django Coding Style: https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/coding-style/
|
.. _Django Coding Style: https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/coding-style/
|
||||||
.. [#f1] But Python's very own ``unittest`` module forces us to use ``setUp`` as a method name...
|
.. _translation: https://docs.djangoproject.com/en/1.11/topics/i18n/translation/
|
||||||
|
.. _class-based views: https://docs.djangoproject.com/en/1.11/topics/class-based-views/
|
||||||
|
.. _pytest-style: https://docs.pytest.org/en/latest/assert.html
|
||||||
|
.. _fixtures: https://docs.pytest.org/en/latest/fixture.html
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
Developer documentation
|
Developer documentation
|
||||||
=======================
|
=======================
|
||||||
|
|
||||||
Contents:
|
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
concepts
|
concepts
|
||||||
setup
|
setup
|
||||||
structure
|
|
||||||
contribution/index
|
contribution/index
|
||||||
implementation/index
|
implementation/index
|
||||||
api/index
|
api/index
|
||||||
|
structure
|
||||||
|
|
||||||
.. TODO::
|
.. TODO::
|
||||||
Document settings objects, ItemVariation objects, form fields.
|
Document settings objects, ItemVariation objects, form fields.
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
.. _`devsetup`:
|
.. _`devsetup`:
|
||||||
|
|
||||||
The development setup
|
Development setup
|
||||||
=====================
|
=================
|
||||||
|
|
||||||
|
This tutorial helps you to get started hacking with pretix on your own computer. You need this to
|
||||||
|
be able to contribute to pretix, but it might also be helpful if you want to write your own plugins.
|
||||||
|
If you want to install pretix on a server for actual usage, go to the :ref:`admindocs` instead.
|
||||||
|
|
||||||
Obtain a copy of the source code
|
Obtain a copy of the source code
|
||||||
--------------------------------
|
--------------------------------
|
||||||
@@ -12,6 +16,8 @@ You can just clone our git repository::
|
|||||||
|
|
||||||
External Dependencies
|
External Dependencies
|
||||||
---------------------
|
---------------------
|
||||||
|
Your should install the following on your system:
|
||||||
|
|
||||||
* Python 3.4 or newer
|
* Python 3.4 or newer
|
||||||
* ``pip`` for Python 3 (Debian package: ``python3-pip``)
|
* ``pip`` for Python 3 (Debian package: ``python3-pip``)
|
||||||
* ``pyvenv`` for Python 3 (Debian package: ``python3-venv``)
|
* ``pyvenv`` for Python 3 (Debian package: ``python3-venv``)
|
||||||
@@ -40,7 +46,7 @@ automatically). If you are working on Ubuntu or Debian, we strongly recommend up
|
|||||||
your pip and setuptools installation inside the virtual environment, otherwise some of
|
your pip and setuptools installation inside the virtual environment, otherwise some of
|
||||||
the dependencies might fail::
|
the dependencies might fail::
|
||||||
|
|
||||||
pip3 install -U pip setuptools==28.6.1
|
pip3 install -U pip setuptools
|
||||||
|
|
||||||
Working with the code
|
Working with the code
|
||||||
---------------------
|
---------------------
|
||||||
@@ -49,7 +55,7 @@ The first thing you need are all the main application's dependencies::
|
|||||||
cd src/
|
cd src/
|
||||||
pip3 install -r requirements.txt -r requirements/dev.txt
|
pip3 install -r requirements.txt -r requirements/dev.txt
|
||||||
|
|
||||||
If you are working with Python 3.4, you will also need (you can skip this for Python 3.5)::
|
If you are working with Python 3.4, you will also need (you can skip this for Python 3.5+)::
|
||||||
|
|
||||||
pip3 install -r requirements/py34.txt
|
pip3 install -r requirements/py34.txt
|
||||||
|
|
||||||
@@ -81,7 +87,7 @@ and head to http://localhost:8000/
|
|||||||
|
|
||||||
As we did not implement an overall front page yet, you need to go directly to
|
As we did not implement an overall front page yet, you need to go directly to
|
||||||
http://localhost:8000/control/ for the admin view or, if you imported the test
|
http://localhost:8000/control/ for the admin view or, if you imported the test
|
||||||
data as suggested above, to the event page at http://localhost:8000/bigevents/2017/
|
data as suggested above, to the event page at http://localhost:8000/bigevents/2018/
|
||||||
|
|
||||||
.. note:: If you want the development server to listen on a different interface or
|
.. note:: If you want the development server to listen on a different interface or
|
||||||
port (for example because you develop on `pretixdroid`_), you can check
|
port (for example because you develop on `pretixdroid`_), you can check
|
||||||
@@ -91,11 +97,18 @@ data as suggested above, to the event page at http://localhost:8000/bigevents/20
|
|||||||
|
|
||||||
Code checks and unit tests
|
Code checks and unit tests
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
Before you check in your code into git, always run the static checkers and unit tests::
|
Before you check in your code into git, always run static checkers and linters. If any of these commands fail,
|
||||||
|
your pull request will not be merged into pretix. If you have trouble figuring out *why* they fail, create your
|
||||||
|
pull request nevertheless and ask us for help, we are happy to assist you.
|
||||||
|
|
||||||
|
Execute the following commands to check for code style errors::
|
||||||
|
|
||||||
flake8 .
|
flake8 .
|
||||||
isort -c -rc .
|
isort -c -rc .
|
||||||
python manage.py check
|
python manage.py check
|
||||||
|
|
||||||
|
Execute the following command to run pretix' test suite (might take a coumple of minutes)::
|
||||||
|
|
||||||
py.test
|
py.test
|
||||||
|
|
||||||
.. note:: If you have multiple CPU cores and want to speed up the test suite, you can install the python
|
.. note:: If you have multiple CPU cores and want to speed up the test suite, you can install the python
|
||||||
@@ -107,9 +120,10 @@ for example::
|
|||||||
|
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
cd $GIT_DIR/../src
|
cd $GIT_DIR/../src
|
||||||
source ../env/bin/activate
|
flake8 . || exit 1
|
||||||
flake8 --ignore=E123,E128,F403,F401,N802,W503 .
|
isort -q -rc -c . || exit 1
|
||||||
|
|
||||||
|
This keeps you from accidentally creating commits violating the sdtyle guide.
|
||||||
|
|
||||||
Working with mails
|
Working with mails
|
||||||
^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^
|
||||||
@@ -151,8 +165,14 @@ To build the documentation, run the following command from the ``doc/`` director
|
|||||||
|
|
||||||
make html
|
make html
|
||||||
|
|
||||||
You will now find the generated documentation in the ``doc/_build/html/`` subdirectory.
|
You will now find the generated documentation in the ``doc/_build/html/`` subdirectory. If you work
|
||||||
|
with the documentation a lot, you might find it useful to use sphinx-autobuild::
|
||||||
|
|
||||||
|
pip3 install sphinx-autobuild
|
||||||
|
sphinx-autobuild . _build/html -p 8081
|
||||||
|
|
||||||
|
Then, go to http://localhost:8081 for a version of the documentation that automatically re-builds
|
||||||
|
whenever you change a source file.
|
||||||
|
|
||||||
.. _Django's documentation: https://docs.djangoproject.com/en/1.11/ref/django-admin/#runserver
|
.. _Django's documentation: https://docs.djangoproject.com/en/1.11/ref/django-admin/#runserver
|
||||||
.. _pretixdroid: https://github.com/pretix/pretixdroid
|
.. _pretixdroid: https://github.com/pretix/pretixdroid
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
Project structure
|
Directory structure
|
||||||
=================
|
===================
|
||||||
|
|
||||||
Python source code
|
|
||||||
------------------
|
|
||||||
|
|
||||||
All the source code lives in ``src/``, which has several subdirectories.
|
All the source code lives in ``src/``, which has several subdirectories.
|
||||||
|
|
||||||
pretix/
|
pretix/
|
||||||
This directory contains nearly all source code.
|
This directory contains nearly all source code that belongs to pretix.
|
||||||
|
|
||||||
base/
|
base/
|
||||||
This is the Django app containing all the models and methods which are
|
This is the Django app containing all the models and methods which are
|
||||||
@@ -19,48 +16,31 @@ pretix/
|
|||||||
presale/
|
presale/
|
||||||
This is the Django app containing the front end for users buying tickets.
|
This is the Django app containing the front end for users buying tickets.
|
||||||
|
|
||||||
|
api/
|
||||||
|
This is the Django app containing all views and serializers for pretix'
|
||||||
|
:ref:`rest-api`.
|
||||||
|
|
||||||
helpers/
|
helpers/
|
||||||
Helpers contain a very few modules providing workarounds for low-level flaws in
|
Helpers contain a very few modules providing workarounds for low-level flaws in
|
||||||
Django or installed 3rd-party packages.
|
Django or installed 3rd-party packages.
|
||||||
|
|
||||||
|
locale/
|
||||||
|
Contains translation file for pretix
|
||||||
|
|
||||||
multidomain/
|
multidomain/
|
||||||
Additional code implementing our customized :ref:`URL handling <urlconf>`.
|
Additional code implementing our customized :ref:`URL handling <urlconf>`.
|
||||||
|
|
||||||
static/
|
static/
|
||||||
Contains all static files (CSS, JavaScript, images)
|
Contains all static files (CSS/SASS, JavaScript, images) of pretix' core
|
||||||
|
We use libsass as a preprocessor for CSS. Our own sass code is built in the same
|
||||||
|
step as Bootstrap and FontAwesome, so their mixins etc. are fully available.
|
||||||
|
|
||||||
static/
|
testutils/
|
||||||
Contains some pretix plugins that ship with pretix itself
|
Contains helper methods that are useful to write the test suite for pretix or test
|
||||||
|
suites for pretix plugins.
|
||||||
|
|
||||||
tests/
|
tests/
|
||||||
This is the root directory for all test codes. It includes subdirectories ``base``,
|
This is the root directory for all test codes. It includes subdirectories ``api``, ``base``,
|
||||||
``control``, ``presale``, ``helpers`` and ``plugins`` to mirror the structure of the
|
``control``, ``presale``, ``helpers`, ``multidomain`` and ``plugins`` to mirror the structure
|
||||||
``pretix`` source code as well as ``testdummy``, which is a pretix plugin used during
|
of the pretix source code as well as ``testdummy``, which is a pretix plugin used during
|
||||||
testing.
|
testing.
|
||||||
|
|
||||||
Language files
|
|
||||||
--------------
|
|
||||||
|
|
||||||
The language files live in ``pretix/locale/*/LC_MESSAGES/``.
|
|
||||||
|
|
||||||
Static files
|
|
||||||
------------
|
|
||||||
|
|
||||||
Sass source code
|
|
||||||
^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
We use libsass as a preprocessor for CSS. Our own sass code is built in the same
|
|
||||||
step as Bootstrap and FontAwesome, so their mixins etc. are fully available.
|
|
||||||
|
|
||||||
pretix.control
|
|
||||||
pretixcontrol has two main SCSS files, ``pretix/static/pretixcontrol/scss/main.scss`` and
|
|
||||||
``pretix/static/pretixcontrol/scss/auth.scss``, importing everything else.
|
|
||||||
|
|
||||||
pretix.presale
|
|
||||||
pretixpresale has one main SCSS files, ``pretix/pretixpresale/scss/main.scss``,
|
|
||||||
importing everything else.
|
|
||||||
|
|
||||||
3rd-party assets
|
|
||||||
^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
Most client-side 3rd-party assets are vendored in various subdirectories of ``pretix/static``.
|
|
||||||
|
|||||||
69
doc/images/logo-white.svg
Normal file
69
doc/images/logo-white.svg
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="254.15625"
|
||||||
|
height="109.59375"
|
||||||
|
viewBox="0 0 254.15625 109.59375"
|
||||||
|
version="1.1"
|
||||||
|
id="svg5"
|
||||||
|
sodipodi:docname="logo-white.svg"
|
||||||
|
inkscape:version="0.92.1 r"><metadata
|
||||||
|
id="metadata9">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<sodipodi:namedview
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1"
|
||||||
|
objecttolerance="10"
|
||||||
|
gridtolerance="10"
|
||||||
|
guidetolerance="10"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:window-width="1364"
|
||||||
|
inkscape:window-height="676"
|
||||||
|
id="namedview7"
|
||||||
|
showgrid="false"
|
||||||
|
fit-margin-top="0"
|
||||||
|
fit-margin-left="0"
|
||||||
|
fit-margin-right="0"
|
||||||
|
fit-margin-bottom="0"
|
||||||
|
inkscape:zoom="1"
|
||||||
|
inkscape:cx="56.462442"
|
||||||
|
inkscape:cy="54.796875"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="72"
|
||||||
|
inkscape:window-maximized="0"
|
||||||
|
inkscape:current-layer="svg5" />
|
||||||
|
|
||||||
|
id="svg2"
|
||||||
|
version="1.1">
|
||||||
|
<defs
|
||||||
|
id="defs4" />
|
||||||
|
<g
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(-277.78125,-568.75)">
|
||||||
|
<path
|
||||||
|
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;marker:none;enable-background:accumulate"
|
||||||
|
d="m 20,20 v 34.09375 c 11.43679,0 20.71875,9.28196 20.71875,20.71875 C 40.71875,86.24928 31.43679,95.5 20,95.5 v 34.09375 h 146.6875 v -9.5 h 3 v 9.5 H 274.15625 V 95.5 c -0.0105,2e-5 -0.0208,0 -0.0312,0 -11.43678,0 -20.71875,-9.25072 -20.71875,-20.6875 0,-11.43679 9.28197,-20.71875 20.71875,-20.71875 0.0105,0 0.0208,-2e-5 0.0312,0 V 20 H 169.6875 v 9.09375 h -3 V 20 Z m 146.6875,16.09375 h 3 v 14 h -3 z m 41.44141,12.833984 c 2.79067,0 5.02343,1.92774 5.02343,4.3125 0,2.38476 -2.23276,4.363282 -5.02343,4.363282 -2.73994,0 -4.97266,-1.978522 -4.97266,-4.363282 0,-2.38476 2.23272,-4.3125 4.97266,-4.3125 z m -13.22852,4.210938 v 8.017578 h 3.95899 v 6.291016 h -3.95899 v 12.279296 c 0,2.02959 0.71015,2.791016 2.13086,2.791016 0.71035,0 1.06703,-0.10181 1.82813,-0.40625 v 5.935547 c -0.71036,0.40591 -2.38661,0.964844 -4.61915,0.964844 -6.13949,0 -8.98046,-3.753876 -8.98046,-8.472657 V 67.447266 h -2.8418 V 61.15625 h 2.8418 V 55.574219 Z M 166.6875,57.09375 h 3 v 14 h -3 z m -74.568359,3.554688 c 8.473509,0 14.207029,4.515688 14.207029,14.105468 0,8.62573 -5.02336,14.105469 -12.07617,14.105469 -1.72514,0 -3.147072,-0.20329 -3.857422,-0.40625 V 99.414062 H 80.751953 V 62.728516 c 2.58772,-1.21775 6.090268,-2.080081 11.367188,-2.080078 z m 49.863279,0 c 8.57499,0 12.63436,5.935363 12.12696,15.220703 l -15.93165,2.234375 c 0.60888,2.94289 2.18061,4.414062 5.68165,4.414062 3.24732,0 5.78445,-0.711556 7.30664,-1.472656 l 2.13086,5.886719 c -2.38476,1.16701 -5.58034,2.080078 -10.6543,2.080078 -8.93017,0 -13.64844,-6.037993 -13.64844,-14.257813 0,-8.21981 4.41329,-14.105468 12.98828,-14.105468 z m -17.92187,0.0059 c 0.8928,0.01358 1.82795,0.04496 2.80468,0.0957 l -1.67578,6.697266 c -1.77589,-0.86257 -3.50104,-0.913692 -4.76953,-0.457032 v 21.513672 h -9.64062 v -25.77539 c 2.79702,-1.376314 7.03166,-2.16926 13.28125,-2.074219 z m 79.24804,0.501953 h 9.64063 v 27.347656 h -9.64063 z m 13.23438,0 h 10.04687 l 3.29883,6.849609 h 0.10156 l 3.60157,-6.849609 h 8.98047 l -7.96485,12.632812 8.72656,14.714844 H 232.67969 L 229.17773,80.9434 h -0.10156 l -3.65234,7.560547 h -9.74219 l 8.57422,-14.105468 z m -74.9668,5.023438 c -2.84142,0 -4.41381,2.585948 -4.10937,7.355468 l 7.76367,-1.166015 c 0,-4.16064 -1.2188,-6.189454 -3.6543,-6.189453 z m -49.507811,0.09961 c -0.71035,0 -1.219131,0.101686 -1.675781,0.253906 v 16.439453 c 0.35517,0.15221 0.863828,0.253906 1.523438,0.253906 3.4503,0 4.871093,-2.840514 4.871093,-8.421874 0,-5.733571 -1.21772,-8.525391 -4.71875,-8.525391 z M 166.6875,78.09375 h 3 v 14 h -3 z m 0,21 h 3 v 14 h -3 z"
|
||||||
|
transform="translate(257.78125,548.75)"
|
||||||
|
id="rect3888"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.4 KiB |
@@ -1,13 +0,0 @@
|
|||||||
Welcome to pretix's documentation!
|
|
||||||
==================================
|
|
||||||
|
|
||||||
Contents:
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 2
|
|
||||||
|
|
||||||
user/index
|
|
||||||
admin/index
|
|
||||||
development/index
|
|
||||||
plugins/index
|
|
||||||
|
|
||||||
201
doc/plugins/banktransfer.rst
Normal file
201
doc/plugins/banktransfer.rst
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
Bank transfer HTTP API
|
||||||
|
======================
|
||||||
|
|
||||||
|
The banktransfer plugin provides a HTTP API that `pretix-banktool`_ uses to send bank
|
||||||
|
transactions to the pretix server. This API is integrated with the regular :ref:`rest-api`
|
||||||
|
and therefore follows the conventions listed there.
|
||||||
|
|
||||||
|
Bank import job resource
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Resource description
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
The bank import job resource contains the following public fields:
|
||||||
|
|
||||||
|
.. rst-class:: rest-resource-table
|
||||||
|
|
||||||
|
===================================== ========================== =======================================================
|
||||||
|
Field Type Description
|
||||||
|
===================================== ========================== =======================================================
|
||||||
|
id integer Internal job ID
|
||||||
|
event string Slug of the event this job was uploaded for or ``null``
|
||||||
|
created datetime Job creation time
|
||||||
|
state string Job state, one of ``pending``, ``running``,
|
||||||
|
``error`` or ``completed``
|
||||||
|
transactions list of objects Transactions included in this job (will only appear
|
||||||
|
after the job has started processing).
|
||||||
|
├ state string Transaction state, one of ``imported``, ``nomatch``,
|
||||||
|
``invalid``, ``error``, ``valid``, ``discarded``,
|
||||||
|
``already`` (already paid)
|
||||||
|
├ message string Error message (if any)
|
||||||
|
├ checksum string Checksum computed from payer, reference, amount and
|
||||||
|
date
|
||||||
|
├ payer string Payment source
|
||||||
|
├ reference string Payment reference
|
||||||
|
├ amount string Payment amount
|
||||||
|
├ date string Payment date (in **user-inputted** format)
|
||||||
|
├ order string Associated order code (or ``null``)
|
||||||
|
└ comment string Internal comment
|
||||||
|
===================================== ========================== =======================================================
|
||||||
|
|
||||||
|
Note that the ``payer`` and ``reference`` fields are set to empty as soon as the payment is matched to an order or
|
||||||
|
discarded to avoid storing sensitive data when not necessary. The ``checksum`` persists to implement deduplication.
|
||||||
|
|
||||||
|
|
||||||
|
Endpoints
|
||||||
|
---------
|
||||||
|
|
||||||
|
.. http:get:: /api/v1/organizers/(organizer)/bankimportjobs/
|
||||||
|
|
||||||
|
Returns a list of all bank import jobs within a given organizer the authenticated user/token has access to.
|
||||||
|
|
||||||
|
**Example request**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
GET /api/v1/organizers/bigevents/bankimportjobs/ HTTP/1.1
|
||||||
|
Host: pretix.eu
|
||||||
|
Accept: application/json, text/javascript
|
||||||
|
|
||||||
|
**Example response**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Vary: Accept
|
||||||
|
Content-Type: text/javascript
|
||||||
|
|
||||||
|
{
|
||||||
|
"count": 1,
|
||||||
|
"next": null,
|
||||||
|
"previous": null,
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"state": "completed",
|
||||||
|
"created": "2017-06-27T08:00:29Z",
|
||||||
|
"event": "sampleconf",
|
||||||
|
"transactions": [
|
||||||
|
{
|
||||||
|
"amount": "57.00",
|
||||||
|
"comment": "",
|
||||||
|
"date": "26.06.2017",
|
||||||
|
"payer": "John Doe",
|
||||||
|
"order": null,
|
||||||
|
"checksum": "5de03a601644dfa63420dacfd285565f8375a8f2",
|
||||||
|
"reference": "GUTSCHRIFT\r\nSAMPLECONF-NAB12 EREF: SAMPLECONF-NAB12\r\nIBAN: DE1234556…",
|
||||||
|
"state": "nomatch",
|
||||||
|
"message": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
:query page: The page number in case of a multi-page result set, default is 1
|
||||||
|
:query event: Return only jobs for the event with the given slug
|
||||||
|
:query state: Return only jobs with the given state
|
||||||
|
:param organizer: The ``slug`` field of a valid organizer
|
||||||
|
:statuscode 200: no error
|
||||||
|
:statuscode 401: Authentication failure
|
||||||
|
:statuscode 403: The requested organizer does not exist **or** you have no permission to view it.
|
||||||
|
|
||||||
|
.. http:get:: /api/v1/organizers/(organizer)/bankimportjobs/(id)/
|
||||||
|
|
||||||
|
Returns information on one job, identified by its ID.
|
||||||
|
|
||||||
|
**Example request**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
GET /api/v1/organizers/bigevents/bankimportjobs/1/ HTTP/1.1
|
||||||
|
Host: pretix.eu
|
||||||
|
Accept: application/json, text/javascript
|
||||||
|
|
||||||
|
**Example response**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Vary: Accept
|
||||||
|
Content-Type: text/javascript
|
||||||
|
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"state": "completed",
|
||||||
|
"created": "2017-06-27T08:00:29Z",
|
||||||
|
"event": "sampleconf",
|
||||||
|
"transactions": [
|
||||||
|
{
|
||||||
|
"amount": "57.00",
|
||||||
|
"comment": "",
|
||||||
|
"date": "26.06.2017",
|
||||||
|
"payer": "John Doe",
|
||||||
|
"order": null,
|
||||||
|
"checksum": "5de03a601644dfa63420dacfd285565f8375a8f2",
|
||||||
|
"reference": "GUTSCHRIFT\r\nSAMPLECONF-NAB12 EREF: SAMPLECONF-NAB12\r\nIBAN: DE1234556…",
|
||||||
|
"state": "nomatch",
|
||||||
|
"message": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
:param organizer: The ``slug`` field of the organizer to fetch
|
||||||
|
:statuscode 200: no error
|
||||||
|
:statuscode 401: Authentication failure
|
||||||
|
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view it.
|
||||||
|
|
||||||
|
.. http:post:: /api/v1/organizers/(organizer)/bankimportjobs/
|
||||||
|
|
||||||
|
Upload a new job and execute it.
|
||||||
|
|
||||||
|
**Example request**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
POST /api/v1/organizers/bigevents/bankimportjobs/ HTTP/1.1
|
||||||
|
Host: pretix.eu
|
||||||
|
Accept: application/json, text/javascript
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"event": "sampleconf",
|
||||||
|
"transactions": [
|
||||||
|
{
|
||||||
|
"payer": "Foo",
|
||||||
|
"reference": "SAMPLECONF-173AS",
|
||||||
|
"amount": "23.00",
|
||||||
|
"date": "2017-06-26"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
**Example response**:
|
||||||
|
|
||||||
|
.. sourcecode:: http
|
||||||
|
|
||||||
|
HTTP/1.1 201 Created
|
||||||
|
Vary: Accept
|
||||||
|
Content-Type: text/javascript
|
||||||
|
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"state": "pending",
|
||||||
|
"created": "2017-06-27T08:00:29Z",
|
||||||
|
"event": "sampleconf",
|
||||||
|
"transactions": []
|
||||||
|
}
|
||||||
|
|
||||||
|
.. note:: Depending on the server configuration, the job might be executed immediately, leading to a longer API
|
||||||
|
response time but a response with state ``completed`` or ``error``, or the job might be put into a
|
||||||
|
background queue, leading to an immediate response of state ``pending`` with an empty list of
|
||||||
|
transactions.
|
||||||
|
|
||||||
|
:param organizer: The ``slug`` field of a valid organizer
|
||||||
|
:statuscode 201: no error
|
||||||
|
:statuscode 400: Invalid input
|
||||||
|
:statuscode 401: Authentication failure
|
||||||
|
:statuscode 403: The requested organizer does not exist **or** you have no permission to perform this action.
|
||||||
|
|
||||||
|
.. _pretix-banktool: https://github.com/pretix/pretix-banktool
|
||||||
@@ -11,3 +11,4 @@ If you want to **create** a plugin, please go to the
|
|||||||
|
|
||||||
list
|
list
|
||||||
pretixdroid
|
pretixdroid
|
||||||
|
banktransfer
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ ways that pretix itself is:
|
|||||||
* PDF ticket output
|
* PDF ticket output
|
||||||
|
|
||||||
The following plugins are not shipped with pretix but are maintained by the
|
The following plugins are not shipped with pretix but are maintained by the
|
||||||
same team:
|
same team. We update them regularly to make them compatible with the latest
|
||||||
|
pretix releases:
|
||||||
|
|
||||||
* `SEPA direct debit`_
|
* `SEPA direct debit`_
|
||||||
* `Pages`_
|
* `Pages`_
|
||||||
@@ -23,8 +24,17 @@ same team:
|
|||||||
* `Cartshare`_
|
* `Cartshare`_
|
||||||
* `Fontpack Free fonts`_
|
* `Fontpack Free fonts`_
|
||||||
|
|
||||||
|
The following closed-source plugins are available to customers of the hosted pretix.eu platform.
|
||||||
|
Please get in touch with the pretix team if you want to have them for your self-hosted
|
||||||
|
pretix installation:
|
||||||
|
|
||||||
|
* Campaign tracking
|
||||||
|
* Integration with Google Analytics and Facebook Pixel
|
||||||
|
* Integration with Slack
|
||||||
|
* Integration with MailChimp
|
||||||
|
|
||||||
The following plugins are from independent third-party authors, so we can make
|
The following plugins are from independent third-party authors, so we can make
|
||||||
no statements about their stability:
|
no statements about their stability or compatibility:
|
||||||
|
|
||||||
* `esPass ticket output`_
|
* `esPass ticket output`_
|
||||||
* `IcePay integration`_
|
* `IcePay integration`_
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ The pretixdroid plugin provides a HTTP API that the `pretixdroid Android app`_
|
|||||||
uses to communicate with the pretix server.
|
uses to communicate with the pretix server.
|
||||||
|
|
||||||
.. warning:: This API is intended **only** to serve the pretixdroid Android app. There are no backwards compatibility
|
.. warning:: This API is intended **only** to serve the pretixdroid Android app. There are no backwards compatibility
|
||||||
guarantees on this API. We will not add features that are not required for the Android App. There will be
|
guarantees on this API. We will not add features that are not required for the Android App. There is a
|
||||||
a proper general-use API for pretix at a later point in time.
|
general-purpose :ref:`rest-api` that not yet provides all features that this API provides, but will do
|
||||||
|
so in the future.
|
||||||
|
|
||||||
.. http:post:: /pretixdroid/api/(organizer)/(event)/redeem/
|
.. http:post:: /pretixdroid/api/(organizer)/(event)/redeem/
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
User Guide
|
User Guide
|
||||||
==========
|
==========
|
||||||
|
|
||||||
Contents:
|
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,12 @@ The choice totally depends on you and what your customers expect from you. Optio
|
|||||||
offer different payment methods and want to encourage your customers to use the ones that come you cheaper, but you
|
offer different payment methods and want to encourage your customers to use the ones that come you cheaper, but you
|
||||||
might also decide to go for option one to make it easier for customers who don't have the option.
|
might also decide to go for option one to make it easier for customers who don't have the option.
|
||||||
|
|
||||||
|
.. warning:: Please note that EU Directive 2015/2366 bans surcharging payment fees for most common payment
|
||||||
|
methods within the European Union. Depending on the payment method, this might affect
|
||||||
|
selling to consumers only or to business customers as well. Depending on your country, this
|
||||||
|
legislation might already be in place or become relevant from January 2018 the latest. This is not
|
||||||
|
legal advice. If in doubt, consult a lawyer or refrain from charging payment fees.
|
||||||
|
|
||||||
If you go for the second option, you can configure pretix to charge the payment method fees to your user. You can
|
If you go for the second option, you can configure pretix to charge the payment method fees to your user. You can
|
||||||
define both an absolute fee as well as a percental fee based on the order total. If you do so, there are two
|
define both an absolute fee as well as a percental fee based on the order total. If you do so, there are two
|
||||||
different ways in which pretix can calculate the fee. Normally, it is fine to just go with the default setting, but
|
different ways in which pretix can calculate the fee. Normally, it is fine to just go with the default setting, but
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
Accepting payments
|
Accepting payments
|
||||||
==================
|
==================
|
||||||
|
|
||||||
Contents:
|
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ t = Team.objects.get_or_create(
|
|||||||
can_change_organizer_settings=True, can_change_event_settings=True, can_change_items=True,
|
can_change_organizer_settings=True, can_change_event_settings=True, can_change_items=True,
|
||||||
can_view_orders=True, can_change_orders=True, can_view_vouchers=True, can_change_vouchers=True
|
can_view_orders=True, can_change_orders=True, can_view_vouchers=True, can_change_vouchers=True
|
||||||
)
|
)
|
||||||
t.members.add(user)
|
t[0].members.add(user)
|
||||||
cat_tickets = ItemCategory.objects.create(
|
cat_tickets = ItemCategory.objects.create(
|
||||||
event=event, name='Tickets'
|
event=event, name='Tickets'
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
__version__ = "1.4.0-dev0"
|
__version__ = "1.5.2"
|
||||||
|
|||||||
0
src/pretix/api/__init__.py
Normal file
0
src/pretix/api/__init__.py
Normal file
0
src/pretix/api/auth/__init__.py
Normal file
0
src/pretix/api/auth/__init__.py
Normal file
43
src/pretix/api/auth/permission.py
Normal file
43
src/pretix/api/auth/permission.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
from rest_framework.permissions import SAFE_METHODS, BasePermission
|
||||||
|
|
||||||
|
from pretix.base.models import Event
|
||||||
|
from pretix.base.models.organizer import Organizer, TeamAPIToken
|
||||||
|
|
||||||
|
|
||||||
|
class EventPermission(BasePermission):
|
||||||
|
model = TeamAPIToken
|
||||||
|
|
||||||
|
def has_permission(self, request, view):
|
||||||
|
if not request.user.is_authenticated and not isinstance(request.auth, TeamAPIToken):
|
||||||
|
if request.method in SAFE_METHODS and request.path.startswith('/api/v1/docs/'):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
perm_holder = (request.auth if isinstance(request.auth, TeamAPIToken)
|
||||||
|
else request.user)
|
||||||
|
if 'event' in request.resolver_match.kwargs and 'organizer' in request.resolver_match.kwargs:
|
||||||
|
request.event = Event.objects.filter(
|
||||||
|
slug=request.resolver_match.kwargs['event'],
|
||||||
|
organizer__slug=request.resolver_match.kwargs['organizer'],
|
||||||
|
).select_related('organizer').first()
|
||||||
|
if not request.event or not perm_holder.has_event_permission(request.event.organizer, request.event):
|
||||||
|
return False
|
||||||
|
request.organizer = request.event.organizer
|
||||||
|
request.eventpermset = perm_holder.get_event_permission_set(request.organizer, request.event)
|
||||||
|
|
||||||
|
if hasattr(view, 'permission'):
|
||||||
|
if view.permission and view.permission not in request.eventpermset:
|
||||||
|
return False
|
||||||
|
|
||||||
|
elif 'organizer' in request.resolver_match.kwargs:
|
||||||
|
request.organizer = Organizer.objects.filter(
|
||||||
|
slug=request.resolver_match.kwargs['organizer'],
|
||||||
|
).first()
|
||||||
|
if not request.organizer or not perm_holder.has_organizer_permission(request.organizer):
|
||||||
|
return False
|
||||||
|
request.orgapermset = perm_holder.get_organizer_permission_set(request.organizer)
|
||||||
|
|
||||||
|
if hasattr(view, 'permission'):
|
||||||
|
if view.permission and view.permission not in request.orgapermset:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
21
src/pretix/api/auth/token.py
Normal file
21
src/pretix/api/auth/token.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
from django.contrib.auth.models import AnonymousUser
|
||||||
|
from rest_framework import exceptions
|
||||||
|
from rest_framework.authentication import TokenAuthentication
|
||||||
|
|
||||||
|
from pretix.base.models.organizer import TeamAPIToken
|
||||||
|
|
||||||
|
|
||||||
|
class TeamTokenAuthentication(TokenAuthentication):
|
||||||
|
model = TeamAPIToken
|
||||||
|
|
||||||
|
def authenticate_credentials(self, key):
|
||||||
|
model = self.get_model()
|
||||||
|
try:
|
||||||
|
token = model.objects.select_related('team', 'team__organizer').get(token=key)
|
||||||
|
except model.DoesNotExist:
|
||||||
|
raise exceptions.AuthenticationFailed('Invalid token.')
|
||||||
|
|
||||||
|
if not token.active:
|
||||||
|
raise exceptions.AuthenticationFailed('Token inactive or deleted.')
|
||||||
|
|
||||||
|
return AnonymousUser(), token
|
||||||
0
src/pretix/api/serializers/__init__.py
Normal file
0
src/pretix/api/serializers/__init__.py
Normal file
10
src/pretix/api/serializers/event.py
Normal file
10
src/pretix/api/serializers/event.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
from pretix.api.serializers.i18n import I18nAwareModelSerializer
|
||||||
|
from pretix.base.models import Event
|
||||||
|
|
||||||
|
|
||||||
|
class EventSerializer(I18nAwareModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Event
|
||||||
|
fields = ('name', 'slug', 'live', 'currency', 'date_from',
|
||||||
|
'date_to', 'date_admission', 'is_public', 'presale_start',
|
||||||
|
'presale_end', 'location')
|
||||||
31
src/pretix/api/serializers/i18n.py
Normal file
31
src/pretix/api/serializers/i18n.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
from django.conf import settings
|
||||||
|
from i18nfield.fields import I18nCharField, I18nTextField
|
||||||
|
from rest_framework.fields import Field
|
||||||
|
from rest_framework.serializers import ModelSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class I18nField(Field):
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
self.allow_blank = kwargs.pop('allow_blank', False)
|
||||||
|
self.trim_whitespace = kwargs.pop('trim_whitespace', True)
|
||||||
|
self.max_length = kwargs.pop('max_length', None)
|
||||||
|
self.min_length = kwargs.pop('min_length', None)
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
def to_representation(self, value):
|
||||||
|
if value is None or value.data is None:
|
||||||
|
return None
|
||||||
|
if isinstance(value.data, dict):
|
||||||
|
return value.data
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
settings.LANGUAGE_CODE: str(value.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class I18nAwareModelSerializer(ModelSerializer):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
I18nAwareModelSerializer.serializer_field_mapping[I18nCharField] = I18nField
|
||||||
|
I18nAwareModelSerializer.serializer_field_mapping[I18nTextField] = I18nField
|
||||||
64
src/pretix/api/serializers/item.py
Normal file
64
src/pretix/api/serializers/item.py
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from pretix.api.serializers.i18n import I18nAwareModelSerializer
|
||||||
|
from pretix.base.models import (
|
||||||
|
Item, ItemAddOn, ItemCategory, ItemVariation, Question, QuestionOption,
|
||||||
|
Quota,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class InlineItemVariationSerializer(I18nAwareModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = ItemVariation
|
||||||
|
fields = ('id', 'value', 'active', 'description',
|
||||||
|
'position', 'default_price', 'price')
|
||||||
|
|
||||||
|
|
||||||
|
class InlineItemAddOnSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = ItemAddOn
|
||||||
|
fields = ('addon_category', 'min_count', 'max_count',
|
||||||
|
'position')
|
||||||
|
|
||||||
|
|
||||||
|
class ItemSerializer(I18nAwareModelSerializer):
|
||||||
|
addons = InlineItemAddOnSerializer(many=True)
|
||||||
|
variations = InlineItemVariationSerializer(many=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Item
|
||||||
|
fields = ('id', 'category', 'name', 'active', 'description',
|
||||||
|
'default_price', 'free_price', 'tax_rate', 'admission',
|
||||||
|
'position', 'picture', 'available_from', 'available_until',
|
||||||
|
'require_voucher', 'hide_without_voucher', 'allow_cancel',
|
||||||
|
'min_per_order', 'max_per_order', 'has_variations',
|
||||||
|
'variations', 'addons')
|
||||||
|
|
||||||
|
|
||||||
|
class ItemCategorySerializer(I18nAwareModelSerializer):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ItemCategory
|
||||||
|
fields = ('id', 'name', 'description', 'position', 'is_addon')
|
||||||
|
|
||||||
|
|
||||||
|
class InlineQuestionOptionSerializer(I18nAwareModelSerializer):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = QuestionOption
|
||||||
|
fields = ('id', 'answer')
|
||||||
|
|
||||||
|
|
||||||
|
class QuestionSerializer(I18nAwareModelSerializer):
|
||||||
|
options = InlineQuestionOptionSerializer(many=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Question
|
||||||
|
fields = ('id', 'question', 'type', 'required', 'items', 'options', 'position')
|
||||||
|
|
||||||
|
|
||||||
|
class QuotaSerializer(I18nAwareModelSerializer):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Quota
|
||||||
|
fields = ('id', 'name', 'size', 'items', 'variations')
|
||||||
118
src/pretix/api/serializers/order.py
Normal file
118
src/pretix/api/serializers/order.py
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
from rest_framework.reverse import reverse
|
||||||
|
|
||||||
|
from pretix.api.serializers.i18n import I18nAwareModelSerializer
|
||||||
|
from pretix.base.models import (
|
||||||
|
Checkin, Invoice, InvoiceAddress, InvoiceLine, Order, OrderPosition,
|
||||||
|
QuestionAnswer,
|
||||||
|
)
|
||||||
|
from pretix.base.signals import register_ticket_outputs
|
||||||
|
|
||||||
|
|
||||||
|
class InvoiceAdddressSerializer(I18nAwareModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = InvoiceAddress
|
||||||
|
fields = ('last_modified', 'company', 'name', 'street', 'zipcode', 'city', 'country', 'vat_id')
|
||||||
|
|
||||||
|
|
||||||
|
class AnswerSerializer(I18nAwareModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = QuestionAnswer
|
||||||
|
fields = ('question', 'answer', 'options')
|
||||||
|
|
||||||
|
|
||||||
|
class CheckinSerializer(I18nAwareModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Checkin
|
||||||
|
fields = ('datetime',)
|
||||||
|
|
||||||
|
|
||||||
|
class OrderDownloadsField(serializers.Field):
|
||||||
|
def to_representation(self, instance: Order):
|
||||||
|
if instance.status != Order.STATUS_PAID:
|
||||||
|
return []
|
||||||
|
|
||||||
|
request = self.context['request']
|
||||||
|
res = []
|
||||||
|
responses = register_ticket_outputs.send(instance.event)
|
||||||
|
for receiver, response in responses:
|
||||||
|
provider = response(instance.event)
|
||||||
|
if provider.is_enabled:
|
||||||
|
res.append({
|
||||||
|
'output': provider.identifier,
|
||||||
|
'url': reverse('api-v1:order-download', kwargs={
|
||||||
|
'organizer': instance.event.organizer.slug,
|
||||||
|
'event': instance.event.slug,
|
||||||
|
'code': instance.code,
|
||||||
|
'output': provider.identifier,
|
||||||
|
}, request=request)
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
class PositionDownloadsField(serializers.Field):
|
||||||
|
def to_representation(self, instance: OrderPosition):
|
||||||
|
if instance.order.status != Order.STATUS_PAID:
|
||||||
|
return []
|
||||||
|
if instance.addon_to_id and not instance.order.event.settings.ticket_download_addons:
|
||||||
|
return []
|
||||||
|
if not instance.item.admission and not instance.order.event.settings.ticket_download_nonadm:
|
||||||
|
return []
|
||||||
|
|
||||||
|
request = self.context['request']
|
||||||
|
res = []
|
||||||
|
responses = register_ticket_outputs.send(instance.order.event)
|
||||||
|
for receiver, response in responses:
|
||||||
|
provider = response(instance.order.event)
|
||||||
|
if provider.is_enabled:
|
||||||
|
res.append({
|
||||||
|
'output': provider.identifier,
|
||||||
|
'url': reverse('api-v1:orderposition-download', kwargs={
|
||||||
|
'organizer': instance.order.event.organizer.slug,
|
||||||
|
'event': instance.order.event.slug,
|
||||||
|
'pk': instance.pk,
|
||||||
|
'output': provider.identifier,
|
||||||
|
}, request=request)
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
class OrderPositionSerializer(I18nAwareModelSerializer):
|
||||||
|
checkins = CheckinSerializer(many=True)
|
||||||
|
answers = AnswerSerializer(many=True)
|
||||||
|
downloads = PositionDownloadsField(source='*')
|
||||||
|
order = serializers.SlugRelatedField(slug_field='code', read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = OrderPosition
|
||||||
|
fields = ('id', 'order', 'positionid', 'item', 'variation', 'price', 'attendee_name', 'attendee_email',
|
||||||
|
'voucher', 'tax_rate', 'tax_value', 'secret', 'addon_to', 'checkins', 'downloads', 'answers')
|
||||||
|
|
||||||
|
|
||||||
|
class OrderSerializer(I18nAwareModelSerializer):
|
||||||
|
invoice_address = InvoiceAdddressSerializer()
|
||||||
|
positions = OrderPositionSerializer(many=True)
|
||||||
|
downloads = OrderDownloadsField(source='*')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Order
|
||||||
|
fields = ('code', 'status', 'secret', 'email', 'locale', 'datetime', 'expires', 'payment_date',
|
||||||
|
'payment_provider', 'payment_fee', 'payment_fee_tax_rate', 'payment_fee_tax_value',
|
||||||
|
'total', 'comment', 'invoice_address', 'positions', 'downloads')
|
||||||
|
|
||||||
|
|
||||||
|
class InlineInvoiceLineSerializer(I18nAwareModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = InvoiceLine
|
||||||
|
fields = ('description', 'gross_value', 'tax_value', 'tax_rate')
|
||||||
|
|
||||||
|
|
||||||
|
class InvoiceSerializer(I18nAwareModelSerializer):
|
||||||
|
order = serializers.SlugRelatedField(slug_field='code', read_only=True)
|
||||||
|
refers = serializers.SlugRelatedField(slug_field='invoice_no', read_only=True)
|
||||||
|
lines = InlineInvoiceLineSerializer(many=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Invoice
|
||||||
|
fields = ('order', 'invoice_no', 'is_cancellation', 'invoice_from', 'invoice_to', 'date', 'refers', 'locale',
|
||||||
|
'introductory_text', 'additional_text', 'payment_provider_text', 'footer_text', 'lines')
|
||||||
8
src/pretix/api/serializers/organizer.py
Normal file
8
src/pretix/api/serializers/organizer.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
from pretix.api.serializers.i18n import I18nAwareModelSerializer
|
||||||
|
from pretix.base.models import Organizer
|
||||||
|
|
||||||
|
|
||||||
|
class OrganizerSerializer(I18nAwareModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Organizer
|
||||||
|
fields = ('name', 'slug')
|
||||||
10
src/pretix/api/serializers/voucher.py
Normal file
10
src/pretix/api/serializers/voucher.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
from pretix.api.serializers.i18n import I18nAwareModelSerializer
|
||||||
|
from pretix.base.models import Voucher
|
||||||
|
|
||||||
|
|
||||||
|
class VoucherSerializer(I18nAwareModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Voucher
|
||||||
|
fields = ('id', 'code', 'max_usages', 'redeemed', 'valid_until', 'block_quota',
|
||||||
|
'allow_ignore_quota', 'price_mode', 'value', 'item', 'variation', 'quota',
|
||||||
|
'tag', 'comment')
|
||||||
9
src/pretix/api/serializers/waitinglist.py
Normal file
9
src/pretix/api/serializers/waitinglist.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
from pretix.api.serializers.i18n import I18nAwareModelSerializer
|
||||||
|
from pretix.base.models import WaitingListEntry
|
||||||
|
|
||||||
|
|
||||||
|
class WaitingListSerializer(I18nAwareModelSerializer):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = WaitingListEntry
|
||||||
|
fields = ('id', 'created', 'email', 'voucher', 'item', 'variation', 'locale')
|
||||||
0
src/pretix/api/templates/__init__.py
Normal file
0
src/pretix/api/templates/__init__.py
Normal file
19
src/pretix/api/templates/rest_framework/api.html
Normal file
19
src/pretix/api/templates/rest_framework/api.html
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{% extends "rest_framework/base.html" %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
{% load compress %}
|
||||||
|
|
||||||
|
{% block bootstrap_theme %}
|
||||||
|
{% compress css %}
|
||||||
|
<link rel="stylesheet" type="text/x-scss" href="{% static "rest_framework/scss/main.scss" %}" />
|
||||||
|
{% endcompress %}
|
||||||
|
{% endblock %}
|
||||||
|
{% block branding %}
|
||||||
|
<a class="navbar-brand" href="/api/v1/">pretix REST API</a>
|
||||||
|
{% endblock %}
|
||||||
|
{% block description %}
|
||||||
|
<div class="alert alert-info alert-docs-link">
|
||||||
|
<a href="https://docs.pretix.eu/en/latest/api/index.html">
|
||||||
|
You can find documentation on our REST API on docs.pretix.eu.
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
36
src/pretix/api/urls.py
Normal file
36
src/pretix/api/urls.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import importlib
|
||||||
|
|
||||||
|
from django.apps import apps
|
||||||
|
from django.conf.urls import include, url
|
||||||
|
from rest_framework import routers
|
||||||
|
|
||||||
|
from .views import event, item, order, organizer, voucher, waitinglist
|
||||||
|
|
||||||
|
router = routers.DefaultRouter()
|
||||||
|
router.register(r'organizers', organizer.OrganizerViewSet)
|
||||||
|
|
||||||
|
orga_router = routers.DefaultRouter()
|
||||||
|
orga_router.register(r'events', event.EventViewSet)
|
||||||
|
|
||||||
|
event_router = routers.DefaultRouter()
|
||||||
|
event_router.register(r'items', item.ItemViewSet)
|
||||||
|
event_router.register(r'categories', item.ItemCategoryViewSet)
|
||||||
|
event_router.register(r'questions', item.QuestionViewSet)
|
||||||
|
event_router.register(r'quotas', item.QuotaViewSet)
|
||||||
|
event_router.register(r'vouchers', voucher.VoucherViewSet)
|
||||||
|
event_router.register(r'orders', order.OrderViewSet)
|
||||||
|
event_router.register(r'orderpositions', order.OrderPositionViewSet)
|
||||||
|
event_router.register(r'invoices', order.InvoiceViewSet)
|
||||||
|
event_router.register(r'waitinglistentries', waitinglist.WaitingListViewSet)
|
||||||
|
|
||||||
|
# Force import of all plugins to give them a chance to register URLs with the router
|
||||||
|
for app in apps.get_app_configs():
|
||||||
|
if hasattr(app, 'PretixPluginMeta'):
|
||||||
|
if importlib.util.find_spec(app.name + '.urls'):
|
||||||
|
importlib.import_module(app.name + '.urls')
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
url(r'^', include(router.urls)),
|
||||||
|
url(r'^organizers/(?P<organizer>[^/]+)/', include(orga_router.urls)),
|
||||||
|
url(r'^organizers/(?P<organizer>[^/]+)/events/(?P<event>[^/]+)/', include(event_router.urls)),
|
||||||
|
]
|
||||||
0
src/pretix/api/views/__init__.py
Normal file
0
src/pretix/api/views/__init__.py
Normal file
14
src/pretix/api/views/event.py
Normal file
14
src/pretix/api/views/event.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
from rest_framework import viewsets
|
||||||
|
|
||||||
|
from pretix.api.serializers.event import EventSerializer
|
||||||
|
from pretix.base.models import Event
|
||||||
|
|
||||||
|
|
||||||
|
class EventViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
|
serializer_class = EventSerializer
|
||||||
|
queryset = Event.objects.none()
|
||||||
|
lookup_field = 'slug'
|
||||||
|
lookup_url_kwarg = 'event'
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return self.request.organizer.events.all()
|
||||||
87
src/pretix/api/views/item.py
Normal file
87
src/pretix/api/views/item.py
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
|
||||||
|
from rest_framework import viewsets
|
||||||
|
from rest_framework.decorators import detail_route
|
||||||
|
from rest_framework.filters import OrderingFilter
|
||||||
|
from rest_framework.response import Response
|
||||||
|
|
||||||
|
from pretix.api.serializers.item import (
|
||||||
|
ItemCategorySerializer, ItemSerializer, QuestionSerializer,
|
||||||
|
QuotaSerializer,
|
||||||
|
)
|
||||||
|
from pretix.base.models import Item, ItemCategory, Question, Quota
|
||||||
|
|
||||||
|
|
||||||
|
class ItemFilter(FilterSet):
|
||||||
|
class Meta:
|
||||||
|
model = Item
|
||||||
|
fields = ['active', 'category', 'admission', 'tax_rate', 'free_price']
|
||||||
|
|
||||||
|
|
||||||
|
class ItemViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
|
serializer_class = ItemSerializer
|
||||||
|
queryset = Item.objects.none()
|
||||||
|
filter_backends = (DjangoFilterBackend, OrderingFilter)
|
||||||
|
ordering_fields = ('id', 'position')
|
||||||
|
ordering = ('position', 'id')
|
||||||
|
filter_class = ItemFilter
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return self.request.event.items.prefetch_related('variations', 'addons').all()
|
||||||
|
|
||||||
|
|
||||||
|
class ItemCategoryFilter(FilterSet):
|
||||||
|
class Meta:
|
||||||
|
model = ItemCategory
|
||||||
|
fields = ['is_addon']
|
||||||
|
|
||||||
|
|
||||||
|
class ItemCategoryViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
|
serializer_class = ItemCategorySerializer
|
||||||
|
queryset = ItemCategory.objects.none()
|
||||||
|
filter_backends = (DjangoFilterBackend, OrderingFilter)
|
||||||
|
filter_class = ItemCategoryFilter
|
||||||
|
ordering_fields = ('id', 'position')
|
||||||
|
ordering = ('position', 'id')
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return self.request.event.categories.all()
|
||||||
|
|
||||||
|
|
||||||
|
class QuestionViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
|
serializer_class = QuestionSerializer
|
||||||
|
queryset = Question.objects.none()
|
||||||
|
filter_backends = (OrderingFilter,)
|
||||||
|
ordering_fields = ('id', 'position')
|
||||||
|
ordering = ('position', 'id')
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return self.request.event.questions.prefetch_related('options').all()
|
||||||
|
|
||||||
|
|
||||||
|
class QuotaViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
|
serializer_class = QuotaSerializer
|
||||||
|
queryset = Quota.objects.none()
|
||||||
|
filter_backends = (OrderingFilter,)
|
||||||
|
ordering_fields = ('id', 'size')
|
||||||
|
ordering = ('id',)
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return self.request.event.quotas.all()
|
||||||
|
|
||||||
|
@detail_route(methods=['get'])
|
||||||
|
def availability(self, request, *args, **kwargs):
|
||||||
|
quota = self.get_object()
|
||||||
|
|
||||||
|
avail = quota.availability()
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'paid_orders': quota.count_paid_orders(),
|
||||||
|
'pending_orders': quota.count_pending_orders(),
|
||||||
|
'blocking_vouchers': quota.count_blocking_vouchers(),
|
||||||
|
'cart_positions': quota.count_in_cart(),
|
||||||
|
'waiting_list': quota.count_waiting_list_pending(),
|
||||||
|
'available_number': avail[1],
|
||||||
|
'available': avail[0] == Quota.AVAILABILITY_OK,
|
||||||
|
'total_size': quota.size,
|
||||||
|
}
|
||||||
|
return Response(data)
|
||||||
181
src/pretix/api/views/order.py
Normal file
181
src/pretix/api/views/order.py
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
import django_filters
|
||||||
|
from django.db.models import Q
|
||||||
|
from django.http import FileResponse
|
||||||
|
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
|
||||||
|
from rest_framework import viewsets
|
||||||
|
from rest_framework.decorators import detail_route
|
||||||
|
from rest_framework.exceptions import APIException, NotFound, PermissionDenied
|
||||||
|
from rest_framework.filters import OrderingFilter
|
||||||
|
|
||||||
|
from pretix.api.serializers.order import (
|
||||||
|
InvoiceSerializer, OrderPositionSerializer, OrderSerializer,
|
||||||
|
)
|
||||||
|
from pretix.base.models import Invoice, Order, OrderPosition
|
||||||
|
from pretix.base.services.invoices import invoice_pdf
|
||||||
|
from pretix.base.services.tickets import (
|
||||||
|
get_cachedticket_for_order, get_cachedticket_for_position,
|
||||||
|
)
|
||||||
|
from pretix.base.signals import register_ticket_outputs
|
||||||
|
|
||||||
|
|
||||||
|
class OrderFilter(FilterSet):
|
||||||
|
class Meta:
|
||||||
|
model = Order
|
||||||
|
fields = ['code', 'status', 'email', 'locale']
|
||||||
|
|
||||||
|
|
||||||
|
class OrderViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
|
serializer_class = OrderSerializer
|
||||||
|
queryset = Order.objects.none()
|
||||||
|
filter_backends = (DjangoFilterBackend, OrderingFilter)
|
||||||
|
ordering = ('datetime',)
|
||||||
|
ordering_fields = ('datetime', 'code', 'status')
|
||||||
|
filter_class = OrderFilter
|
||||||
|
lookup_field = 'code'
|
||||||
|
permission = 'can_view_orders'
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return self.request.event.orders.prefetch_related(
|
||||||
|
'positions', 'positions__checkins', 'positions__item', 'positions__answers', 'positions__answers__options'
|
||||||
|
).select_related(
|
||||||
|
'invoice_address'
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_output_provider(self, identifier):
|
||||||
|
responses = register_ticket_outputs.send(self.request.event)
|
||||||
|
for receiver, response in responses:
|
||||||
|
prov = response(self.request.event)
|
||||||
|
if prov.identifier == identifier:
|
||||||
|
return prov
|
||||||
|
raise NotFound('Unknown output provider.')
|
||||||
|
|
||||||
|
@detail_route(url_name='download', url_path='download/(?P<output>[^/]+)')
|
||||||
|
def download(self, request, output, **kwargs):
|
||||||
|
provider = self._get_output_provider(output)
|
||||||
|
order = self.get_object()
|
||||||
|
|
||||||
|
if order.status != Order.STATUS_PAID:
|
||||||
|
raise PermissionDenied("Downloads are not available for unpaid orders.")
|
||||||
|
|
||||||
|
ct = get_cachedticket_for_order(order, provider.identifier)
|
||||||
|
|
||||||
|
if not ct.file:
|
||||||
|
raise RetryException()
|
||||||
|
else:
|
||||||
|
resp = FileResponse(ct.file.file, content_type=ct.type)
|
||||||
|
resp['Content-Disposition'] = 'attachment; filename="{}-{}-{}{}"'.format(
|
||||||
|
self.request.event.slug.upper(), order.code,
|
||||||
|
provider.identifier, ct.extension
|
||||||
|
)
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
class OrderPositionFilter(FilterSet):
|
||||||
|
order = django_filters.CharFilter(name='order', lookup_expr='code')
|
||||||
|
has_checkin = django_filters.rest_framework.BooleanFilter(method='has_checkin_qs')
|
||||||
|
attendee_name = django_filters.CharFilter(method='attendee_name_qs')
|
||||||
|
|
||||||
|
def has_checkin_qs(self, queryset, name, value):
|
||||||
|
return queryset.filter(checkins__isnull=not value)
|
||||||
|
|
||||||
|
def attendee_name_qs(self, queryset, name, value):
|
||||||
|
return queryset.filter(Q(attendee_name=value) | Q(addon_to__attendee_name=value))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = OrderPosition
|
||||||
|
fields = ['item', 'variation', 'attendee_name', 'secret', 'order', 'order__status', 'has_checkin',
|
||||||
|
'addon_to']
|
||||||
|
|
||||||
|
|
||||||
|
class OrderPositionViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
|
serializer_class = OrderPositionSerializer
|
||||||
|
queryset = OrderPosition.objects.none()
|
||||||
|
filter_backends = (DjangoFilterBackend, OrderingFilter)
|
||||||
|
ordering = ('order__datetime', 'positionid')
|
||||||
|
ordering_fields = ('order__code', 'order__datetime', 'positionid', 'attendee_name', 'order__status',)
|
||||||
|
filter_class = OrderPositionFilter
|
||||||
|
permission = 'can_view_orders'
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return OrderPosition.objects.filter(order__event=self.request.event).prefetch_related(
|
||||||
|
'checkins', 'answers', 'answers__options'
|
||||||
|
).select_related(
|
||||||
|
'item', 'order', 'order__event', 'order__event__organizer'
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_output_provider(self, identifier):
|
||||||
|
responses = register_ticket_outputs.send(self.request.event)
|
||||||
|
for receiver, response in responses:
|
||||||
|
prov = response(self.request.event)
|
||||||
|
if prov.identifier == identifier:
|
||||||
|
return prov
|
||||||
|
raise NotFound('Unknown output provider.')
|
||||||
|
|
||||||
|
@detail_route(url_name='download', url_path='download/(?P<output>[^/]+)')
|
||||||
|
def download(self, request, output, **kwargs):
|
||||||
|
provider = self._get_output_provider(output)
|
||||||
|
pos = self.get_object()
|
||||||
|
|
||||||
|
if pos.order.status != Order.STATUS_PAID:
|
||||||
|
raise PermissionDenied("Downloads are not available for unpaid orders.")
|
||||||
|
if pos.addon_to_id and not request.event.settings.ticket_download_addons:
|
||||||
|
raise PermissionDenied("Downloads are not enabled for add-on products.")
|
||||||
|
if not pos.item.admission and not request.event.settings.ticket_download_nonadm:
|
||||||
|
raise PermissionDenied("Downloads are not enabled for non-admission products.")
|
||||||
|
|
||||||
|
ct = get_cachedticket_for_position(pos, provider.identifier)
|
||||||
|
|
||||||
|
if not ct.file:
|
||||||
|
raise RetryException()
|
||||||
|
else:
|
||||||
|
resp = FileResponse(ct.file.file, content_type=ct.type)
|
||||||
|
resp['Content-Disposition'] = 'attachment; filename="{}-{}-{}-{}{}"'.format(
|
||||||
|
self.request.event.slug.upper(), pos.order.code, pos.positionid,
|
||||||
|
provider.identifier, ct.extension
|
||||||
|
)
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
class InvoiceFilter(FilterSet):
|
||||||
|
refers = django_filters.CharFilter(name='refers', lookup_expr='invoice_no__iexact')
|
||||||
|
order = django_filters.CharFilter(name='order', lookup_expr='code__iexact')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Invoice
|
||||||
|
fields = ['order', 'invoice_no', 'is_cancellation', 'refers', 'locale']
|
||||||
|
|
||||||
|
|
||||||
|
class RetryException(APIException):
|
||||||
|
status_code = 409
|
||||||
|
default_detail = 'The requested resource is not ready, please retry later.'
|
||||||
|
default_code = 'retry_later'
|
||||||
|
|
||||||
|
|
||||||
|
class InvoiceViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
|
serializer_class = InvoiceSerializer
|
||||||
|
queryset = Invoice.objects.none()
|
||||||
|
filter_backends = (DjangoFilterBackend, OrderingFilter)
|
||||||
|
ordering = ('invoice_no',)
|
||||||
|
ordering_fields = ('invoice_no', 'date')
|
||||||
|
filter_class = InvoiceFilter
|
||||||
|
lookup_field = 'invoice_no'
|
||||||
|
lookup_url_kwarg = 'invoice_no'
|
||||||
|
permission = 'can_view_orders'
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return self.request.event.invoices.prefetch_related('lines').select_related('order')
|
||||||
|
|
||||||
|
@detail_route()
|
||||||
|
def download(self, request, **kwargs):
|
||||||
|
invoice = self.get_object()
|
||||||
|
|
||||||
|
if not invoice.file:
|
||||||
|
invoice_pdf(invoice.pk)
|
||||||
|
invoice.refresh_from_db()
|
||||||
|
|
||||||
|
if not invoice.file:
|
||||||
|
raise RetryException()
|
||||||
|
|
||||||
|
resp = FileResponse(invoice.file.file, content_type='application/pdf')
|
||||||
|
resp['Content-Disposition'] = 'attachment; filename="{}.pdf"'.format(invoice.number)
|
||||||
|
return resp
|
||||||
20
src/pretix/api/views/organizer.py
Normal file
20
src/pretix/api/views/organizer.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
from rest_framework import viewsets
|
||||||
|
|
||||||
|
from pretix.api.serializers.organizer import OrganizerSerializer
|
||||||
|
from pretix.base.models import Organizer
|
||||||
|
|
||||||
|
|
||||||
|
class OrganizerViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
|
serializer_class = OrganizerSerializer
|
||||||
|
queryset = Organizer.objects.none()
|
||||||
|
lookup_field = 'slug'
|
||||||
|
lookup_url_kwarg = 'organizer'
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
if self.request.user.is_authenticated():
|
||||||
|
if self.request.user.is_superuser:
|
||||||
|
return Organizer.objects.all()
|
||||||
|
else:
|
||||||
|
return Organizer.objects.filter(pk__in=self.request.user.teams.values_list('organizer', flat=True))
|
||||||
|
else:
|
||||||
|
return Organizer.objects.filter(pk=self.request.auth.team.organizer_id)
|
||||||
40
src/pretix/api/views/voucher.py
Normal file
40
src/pretix/api/views/voucher.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
from django.db.models import F, Q
|
||||||
|
from django.utils.timezone import now
|
||||||
|
from django_filters.rest_framework import (
|
||||||
|
BooleanFilter, DjangoFilterBackend, FilterSet,
|
||||||
|
)
|
||||||
|
from rest_framework import viewsets
|
||||||
|
from rest_framework.filters import OrderingFilter
|
||||||
|
|
||||||
|
from pretix.api.serializers.voucher import VoucherSerializer
|
||||||
|
from pretix.base.models import Voucher
|
||||||
|
|
||||||
|
|
||||||
|
class VoucherFilter(FilterSet):
|
||||||
|
active = BooleanFilter(method='filter_active')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Voucher
|
||||||
|
fields = ['code', 'max_usages', 'redeemed', 'block_quota', 'allow_ignore_quota',
|
||||||
|
'price_mode', 'value', 'item', 'variation', 'quota', 'tag']
|
||||||
|
|
||||||
|
def filter_active(self, queryset, name, value):
|
||||||
|
if value:
|
||||||
|
return queryset.filter(Q(redeemed__lt=F('max_usages')) &
|
||||||
|
(Q(valid_until__isnull=True) | Q(valid_until__gt=now())))
|
||||||
|
else:
|
||||||
|
return queryset.filter(Q(redeemed__gte=F('max_usages')) |
|
||||||
|
(Q(valid_until__isnull=False) & Q(valid_until__lte=now())))
|
||||||
|
|
||||||
|
|
||||||
|
class VoucherViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
|
serializer_class = VoucherSerializer
|
||||||
|
queryset = Voucher.objects.none()
|
||||||
|
filter_backends = (DjangoFilterBackend, OrderingFilter)
|
||||||
|
ordering = ('id',)
|
||||||
|
ordering_fields = ('id', 'code', 'max_usages', 'valid_until', 'value')
|
||||||
|
filter_class = VoucherFilter
|
||||||
|
permission = 'can_view_vouchers'
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return self.request.event.vouchers.all()
|
||||||
31
src/pretix/api/views/waitinglist.py
Normal file
31
src/pretix/api/views/waitinglist.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import django_filters
|
||||||
|
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
|
||||||
|
from rest_framework import viewsets
|
||||||
|
from rest_framework.filters import OrderingFilter
|
||||||
|
|
||||||
|
from pretix.api.serializers.waitinglist import WaitingListSerializer
|
||||||
|
from pretix.base.models import WaitingListEntry
|
||||||
|
|
||||||
|
|
||||||
|
class WaitingListFilter(FilterSet):
|
||||||
|
has_voucher = django_filters.rest_framework.BooleanFilter(method='has_voucher_qs')
|
||||||
|
|
||||||
|
def has_voucher_qs(self, queryset, name, value):
|
||||||
|
return queryset.filter(voucher__isnull=not value)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = WaitingListEntry
|
||||||
|
fields = ['item', 'variation', 'email', 'locale', 'has_voucher']
|
||||||
|
|
||||||
|
|
||||||
|
class WaitingListViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
|
serializer_class = WaitingListSerializer
|
||||||
|
queryset = WaitingListEntry.objects.none()
|
||||||
|
filter_backends = (DjangoFilterBackend, OrderingFilter)
|
||||||
|
ordering = ('created',)
|
||||||
|
ordering_fields = ('id', 'created', 'email', 'item')
|
||||||
|
filter_class = WaitingListFilter
|
||||||
|
permission = 'can_view_orders'
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return self.request.event.waitinglistentries.all()
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import csv
|
|
||||||
import io
|
import io
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
import pytz
|
import pytz
|
||||||
|
from defusedcsv import csv
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.db.models import Sum
|
from django.db.models import Sum
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
@@ -13,7 +13,7 @@ from django.utils.translation import ugettext as _, ugettext_lazy
|
|||||||
from pretix.base.models import InvoiceAddress, Order, OrderPosition
|
from pretix.base.models import InvoiceAddress, Order, OrderPosition
|
||||||
|
|
||||||
from ..exporter import BaseExporter
|
from ..exporter import BaseExporter
|
||||||
from ..signals import register_data_exporters, register_payment_providers
|
from ..signals import register_data_exporters
|
||||||
|
|
||||||
|
|
||||||
class OrderListExporter(BaseExporter):
|
class OrderListExporter(BaseExporter):
|
||||||
@@ -35,13 +35,13 @@ class OrderListExporter(BaseExporter):
|
|||||||
|
|
||||||
def _get_all_tax_rates(self, qs):
|
def _get_all_tax_rates(self, qs):
|
||||||
tax_rates = set(
|
tax_rates = set(
|
||||||
qs.exclude(payment_fee=0).values_list('payment_fee_tax_rate', flat=True)
|
qs.exclude(payment_fee=0).values_list('payment_fee_tax_rate', flat=True).distinct().order_by()
|
||||||
.distinct().order_by()
|
|
||||||
)
|
)
|
||||||
tax_rates |= set(
|
tax_rates |= set(
|
||||||
a for a
|
a for a
|
||||||
in OrderPosition.objects.filter(order__event=self.event)
|
in OrderPosition.objects.filter(
|
||||||
.values_list('tax_rate', flat=True).distinct().order_by()
|
order__event=self.event
|
||||||
|
).values_list('tax_rate', flat=True).distinct().order_by()
|
||||||
)
|
)
|
||||||
tax_rates = sorted(tax_rates)
|
tax_rates = sorted(tax_rates)
|
||||||
return tax_rates
|
return tax_rates
|
||||||
@@ -73,11 +73,10 @@ class OrderListExporter(BaseExporter):
|
|||||||
|
|
||||||
writer.writerow(headers)
|
writer.writerow(headers)
|
||||||
|
|
||||||
provider_names = {}
|
provider_names = {
|
||||||
responses = register_payment_providers.send(self.event)
|
k: v.verbose_name
|
||||||
for rec, response in responses:
|
for k, v in self.event.get_payment_providers().items()
|
||||||
provider = response(self.event)
|
}
|
||||||
provider_names[provider.identifier] = provider.verbose_name
|
|
||||||
|
|
||||||
sum_cache = {
|
sum_cache = {
|
||||||
(o['order__id'], o['tax_rate']): o for o in
|
(o['order__id'], o['tax_rate']): o for o in
|
||||||
@@ -131,6 +130,42 @@ class OrderListExporter(BaseExporter):
|
|||||||
return 'orders.csv', 'text/csv', output.getvalue().encode("utf-8")
|
return 'orders.csv', 'text/csv', output.getvalue().encode("utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
class QuotaListExporter(BaseExporter):
|
||||||
|
identifier = 'quotalistcsv'
|
||||||
|
verbose_name = ugettext_lazy('Quota availabilities (CSV)')
|
||||||
|
|
||||||
|
def render(self, form_data: dict):
|
||||||
|
output = io.StringIO()
|
||||||
|
writer = csv.writer(output, quoting=csv.QUOTE_NONNUMERIC, delimiter=",")
|
||||||
|
|
||||||
|
headers = [
|
||||||
|
_('Quota name'), _('Total quota'), _('Paid orders'), _('Pending orders'), _('Blocking vouchers'),
|
||||||
|
_('Current user\'s carts'), _('Waiting list'), _('Current availability')
|
||||||
|
]
|
||||||
|
writer.writerow(headers)
|
||||||
|
|
||||||
|
for quota in self.event.quotas.all():
|
||||||
|
avail = quota.availability()
|
||||||
|
row = [
|
||||||
|
quota.name,
|
||||||
|
_('Infinite') if quota.size is None else quota.size,
|
||||||
|
quota.count_paid_orders(),
|
||||||
|
quota.count_pending_orders(),
|
||||||
|
quota.count_blocking_vouchers(),
|
||||||
|
quota.count_in_cart(),
|
||||||
|
quota.count_waiting_list_pending(),
|
||||||
|
_('Infinite') if avail[1] is None else avail[1]
|
||||||
|
]
|
||||||
|
writer.writerow(row)
|
||||||
|
|
||||||
|
return 'quotas.csv', 'text/csv', output.getvalue().encode("utf-8")
|
||||||
|
|
||||||
|
|
||||||
@receiver(register_data_exporters, dispatch_uid="exporter_orderlist")
|
@receiver(register_data_exporters, dispatch_uid="exporter_orderlist")
|
||||||
def register_orderlist_exporter(sender, **kwargs):
|
def register_orderlist_exporter(sender, **kwargs):
|
||||||
return OrderListExporter
|
return OrderListExporter
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(register_data_exporters, dispatch_uid="exporter_quotalist")
|
||||||
|
def register_quotalist_exporter(sender, **kwargs):
|
||||||
|
return QuotaListExporter
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ class UserSettingsForm(forms.ModelForm):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.user = kwargs.pop('user')
|
self.user = kwargs.pop('user')
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
self.fields['email'].required = True
|
||||||
|
|
||||||
def clean_old_pw(self):
|
def clean_old_pw(self):
|
||||||
old_pw = self.cleaned_data.get('old_pw')
|
old_pw = self.cleaned_data.get('old_pw')
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
from urllib.parse import urlsplit
|
||||||
|
|
||||||
import pytz
|
import pytz
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@@ -6,6 +7,7 @@ from django.core.urlresolvers import get_script_prefix
|
|||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest, HttpResponse
|
||||||
from django.utils import timezone, translation
|
from django.utils import timezone, translation
|
||||||
from django.utils.cache import patch_vary_headers
|
from django.utils.cache import patch_vary_headers
|
||||||
|
from django.utils.crypto import get_random_string
|
||||||
from django.utils.deprecation import MiddlewareMixin
|
from django.utils.deprecation import MiddlewareMixin
|
||||||
from django.utils.translation import LANGUAGE_SESSION_KEY
|
from django.utils.translation import LANGUAGE_SESSION_KEY
|
||||||
from django.utils.translation.trans_real import (
|
from django.utils.translation.trans_real import (
|
||||||
@@ -13,6 +15,8 @@ from django.utils.translation.trans_real import (
|
|||||||
parse_accept_lang_header,
|
parse_accept_lang_header,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from pretix.multidomain.urlreverse import get_domain
|
||||||
|
|
||||||
_supported = None
|
_supported = None
|
||||||
|
|
||||||
|
|
||||||
@@ -158,6 +162,12 @@ def _merge_csp(a, b):
|
|||||||
|
|
||||||
|
|
||||||
class SecurityMiddleware(MiddlewareMixin):
|
class SecurityMiddleware(MiddlewareMixin):
|
||||||
|
CSP_EXEMPT = (
|
||||||
|
'/api/v1/docs/',
|
||||||
|
)
|
||||||
|
|
||||||
|
def process_request(self, request):
|
||||||
|
request.csp_nonce = get_random_string(length=32)
|
||||||
|
|
||||||
def process_response(self, request, resp):
|
def process_response(self, request, resp):
|
||||||
if settings.DEBUG and resp.status_code >= 400:
|
if settings.DEBUG and resp.status_code >= 400:
|
||||||
@@ -173,9 +183,9 @@ class SecurityMiddleware(MiddlewareMixin):
|
|||||||
# frame-src is deprecated but kept for compatibility with CSP 1.0 browsers, e.g. Safari 9
|
# frame-src is deprecated but kept for compatibility with CSP 1.0 browsers, e.g. Safari 9
|
||||||
'frame-src': ['{static}', 'https://checkout.stripe.com', 'https://js.stripe.com'],
|
'frame-src': ['{static}', 'https://checkout.stripe.com', 'https://js.stripe.com'],
|
||||||
'child-src': ['{static}', 'https://checkout.stripe.com', 'https://js.stripe.com'],
|
'child-src': ['{static}', 'https://checkout.stripe.com', 'https://js.stripe.com'],
|
||||||
'style-src': ["{static}"],
|
'style-src': ["{static}", "'nonce-{nonce}'"],
|
||||||
'connect-src': ["{dynamic}", "https://checkout.stripe.com"],
|
'connect-src': ["{dynamic}", "https://checkout.stripe.com"],
|
||||||
'img-src': ["{static}", "data:", "https://*.stripe.com"],
|
'img-src': ["{static}", "{media}", "data:", "https://*.stripe.com"],
|
||||||
# form-action is not only used to match on form actions, but also on URLs
|
# form-action is not only used to match on form actions, but also on URLs
|
||||||
# form-actions redirect to. In the context of e.g. payment providers or
|
# form-actions redirect to. In the context of e.g. payment providers or
|
||||||
# single-sign-on this can be nearly anything so we cannot really restrict
|
# single-sign-on this can be nearly anything so we cannot really restrict
|
||||||
@@ -187,6 +197,9 @@ class SecurityMiddleware(MiddlewareMixin):
|
|||||||
|
|
||||||
staticdomain = "'self'"
|
staticdomain = "'self'"
|
||||||
dynamicdomain = "'self'"
|
dynamicdomain = "'self'"
|
||||||
|
mediadomain = "'self'"
|
||||||
|
if settings.MEDIA_URL.startswith('http'):
|
||||||
|
mediadomain += " " + settings.MEDIA_URL[:settings.MEDIA_URL.find('/', 9)]
|
||||||
if settings.STATIC_URL.startswith('http'):
|
if settings.STATIC_URL.startswith('http'):
|
||||||
staticdomain += " " + settings.STATIC_URL[:settings.STATIC_URL.find('/', 9)]
|
staticdomain += " " + settings.STATIC_URL[:settings.STATIC_URL.find('/', 9)]
|
||||||
if settings.SITE_URL.startswith('http'):
|
if settings.SITE_URL.startswith('http'):
|
||||||
@@ -196,5 +209,16 @@ class SecurityMiddleware(MiddlewareMixin):
|
|||||||
else:
|
else:
|
||||||
staticdomain += " " + settings.SITE_URL
|
staticdomain += " " + settings.SITE_URL
|
||||||
dynamicdomain += " " + settings.SITE_URL
|
dynamicdomain += " " + settings.SITE_URL
|
||||||
resp['Content-Security-Policy'] = _render_csp(h).format(static=staticdomain, dynamic=dynamicdomain)
|
|
||||||
|
if hasattr(request, 'organizer') and request.organizer:
|
||||||
|
domain = get_domain(request.organizer)
|
||||||
|
if domain:
|
||||||
|
siteurlsplit = urlsplit(settings.SITE_URL)
|
||||||
|
if siteurlsplit.port and siteurlsplit.port not in (80, 443):
|
||||||
|
domain = '%s:%d' % (domain, siteurlsplit.port)
|
||||||
|
dynamicdomain += " " + domain
|
||||||
|
|
||||||
|
if request.path not in self.CSP_EXEMPT:
|
||||||
|
resp['Content-Security-Policy'] = _render_csp(h).format(static=staticdomain, dynamic=dynamicdomain,
|
||||||
|
media=mediadomain, nonce=request.csp_nonce)
|
||||||
return resp
|
return resp
|
||||||
|
|||||||
27
src/pretix/base/migrations/0061_auto_20170521_0942.py
Normal file
27
src/pretix/base/migrations/0061_auto_20170521_0942.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.7 on 2017-05-21 09:42
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.core.cache import cache
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
def rename_placeholder(app, schema_editor):
|
||||||
|
EventSettingsStore = app.get_model('pretixbase', 'Event_SettingsStore')
|
||||||
|
|
||||||
|
for setting in EventSettingsStore.objects.all():
|
||||||
|
if setting.key == 'mail_text_order_placed':
|
||||||
|
new_value = setting.value.replace('{paymentinfo}', '{payment_info}')
|
||||||
|
setting.value = new_value
|
||||||
|
cache.delete('hierarkey_{}_{}'.format('event', setting.object_id))
|
||||||
|
setting.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('pretixbase', '0060_auto_20170510_1027'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(rename_placeholder, migrations.RunPython.noop)
|
||||||
|
]
|
||||||
28
src/pretix/base/migrations/0062_auto_20170602_0948.py
Normal file
28
src/pretix/base/migrations/0062_auto_20170602_0948.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.2 on 2017-06-02 09:48
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
import pretix.base.models.organizer
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('pretixbase', '0061_auto_20170521_0942'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='TeamAPIToken',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=190)),
|
||||||
|
('active', models.BooleanField(default=True)),
|
||||||
|
('token', models.CharField(default=pretix.base.models.organizer.generate_api_token, max_length=64)),
|
||||||
|
('team', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tokens', to='pretixbase.Team')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -67,7 +67,9 @@ class Event(LoggedModel):
|
|||||||
max_length=50, db_index=True,
|
max_length=50, db_index=True,
|
||||||
help_text=_(
|
help_text=_(
|
||||||
"Should be short, only contain lowercase letters and numbers, and must be unique among your events. "
|
"Should be short, only contain lowercase letters and numbers, and must be unique among your events. "
|
||||||
"This will be used in order codes, invoice numbers, links and bank transfer references."),
|
"We recommend some kind of abbreviation or a date with less than 10 characters that can be easily "
|
||||||
|
"remembered, but you can also choose to use a random value. "
|
||||||
|
"This will be used in URLs, order codes, invoice numbers, and bank transfer references."),
|
||||||
validators=[
|
validators=[
|
||||||
RegexValidator(
|
RegexValidator(
|
||||||
regex="^[a-zA-Z0-9.-]+$",
|
regex="^[a-zA-Z0-9.-]+$",
|
||||||
@@ -311,6 +313,19 @@ class Event(LoggedModel):
|
|||||||
|
|
||||||
event_copy_data.send(sender=self, other=other)
|
event_copy_data.send(sender=self, other=other)
|
||||||
|
|
||||||
|
def get_payment_providers(self) -> dict:
|
||||||
|
from ..signals import register_payment_providers
|
||||||
|
|
||||||
|
responses = register_payment_providers.send(self)
|
||||||
|
providers = {}
|
||||||
|
for receiver, response in responses:
|
||||||
|
if not isinstance(response, list):
|
||||||
|
response = [response]
|
||||||
|
for p in response:
|
||||||
|
pp = p(self)
|
||||||
|
providers[pp.identifier] = pp
|
||||||
|
return providers
|
||||||
|
|
||||||
|
|
||||||
def generate_invite_token():
|
def generate_invite_token():
|
||||||
return get_random_string(length=32, allowed_chars=string.ascii_lowercase + string.digits)
|
return get_random_string(length=32, allowed_chars=string.ascii_lowercase + string.digits)
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import string
|
import string
|
||||||
from datetime import date
|
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
from django.db import DatabaseError, models, transaction
|
from django.db import DatabaseError, models, transaction
|
||||||
|
from django.utils import timezone
|
||||||
from django.utils.crypto import get_random_string
|
from django.utils.crypto import get_random_string
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
|
|
||||||
@@ -15,6 +15,10 @@ def invoice_filename(instance, filename: str) -> str:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def today():
|
||||||
|
return timezone.now().date()
|
||||||
|
|
||||||
|
|
||||||
class Invoice(models.Model):
|
class Invoice(models.Model):
|
||||||
"""
|
"""
|
||||||
Represents an invoice that is issued because of an order. Because invoices are legally required
|
Represents an invoice that is issued because of an order. Because invoices are legally required
|
||||||
@@ -56,7 +60,7 @@ class Invoice(models.Model):
|
|||||||
refers = models.ForeignKey('Invoice', related_name='refered', null=True, blank=True)
|
refers = models.ForeignKey('Invoice', related_name='refered', null=True, blank=True)
|
||||||
invoice_from = models.TextField()
|
invoice_from = models.TextField()
|
||||||
invoice_to = models.TextField()
|
invoice_to = models.TextField()
|
||||||
date = models.DateField(default=date.today)
|
date = models.DateField(default=today)
|
||||||
locale = models.CharField(max_length=50, default='en')
|
locale = models.CharField(max_length=50, default='en')
|
||||||
introductory_text = models.TextField(blank=True)
|
introductory_text = models.TextField(blank=True)
|
||||||
additional_text = models.TextField(blank=True)
|
additional_text = models.TextField(blank=True)
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ class Item(LoggedModel):
|
|||||||
allow_cancel = models.BooleanField(
|
allow_cancel = models.BooleanField(
|
||||||
verbose_name=_('Allow product to be canceled'),
|
verbose_name=_('Allow product to be canceled'),
|
||||||
default=True,
|
default=True,
|
||||||
help_text=_('If this is active and the general event settings allo wit, orders containing this product can be '
|
help_text=_('If this is active and the general event settings allow it, orders containing this product can be '
|
||||||
'canceled by the user until the order is paid for. Users cannot cancel paid orders on their own '
|
'canceled by the user until the order is paid for. Users cannot cancel paid orders on their own '
|
||||||
'and you can cancel orders at all times, regardless of this setting')
|
'and you can cancel orders at all times, regardless of this setting')
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from django.contrib.contenttypes.models import ContentType
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
|
from django.utils.html import escape
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
@@ -66,7 +67,7 @@ class LogEntry(models.Model):
|
|||||||
'organizer': self.event.organizer.slug,
|
'organizer': self.event.organizer.slug,
|
||||||
'code': co.code
|
'code': co.code
|
||||||
}),
|
}),
|
||||||
'val': co.code,
|
'val': escape(co.code),
|
||||||
}
|
}
|
||||||
elif isinstance(co, Voucher):
|
elif isinstance(co, Voucher):
|
||||||
a_text = _('Voucher {val}…')
|
a_text = _('Voucher {val}…')
|
||||||
@@ -76,7 +77,7 @@ class LogEntry(models.Model):
|
|||||||
'organizer': self.event.organizer.slug,
|
'organizer': self.event.organizer.slug,
|
||||||
'voucher': co.id
|
'voucher': co.id
|
||||||
}),
|
}),
|
||||||
'val': co.code[:6],
|
'val': escape(co.code[:6]),
|
||||||
}
|
}
|
||||||
elif isinstance(co, Item):
|
elif isinstance(co, Item):
|
||||||
a_text = _('Product {val}')
|
a_text = _('Product {val}')
|
||||||
@@ -86,7 +87,7 @@ class LogEntry(models.Model):
|
|||||||
'organizer': self.event.organizer.slug,
|
'organizer': self.event.organizer.slug,
|
||||||
'item': co.id
|
'item': co.id
|
||||||
}),
|
}),
|
||||||
'val': co.name,
|
'val': escape(co.name),
|
||||||
}
|
}
|
||||||
elif isinstance(co, Quota):
|
elif isinstance(co, Quota):
|
||||||
a_text = _('Quota {val}')
|
a_text = _('Quota {val}')
|
||||||
@@ -96,7 +97,7 @@ class LogEntry(models.Model):
|
|||||||
'organizer': self.event.organizer.slug,
|
'organizer': self.event.organizer.slug,
|
||||||
'quota': co.id
|
'quota': co.id
|
||||||
}),
|
}),
|
||||||
'val': co.name,
|
'val': escape(co.name),
|
||||||
}
|
}
|
||||||
elif isinstance(co, ItemCategory):
|
elif isinstance(co, ItemCategory):
|
||||||
a_text = _('Category {val}')
|
a_text = _('Category {val}')
|
||||||
@@ -106,7 +107,7 @@ class LogEntry(models.Model):
|
|||||||
'organizer': self.event.organizer.slug,
|
'organizer': self.event.organizer.slug,
|
||||||
'category': co.id
|
'category': co.id
|
||||||
}),
|
}),
|
||||||
'val': co.name,
|
'val': escape(co.name),
|
||||||
}
|
}
|
||||||
elif isinstance(co, Question):
|
elif isinstance(co, Question):
|
||||||
a_text = _('Question {val}')
|
a_text = _('Question {val}')
|
||||||
@@ -116,7 +117,7 @@ class LogEntry(models.Model):
|
|||||||
'organizer': self.event.organizer.slug,
|
'organizer': self.event.organizer.slug,
|
||||||
'question': co.id
|
'question': co.id
|
||||||
}),
|
}),
|
||||||
'val': co.question,
|
'val': escape(co.question),
|
||||||
}
|
}
|
||||||
|
|
||||||
if a_text and a_map:
|
if a_text and a_map:
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import copy
|
import copy
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
import string
|
import string
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
@@ -183,6 +184,10 @@ class Order(LoggedModel):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.full_code
|
return self.full_code
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def meta_info_data(self):
|
||||||
|
return json.loads(self.meta_info)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def full_code(self):
|
def full_code(self):
|
||||||
"""
|
"""
|
||||||
@@ -305,7 +310,7 @@ class Order(LoggedModel):
|
|||||||
|
|
||||||
def _is_still_available(self, now_dt: datetime=None) -> Union[bool, str]:
|
def _is_still_available(self, now_dt: datetime=None) -> Union[bool, str]:
|
||||||
error_messages = {
|
error_messages = {
|
||||||
'unavailable': _('Some of the ordered products are no longer available.'),
|
'unavailable': _('The ordered product "{item}" is no longer available.'),
|
||||||
}
|
}
|
||||||
now_dt = now_dt or now()
|
now_dt = now_dt or now()
|
||||||
positions = self.positions.all().select_related('item', 'variation')
|
positions = self.positions.all().select_related('item', 'variation')
|
||||||
@@ -327,7 +332,9 @@ class Order(LoggedModel):
|
|||||||
quota.cached_availability -= 1
|
quota.cached_availability -= 1
|
||||||
if quota.cached_availability < 0:
|
if quota.cached_availability < 0:
|
||||||
# This quota is sold out/currently unavailable, so do not sell this at all
|
# This quota is sold out/currently unavailable, so do not sell this at all
|
||||||
raise Quota.QuotaExceededException(error_messages['unavailable'])
|
raise Quota.QuotaExceededException(error_messages['unavailable'].format(
|
||||||
|
item=str(op.item) + (' - ' + str(op.variation) if op.variation else '')
|
||||||
|
))
|
||||||
except Quota.QuotaExceededException as e:
|
except Quota.QuotaExceededException as e:
|
||||||
return str(e)
|
return str(e)
|
||||||
return True
|
return True
|
||||||
@@ -491,6 +498,10 @@ class OrderPosition(AbstractPosition):
|
|||||||
verbose_name_plural = _("Order positions")
|
verbose_name_plural = _("Order positions")
|
||||||
ordering = ("positionid", "id")
|
ordering = ("positionid", "id")
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def sort_key(self):
|
||||||
|
return self.addon_to.positionid if self.addon_to else self.positionid, self.addon_to_id or 0
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def transform_cart_positions(cls, cp: List, order) -> list:
|
def transform_cart_positions(cls, cp: List, order) -> list:
|
||||||
from . import Voucher
|
from . import Voucher
|
||||||
@@ -522,6 +533,13 @@ class OrderPosition(AbstractPosition):
|
|||||||
cartpos.delete()
|
cartpos.delete()
|
||||||
return ops
|
return ops
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if self.variation:
|
||||||
|
return '#{} – {} – {}'.format(
|
||||||
|
self.positionid, str(self.item), str(self.variation)
|
||||||
|
)
|
||||||
|
return '#{} – {}'.format(self.positionid, str(self.item))
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<OrderPosition: item %d, variation %d for order %s>' % (
|
return '<OrderPosition: item %d, variation %d for order %s>' % (
|
||||||
self.item.id, self.variation.id if self.variation else 0, self.order_id
|
self.item.id, self.variation.id if self.variation else 0, self.order_id
|
||||||
|
|||||||
@@ -72,6 +72,10 @@ def generate_invite_token():
|
|||||||
return get_random_string(length=32, allowed_chars=string.ascii_lowercase + string.digits)
|
return get_random_string(length=32, allowed_chars=string.ascii_lowercase + string.digits)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_api_token():
|
||||||
|
return get_random_string(length=64, allowed_chars=string.ascii_lowercase + string.digits)
|
||||||
|
|
||||||
|
|
||||||
class Team(LoggedModel):
|
class Team(LoggedModel):
|
||||||
"""
|
"""
|
||||||
A team is a collection of people given certain access rights to one or more events of an organizer.
|
A team is a collection of people given certain access rights to one or more events of an organizer.
|
||||||
@@ -175,6 +179,10 @@ class Team(LoggedModel):
|
|||||||
else:
|
else:
|
||||||
return self.limit_events.filter(pk=event.pk).exists()
|
return self.limit_events.filter(pk=event.pk).exists()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def active_tokens(self):
|
||||||
|
return self.tokens.filter(active=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("Team")
|
verbose_name = _("Team")
|
||||||
verbose_name_plural = _("Teams")
|
verbose_name_plural = _("Teams")
|
||||||
@@ -200,3 +208,81 @@ class TeamInvite(models.Model):
|
|||||||
return _("Invite to team '{team}' for '{email}'").format(
|
return _("Invite to team '{team}' for '{email}'").format(
|
||||||
team=str(self.team), email=self.email
|
team=str(self.team), email=self.email
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TeamAPIToken(models.Model):
|
||||||
|
"""
|
||||||
|
A TeamAPIToken represents an API token that has the same access level as the team it belongs to.
|
||||||
|
|
||||||
|
:param team: The team the person is invited to
|
||||||
|
:type team: Team
|
||||||
|
:param name: A human-readable name for the token
|
||||||
|
:type name: str
|
||||||
|
:param active: Whether or not this token is active
|
||||||
|
:type active: bool
|
||||||
|
:param token: The secret required to submit to the API
|
||||||
|
:type token: str
|
||||||
|
"""
|
||||||
|
team = models.ForeignKey(Team, related_name="tokens", on_delete=models.CASCADE)
|
||||||
|
name = models.CharField(max_length=190)
|
||||||
|
active = models.BooleanField(default=True)
|
||||||
|
token = models.CharField(default=generate_api_token, max_length=64)
|
||||||
|
|
||||||
|
def get_event_permission_set(self, organizer, event) -> set:
|
||||||
|
"""
|
||||||
|
Gets a set of permissions (as strings) that a token holds for a particular event
|
||||||
|
|
||||||
|
:param organizer: The organizer of the event
|
||||||
|
:param event: The event to check
|
||||||
|
:return: set of permissions
|
||||||
|
"""
|
||||||
|
has_event_access = (self.team.all_events and organizer == self.team.organizer) or (
|
||||||
|
event in self.team.limit_events.all()
|
||||||
|
)
|
||||||
|
return self.team.permission_set() if has_event_access else set()
|
||||||
|
|
||||||
|
def get_organizer_permission_set(self, organizer) -> set:
|
||||||
|
"""
|
||||||
|
Gets a set of permissions (as strings) that a token holds for a particular organizer
|
||||||
|
|
||||||
|
:param organizer: The organizer of the event
|
||||||
|
:return: set of permissions
|
||||||
|
"""
|
||||||
|
return self.team.permission_set() if self.team.organizer == organizer else set()
|
||||||
|
|
||||||
|
def has_event_permission(self, organizer, event, perm_name=None) -> bool:
|
||||||
|
"""
|
||||||
|
Checks if this token is part of a team that grants access of type ``perm_name``
|
||||||
|
to the event ``event``.
|
||||||
|
|
||||||
|
:param organizer: The organizer of the event
|
||||||
|
:param event: The event to check
|
||||||
|
:param perm_name: The permission, e.g. ``can_change_teams``
|
||||||
|
:return: bool
|
||||||
|
"""
|
||||||
|
has_event_access = (self.team.all_events and organizer == self.team.organizer) or (
|
||||||
|
event in self.team.limit_events.all()
|
||||||
|
)
|
||||||
|
return has_event_access and (not perm_name or self.team.has_permission(perm_name))
|
||||||
|
|
||||||
|
def has_organizer_permission(self, organizer, perm_name=None):
|
||||||
|
"""
|
||||||
|
Checks if this token is part of a team that grants access of type ``perm_name``
|
||||||
|
to the organizer ``organizer``.
|
||||||
|
|
||||||
|
:param organizer: The organizer to check
|
||||||
|
:param perm_name: The permission, e.g. ``can_change_teams``
|
||||||
|
:return: bool
|
||||||
|
"""
|
||||||
|
return organizer == self.team.organizer and (not perm_name or self.team.has_permission(perm_name))
|
||||||
|
|
||||||
|
def get_events_with_any_permission(self):
|
||||||
|
"""
|
||||||
|
Returns a queryset of events the token has any permissions to.
|
||||||
|
|
||||||
|
:return: Iterable of Events
|
||||||
|
"""
|
||||||
|
if self.team.all_events:
|
||||||
|
return self.team.organizer.events.all()
|
||||||
|
else:
|
||||||
|
return self.team.limit_events.all()
|
||||||
|
|||||||
@@ -13,14 +13,16 @@ from .event import Event
|
|||||||
from .items import Item, ItemVariation, Quota
|
from .items import Item, ItemVariation, Quota
|
||||||
|
|
||||||
|
|
||||||
def _generate_random_code():
|
def _generate_random_code(prefix=None):
|
||||||
charset = list('ABCDEFGHKLMNPQRSTUVWXYZ23456789')
|
charset = list('ABCDEFGHKLMNPQRSTUVWXYZ23456789')
|
||||||
|
if prefix:
|
||||||
|
return prefix + get_random_string(length=settings.ENTROPY['voucher_code'], allowed_chars=charset)
|
||||||
return get_random_string(length=settings.ENTROPY['voucher_code'], allowed_chars=charset)
|
return get_random_string(length=settings.ENTROPY['voucher_code'], allowed_chars=charset)
|
||||||
|
|
||||||
|
|
||||||
def generate_code():
|
def generate_code(prefix=None):
|
||||||
while True:
|
while True:
|
||||||
code = _generate_random_code()
|
code = _generate_random_code(prefix=prefix)
|
||||||
if not Voucher.objects.filter(code=code).exists():
|
if not Voucher.objects.filter(code=code).exists():
|
||||||
return code
|
return code
|
||||||
|
|
||||||
@@ -163,6 +165,7 @@ class Voucher(LoggedModel):
|
|||||||
verbose_name = _("Voucher")
|
verbose_name = _("Voucher")
|
||||||
verbose_name_plural = _("Vouchers")
|
verbose_name_plural = _("Vouchers")
|
||||||
unique_together = (("event", "code"),)
|
unique_together = (("event", "code"),)
|
||||||
|
ordering = ('code', )
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.code
|
return self.code
|
||||||
|
|||||||
@@ -80,6 +80,16 @@ class BasePaymentProvider:
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError() # NOQA
|
raise NotImplementedError() # NOQA
|
||||||
|
|
||||||
|
@property
|
||||||
|
def public_name(self) -> str:
|
||||||
|
"""
|
||||||
|
A human-readable name for this payment provider to be shown to the public.
|
||||||
|
This should be short but self-explaining. Good examples include 'Bank transfer'
|
||||||
|
and 'Credit card', but 'Credit card via Stripe' might be to explicit. By default,
|
||||||
|
this is the same as ``verbose_name``
|
||||||
|
"""
|
||||||
|
return self.verbose_name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def identifier(self) -> str:
|
def identifier(self) -> str:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import copy
|
import copy
|
||||||
import tempfile
|
import tempfile
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from datetime import date
|
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
from django.contrib.staticfiles import finders
|
from django.contrib.staticfiles import finders
|
||||||
from django.core.files.base import ContentFile
|
from django.core.files.base import ContentFile
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
from django.utils import timezone
|
||||||
from django.utils.formats import date_format, localize
|
from django.utils.formats import date_format, localize
|
||||||
from django.utils.timezone import now
|
|
||||||
from django.utils.translation import pgettext, ugettext as _
|
from django.utils.translation import pgettext, ugettext as _
|
||||||
from i18nfield.strings import LazyI18nString
|
from i18nfield.strings import LazyI18nString
|
||||||
from reportlab.lib import pagesizes
|
from reportlab.lib import pagesizes
|
||||||
@@ -25,7 +24,6 @@ from reportlab.platypus import (
|
|||||||
from pretix.base.i18n import language
|
from pretix.base.i18n import language
|
||||||
from pretix.base.models import Invoice, InvoiceAddress, InvoiceLine, Order
|
from pretix.base.models import Invoice, InvoiceAddress, InvoiceLine, Order
|
||||||
from pretix.base.services.async import TransactionAwareTask
|
from pretix.base.services.async import TransactionAwareTask
|
||||||
from pretix.base.signals import register_payment_providers
|
|
||||||
from pretix.celery_app import app
|
from pretix.celery_app import app
|
||||||
from pretix.helpers.database import rolledback_transaction
|
from pretix.helpers.database import rolledback_transaction
|
||||||
|
|
||||||
@@ -33,12 +31,7 @@ from pretix.helpers.database import rolledback_transaction
|
|||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def build_invoice(invoice: Invoice) -> Invoice:
|
def build_invoice(invoice: Invoice) -> Invoice:
|
||||||
with language(invoice.locale):
|
with language(invoice.locale):
|
||||||
responses = register_payment_providers.send(invoice.event)
|
payment_provider = invoice.event.get_payment_providers().get(invoice.order.payment_provider)
|
||||||
for receiver, response in responses:
|
|
||||||
provider = response(invoice.event)
|
|
||||||
if provider.identifier == invoice.order.payment_provider:
|
|
||||||
payment_provider = provider
|
|
||||||
break
|
|
||||||
|
|
||||||
invoice.invoice_from = invoice.event.settings.get('invoice_address_from')
|
invoice.invoice_from = invoice.event.settings.get('invoice_address_from')
|
||||||
|
|
||||||
@@ -68,7 +61,9 @@ def build_invoice(invoice: Invoice) -> Invoice:
|
|||||||
invoice.save()
|
invoice.save()
|
||||||
invoice.lines.all().delete()
|
invoice.lines.all().delete()
|
||||||
|
|
||||||
for p in invoice.order.positions.all():
|
positions = list(invoice.order.positions.select_related('addon_to', 'item', 'variation'))
|
||||||
|
positions.sort(key=lambda p: p.sort_key)
|
||||||
|
for p in positions:
|
||||||
desc = str(p.item.name)
|
desc = str(p.item.name)
|
||||||
if p.variation:
|
if p.variation:
|
||||||
desc += " - " + str(p.variation.value)
|
desc += " - " + str(p.variation.value)
|
||||||
@@ -108,7 +103,7 @@ def generate_cancellation(invoice: Invoice):
|
|||||||
cancellation.invoice_no = None
|
cancellation.invoice_no = None
|
||||||
cancellation.refers = invoice
|
cancellation.refers = invoice
|
||||||
cancellation.is_cancellation = True
|
cancellation.is_cancellation = True
|
||||||
cancellation.date = date.today()
|
cancellation.date = timezone.now().date()
|
||||||
cancellation.payment_provider_text = ''
|
cancellation.payment_provider_text = ''
|
||||||
cancellation.save()
|
cancellation.save()
|
||||||
|
|
||||||
@@ -135,7 +130,7 @@ def generate_invoice(order: Order):
|
|||||||
invoice = Invoice(
|
invoice = Invoice(
|
||||||
order=order,
|
order=order,
|
||||||
event=order.event,
|
event=order.event,
|
||||||
date=date.today(),
|
date=timezone.now().date(),
|
||||||
locale=locale
|
locale=locale
|
||||||
)
|
)
|
||||||
invoice = build_invoice(invoice)
|
invoice = build_invoice(invoice)
|
||||||
@@ -430,11 +425,11 @@ def build_preview_invoice_pdf(event):
|
|||||||
locale = event.settings.locale
|
locale = event.settings.locale
|
||||||
|
|
||||||
with rolledback_transaction(), language(locale):
|
with rolledback_transaction(), language(locale):
|
||||||
order = event.orders.create(status=Order.STATUS_PENDING, datetime=now(),
|
order = event.orders.create(status=Order.STATUS_PENDING, datetime=timezone.now(),
|
||||||
expires=now(), code="PREVIEW", total=119)
|
expires=timezone.now(), code="PREVIEW", total=119)
|
||||||
invoice = Invoice(
|
invoice = Invoice(
|
||||||
order=order, event=event, invoice_no="PREVIEW",
|
order=order, event=event, invoice_no="PREVIEW",
|
||||||
date=date.today(), locale=locale
|
date=timezone.now().date(), locale=locale
|
||||||
)
|
)
|
||||||
invoice.invoice_from = event.settings.get('invoice_address_from')
|
invoice.invoice_from = event.settings.get('invoice_address_from')
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import pytz
|
|||||||
from celery.exceptions import MaxRetriesExceededError
|
from celery.exceptions import MaxRetriesExceededError
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import F, Q
|
from django.db.models import F, Max, Q
|
||||||
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.timezone import make_aware, now
|
from django.utils.timezone import make_aware, now
|
||||||
@@ -30,9 +30,7 @@ from pretix.base.services.invoices import (
|
|||||||
)
|
)
|
||||||
from pretix.base.services.locking import LockTimeoutException
|
from pretix.base.services.locking import LockTimeoutException
|
||||||
from pretix.base.services.mail import SendMailException, mail
|
from pretix.base.services.mail import SendMailException, mail
|
||||||
from pretix.base.signals import (
|
from pretix.base.signals import order_paid, order_placed, periodic_task
|
||||||
order_paid, order_placed, periodic_task, register_payment_providers,
|
|
||||||
)
|
|
||||||
from pretix.celery_app import app
|
from pretix.celery_app import app
|
||||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||||
|
|
||||||
@@ -366,12 +364,7 @@ def _perform_order(event: str, payment_provider: str, position_ids: List[str],
|
|||||||
email: str, locale: str, address: int, meta_info: dict=None):
|
email: str, locale: str, address: int, meta_info: dict=None):
|
||||||
|
|
||||||
event = Event.objects.get(id=event)
|
event = Event.objects.get(id=event)
|
||||||
responses = register_payment_providers.send(event)
|
pprov = event.get_payment_providers().get(payment_provider)
|
||||||
pprov = None
|
|
||||||
for rec, response in responses:
|
|
||||||
provider = response(event)
|
|
||||||
if provider.identifier == payment_provider:
|
|
||||||
pprov = provider
|
|
||||||
if not pprov:
|
if not pprov:
|
||||||
raise OrderError(error_messages['internal'])
|
raise OrderError(error_messages['internal'])
|
||||||
|
|
||||||
@@ -414,7 +407,7 @@ def _perform_order(event: str, payment_provider: str, position_ids: List[str],
|
|||||||
'order': order.code,
|
'order': order.code,
|
||||||
'secret': order.secret
|
'secret': order.secret
|
||||||
}),
|
}),
|
||||||
'paymentinfo': str(pprov.order_pending_mail_render(order)),
|
'payment_info': str(pprov.order_pending_mail_render(order)),
|
||||||
'invoice_name': invoice_name,
|
'invoice_name': invoice_name,
|
||||||
'invoice_company': invoice_company,
|
'invoice_company': invoice_company,
|
||||||
},
|
},
|
||||||
@@ -495,11 +488,14 @@ class OrderChangeManager:
|
|||||||
'paid_to_free_exceeded': _('This operation would make the order free and therefore immediately paid, however '
|
'paid_to_free_exceeded': _('This operation would make the order free and therefore immediately paid, however '
|
||||||
'no quota is available.'),
|
'no quota is available.'),
|
||||||
'paid_price_change': _('Currently, paid orders can only be changed in a way that does not change the total '
|
'paid_price_change': _('Currently, paid orders can only be changed in a way that does not change the total '
|
||||||
'price of the order as partial payments or refunds are not yet supported.')
|
'price of the order as partial payments or refunds are not yet supported.'),
|
||||||
|
'addon_to_required': _('This is an addon product, please select the base position it should be added to.'),
|
||||||
|
'addon_invalid': _('The selected base position does not allow you to add this product as an add-on.'),
|
||||||
}
|
}
|
||||||
ItemOperation = namedtuple('ItemOperation', ('position', 'item', 'variation', 'price'))
|
ItemOperation = namedtuple('ItemOperation', ('position', 'item', 'variation', 'price'))
|
||||||
PriceOperation = namedtuple('PriceOperation', ('position', 'price'))
|
PriceOperation = namedtuple('PriceOperation', ('position', 'price'))
|
||||||
CancelOperation = namedtuple('CancelOperation', ('position',))
|
CancelOperation = namedtuple('CancelOperation', ('position',))
|
||||||
|
AddOperation = namedtuple('AddOperation', ('item', 'variation', 'price', 'addon_to'))
|
||||||
|
|
||||||
def __init__(self, order: Order, user):
|
def __init__(self, order: Order, user):
|
||||||
self.order = order
|
self.order = order
|
||||||
@@ -512,7 +508,7 @@ class OrderChangeManager:
|
|||||||
if (not variation and item.has_variations) or (variation and variation.item_id != item.pk):
|
if (not variation and item.has_variations) or (variation and variation.item_id != item.pk):
|
||||||
raise OrderError(self.error_messages['product_without_variation'])
|
raise OrderError(self.error_messages['product_without_variation'])
|
||||||
price = item.default_price if variation is None else variation.price
|
price = item.default_price if variation is None else variation.price
|
||||||
if not price:
|
if price is None:
|
||||||
raise OrderError(self.error_messages['product_invalid'])
|
raise OrderError(self.error_messages['product_invalid'])
|
||||||
self._totaldiff = price - position.price
|
self._totaldiff = price - position.price
|
||||||
self._quotadiff.update(variation.quotas.all() if variation else item.quotas.all())
|
self._quotadiff.update(variation.quotas.all() if variation else item.quotas.all())
|
||||||
@@ -528,6 +524,21 @@ class OrderChangeManager:
|
|||||||
self._quotadiff.subtract(position.variation.quotas.all() if position.variation else position.item.quotas.all())
|
self._quotadiff.subtract(position.variation.quotas.all() if position.variation else position.item.quotas.all())
|
||||||
self._operations.append(self.CancelOperation(position))
|
self._operations.append(self.CancelOperation(position))
|
||||||
|
|
||||||
|
def add_position(self, item: Item, variation: ItemVariation, price: Decimal, addon_to: Order):
|
||||||
|
if price is None:
|
||||||
|
price = item.default_price if variation is None else variation.price
|
||||||
|
if price is None:
|
||||||
|
raise OrderError(self.error_messages['product_invalid'])
|
||||||
|
if not addon_to and item.category and item.category.is_addon:
|
||||||
|
raise OrderError(self.error_messages['addon_to_required'])
|
||||||
|
if addon_to:
|
||||||
|
if not item.category or item.category_id not in addon_to.item.addons.values_list('addon_category', flat=True):
|
||||||
|
raise OrderError(self.error_messages['addon_invalid'])
|
||||||
|
|
||||||
|
self._totaldiff = price
|
||||||
|
self._quotadiff.update(variation.quotas.all() if variation else item.quotas.all())
|
||||||
|
self._operations.append(self.AddOperation(item, variation, price, addon_to))
|
||||||
|
|
||||||
def _check_quotas(self):
|
def _check_quotas(self):
|
||||||
for quota, diff in self._quotadiff.items():
|
for quota, diff in self._quotadiff.items():
|
||||||
if diff <= 0:
|
if diff <= 0:
|
||||||
@@ -552,6 +563,7 @@ class OrderChangeManager:
|
|||||||
raise OrderError(self.error_messages['paid_to_free_exceeded'])
|
raise OrderError(self.error_messages['paid_to_free_exceeded'])
|
||||||
|
|
||||||
def _perform_operations(self):
|
def _perform_operations(self):
|
||||||
|
nextposid = self.order.positions.aggregate(m=Max('positionid'))['m'] + 1
|
||||||
for op in self._operations:
|
for op in self._operations:
|
||||||
if isinstance(op, self.ItemOperation):
|
if isinstance(op, self.ItemOperation):
|
||||||
self.order.log_action('pretix.event.order.changed.item', user=self.user, data={
|
self.order.log_action('pretix.event.order.changed.item', user=self.user, data={
|
||||||
@@ -600,13 +612,29 @@ class OrderChangeManager:
|
|||||||
'addon_to': None,
|
'addon_to': None,
|
||||||
})
|
})
|
||||||
op.position.delete()
|
op.position.delete()
|
||||||
|
elif isinstance(op, self.AddOperation):
|
||||||
|
pos = OrderPosition.objects.create(
|
||||||
|
item=op.item, variation=op.variation, addon_to=op.addon_to,
|
||||||
|
price=op.price, order=self.order,
|
||||||
|
positionid=nextposid
|
||||||
|
)
|
||||||
|
nextposid += 1
|
||||||
|
self.order.log_action('pretix.event.order.changed.add', user=self.user, data={
|
||||||
|
'position': pos.pk,
|
||||||
|
'item': op.item.pk,
|
||||||
|
'variation': op.variation.pk if op.variation else None,
|
||||||
|
'addon_to': op.addon_to.pk if op.addon_to else None,
|
||||||
|
'price': op.price,
|
||||||
|
'positionid': pos.positionid
|
||||||
|
})
|
||||||
|
|
||||||
def _recalculate_total_and_payment_fee(self):
|
def _recalculate_total_and_payment_fee(self):
|
||||||
self.order.total = sum([p.price for p in self.order.positions.all()])
|
self.order.total = sum([p.price for p in self.order.positions.all()])
|
||||||
if self.order.total == 0:
|
payment_fee = Decimal('0.00')
|
||||||
payment_fee = Decimal('0.00')
|
if self.order.total != 0:
|
||||||
else:
|
prov = self._get_payment_provider()
|
||||||
payment_fee = self._get_payment_provider().calculate_fee(self.order.total)
|
if prov:
|
||||||
|
payment_fee = prov.calculate_fee(self.order.total)
|
||||||
self.order.payment_fee = payment_fee
|
self.order.payment_fee = payment_fee
|
||||||
self.order.total += payment_fee
|
self.order.total += payment_fee
|
||||||
self.order._calculate_tax()
|
self.order._calculate_tax()
|
||||||
@@ -669,14 +697,10 @@ class OrderChangeManager:
|
|||||||
CachedTicket.objects.filter(order_position__order=self.order).delete()
|
CachedTicket.objects.filter(order_position__order=self.order).delete()
|
||||||
|
|
||||||
def _get_payment_provider(self):
|
def _get_payment_provider(self):
|
||||||
responses = register_payment_providers.send(self.order.event)
|
pprov = self.order.event.get_payment_providers().get(self.order.payment_provider)
|
||||||
pprov = None
|
|
||||||
for rec, response in responses:
|
|
||||||
provider = response(self.order.event)
|
|
||||||
if provider.identifier == self.order.payment_provider:
|
|
||||||
return provider
|
|
||||||
if not pprov:
|
if not pprov:
|
||||||
raise OrderError(error_messages['internal'])
|
raise OrderError(error_messages['internal'])
|
||||||
|
return pprov
|
||||||
|
|
||||||
|
|
||||||
@app.task(base=ProfiledTask, bind=True, max_retries=5, default_retry_delay=1, throws=(OrderError,))
|
@app.task(base=ProfiledTask, bind=True, max_retries=5, default_retry_delay=1, throws=(OrderError,))
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ from django.db.models import Count, Sum
|
|||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from pretix.base.models import Event, Item, ItemCategory, Order, OrderPosition
|
from pretix.base.models import Event, Item, ItemCategory, Order, OrderPosition
|
||||||
from pretix.base.signals import register_payment_providers
|
|
||||||
|
|
||||||
|
|
||||||
class DummyObject:
|
class DummyObject:
|
||||||
@@ -182,11 +181,10 @@ def order_overview(event: Event) -> Tuple[List[Tuple[ItemCategory, List[Item]]],
|
|||||||
}
|
}
|
||||||
num_total = dictsum(num_pending, num_paid)
|
num_total = dictsum(num_pending, num_paid)
|
||||||
|
|
||||||
provider_names = {}
|
provider_names = {
|
||||||
responses = register_payment_providers.send(event)
|
k: v.verbose_name
|
||||||
for receiver, response in responses:
|
for k, v in event.get_payment_providers().items()
|
||||||
provider = response(event)
|
}
|
||||||
provider_names[provider.identifier] = provider.verbose_name
|
|
||||||
|
|
||||||
for pprov, total in num_total.items():
|
for pprov, total in num_total.items():
|
||||||
ppobj = DummyObject()
|
ppobj = DummyObject()
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import os
|
import os
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
from django.core.files.base import ContentFile
|
from django.core.files.base import ContentFile
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
@@ -85,3 +86,43 @@ def preview(event: int, provider: str):
|
|||||||
prov = response(event)
|
prov = response(event)
|
||||||
if prov.identifier == provider:
|
if prov.identifier == provider:
|
||||||
return prov.generate(p)
|
return prov.generate(p)
|
||||||
|
|
||||||
|
|
||||||
|
def get_cachedticket_for_position(pos, identifier):
|
||||||
|
try:
|
||||||
|
ct = CachedTicket.objects.filter(
|
||||||
|
order_position=pos, provider=identifier
|
||||||
|
).last()
|
||||||
|
except CachedTicket.DoesNotExist:
|
||||||
|
ct = None
|
||||||
|
|
||||||
|
if not ct:
|
||||||
|
ct = CachedTicket.objects.create(
|
||||||
|
order_position=pos, provider=identifier,
|
||||||
|
extension='', type='', file=None)
|
||||||
|
generate.apply_async(args=(pos.id, identifier))
|
||||||
|
|
||||||
|
if not ct.file:
|
||||||
|
if now() - ct.created > timedelta(minutes=5):
|
||||||
|
generate.apply_async(args=(pos.id, identifier))
|
||||||
|
return ct
|
||||||
|
|
||||||
|
|
||||||
|
def get_cachedticket_for_order(order, identifier):
|
||||||
|
try:
|
||||||
|
ct = CachedCombinedTicket.objects.filter(
|
||||||
|
order=order, provider=identifier
|
||||||
|
).last()
|
||||||
|
except CachedCombinedTicket.DoesNotExist:
|
||||||
|
ct = None
|
||||||
|
|
||||||
|
if not ct:
|
||||||
|
ct = CachedCombinedTicket.objects.create(
|
||||||
|
order=order, provider=identifier,
|
||||||
|
extension='', type='', file=None)
|
||||||
|
generate_order.apply_async(args=(order.id, identifier))
|
||||||
|
|
||||||
|
if not ct.file:
|
||||||
|
if now() - ct.created > timedelta(minutes=5):
|
||||||
|
generate_order.apply_async(args=(order.id, identifier))
|
||||||
|
return ct
|
||||||
|
|||||||
@@ -35,6 +35,10 @@ DEFAULTS = {
|
|||||||
'default': 'False',
|
'default': 'False',
|
||||||
'type': bool
|
'type': bool
|
||||||
},
|
},
|
||||||
|
'order_email_asked_twice': {
|
||||||
|
'default': 'False',
|
||||||
|
'type': bool
|
||||||
|
},
|
||||||
'invoice_address_asked': {
|
'invoice_address_asked': {
|
||||||
'default': 'True',
|
'default': 'True',
|
||||||
'type': bool,
|
'type': bool,
|
||||||
@@ -240,7 +244,7 @@ Your {event} team"""))
|
|||||||
we successfully received your order for {event} with a total value
|
we successfully received your order for {event} with a total value
|
||||||
of {total} {currency}. Please complete your payment before {date}.
|
of {total} {currency}. Please complete your payment before {date}.
|
||||||
|
|
||||||
{paymentinfo}
|
{payment_info}
|
||||||
|
|
||||||
You can change your order details and view the status of your order at
|
You can change your order details and view the status of your order at
|
||||||
{url}
|
{url}
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
{% with 'target="blank" href="https://pretix.eu"'|safe as a_attr %}
|
||||||
|
{% blocktrans trimmed %}
|
||||||
|
powered by <a {{ a_attr }}>pretix</a>
|
||||||
|
{% endblocktrans %}
|
||||||
|
{% endwith %}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user