mirror of
https://github.com/pretix/pretix.git
synced 2025-12-11 01:22:28 +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
|
||||
- source env/bin/activate
|
||||
- pip install -U pip wheel setuptools
|
||||
- XDG_CACHE_HOME=/cache bash .travis.sh style
|
||||
- XDG_CACHE_HOME=/cache bash .travis.sh tests
|
||||
tags:
|
||||
- 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
|
||||
cd src
|
||||
flake8 .
|
||||
isort -c -rc .
|
||||
isort -c -rc -df .
|
||||
fi
|
||||
if [ "$1" == "doctests" ]; then
|
||||
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
|
||||
- python: 3.6
|
||||
env: JOB=tests PRETIX_CONFIG_FILE=tests/travis_postgres.cfg
|
||||
- python: 3.4
|
||||
- python: 3.6
|
||||
env: JOB=style
|
||||
- python: 3.4
|
||||
- python: 3.6
|
||||
env: JOB=plugins
|
||||
- python: 3.4
|
||||
- python: 3.6
|
||||
env: JOB=tests-cov
|
||||
addons:
|
||||
postgresql: "9.4"
|
||||
|
||||
13
Dockerfile
13
Dockerfile
@@ -1,11 +1,9 @@
|
||||
FROM debian:jessie
|
||||
FROM python:3.6
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y python3 git python3-pip \
|
||||
libxml2-dev libxslt1-dev python-dev python-virtualenv locales libffi-dev \
|
||||
build-essential python3-dev zlib1g-dev libssl-dev gettext \
|
||||
libpq-dev libmysqlclient-dev libmemcached-dev libjpeg-dev \
|
||||
aqbanking-tools supervisor nginx sudo \
|
||||
apt-get install -y git libxml2-dev libxslt1-dev python-dev python-virtualenv locales \
|
||||
libffi-dev build-essential python3-dev zlib1g-dev libssl-dev gettext libpq-dev \
|
||||
libmysqlclient-dev libmemcached-dev libjpeg-dev supervisor nginx sudo \
|
||||
--no-install-recommends && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
@@ -33,8 +31,7 @@ RUN chmod +x /usr/local/bin/pretix && \
|
||||
cd /pretix/src && \
|
||||
rm -f pretix.cfg && \
|
||||
pip3 install -r requirements.txt -r requirements/mysql.txt -r requirements/postgres.txt \
|
||||
-r requirements/memcached.txt -r requirements/redis.txt \
|
||||
-r requirements/py34.txt gunicorn && \
|
||||
-r requirements/memcached.txt -r requirements/redis.txt gunicorn && \
|
||||
mkdir -p data && \
|
||||
chown -R pretixuser:pretixuser /pretix /data data && \
|
||||
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
|
||||
===========================
|
||||
|
||||
This documentation is for everyone who wants to install pretix on a server.
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ If there is a problem, a status code in the ``5xx`` range will be returned.
|
||||
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
|
||||
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
|
||||
|
||||
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,
|
||||
# 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.
|
||||
from datetime import date
|
||||
|
||||
sys.path.insert(0, os.path.abspath('../src'))
|
||||
|
||||
import django
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "pretix.settings")
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "pretix.testutils.settings")
|
||||
django.setup()
|
||||
|
||||
# -- General configuration ------------------------------------------------
|
||||
@@ -51,11 +53,11 @@ source_suffix = '.rst'
|
||||
#source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
master_doc = 'contents'
|
||||
|
||||
# General information about the project.
|
||||
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
|
||||
# |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
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
html_theme_options = {
|
||||
'logo_only': True,
|
||||
}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
#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
|
||||
# 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
|
||||
# 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,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# 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
|
||||
# .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
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
html_additional_pages = {
|
||||
'index': 'index.html'
|
||||
}
|
||||
|
||||
# If false, no module index is generated.
|
||||
html_domain_indices = False
|
||||
@@ -171,7 +181,7 @@ html_use_index = False
|
||||
#html_split_index = False
|
||||
|
||||
# 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.
|
||||
#html_show_sphinx = True
|
||||
@@ -190,11 +200,8 @@ html_use_index = False
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'pretixdoc'
|
||||
|
||||
on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
|
||||
if not on_rtd: # only import and set the theme if we're building docs locally
|
||||
import sphinx_rtd_theme
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
|
||||
html_theme = 'pretix_theme'
|
||||
html_theme_path = [os.path.abspath('_themes')]
|
||||
|
||||
|
||||
# -- Options for LaTeX output ---------------------------------------------
|
||||
@@ -214,7 +221,7 @@ latex_elements = {
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
('index', 'pretix.tex', 'pretix Documentation',
|
||||
('contents', 'pretix.tex', 'pretix Documentation',
|
||||
'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.organizer`` attribute contains the correct ``Organizer`` object
|
||||
* 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
|
||||
: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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Plugin hooks
|
||||
============
|
||||
Plugin development
|
||||
==================
|
||||
|
||||
Contents:
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
.. highlight:: python
|
||||
:linenothreshold: 5
|
||||
|
||||
Plugin basics
|
||||
=============
|
||||
.. _`pluginsetup`:
|
||||
|
||||
Creating a 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
|
||||
@@ -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
|
||||
on the next pages.
|
||||
|
||||
.. _`pluginsetup`:
|
||||
|
||||
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.
|
||||
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
|
||||
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
|
||||
configuration class. The metadata class must define the following attributes:
|
||||
|
||||
``name`` (``str``):
|
||||
The human-readable name of your plugin
|
||||
.. rst-class:: rest-resource-table
|
||||
|
||||
``author`` (``str``):
|
||||
Your name
|
||||
|
||||
``version`` (``str``):
|
||||
A human-readable version code of your plugin
|
||||
|
||||
``description`` (``str``):
|
||||
A more verbose description of what your plugin does.
|
||||
|
||||
``visible`` (``bool``):
|
||||
``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.
|
||||
================== ==================== ===========================================================
|
||||
Attribute Type Description
|
||||
================== ==================== ===========================================================
|
||||
name string The human-readable name of your plugin
|
||||
author string Your name
|
||||
version string A human-readable version code of your plugin
|
||||
description string 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
|
||||
for an event by system administrators / superusers.
|
||||
================== ==================== ===========================================================
|
||||
|
||||
A working example would be::
|
||||
|
||||
# file: pretix/plugins/timerestriction/__init__.py
|
||||
from django.apps import AppConfig
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
class PaypalApp(AppConfig):
|
||||
name = 'pretix.plugins.paypal'
|
||||
verbose_name = _("Stripe")
|
||||
name = 'pretix_paypal'
|
||||
verbose_name = _("PayPal")
|
||||
|
||||
class PretixPluginMeta:
|
||||
name = _("PayPal")
|
||||
@@ -69,8 +70,7 @@ A working example would be::
|
||||
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
|
||||
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::
|
||||
|
||||
setup(
|
||||
…
|
||||
|
||||
args...,
|
||||
entry_points="""
|
||||
[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
|
||||
method to make your receivers available::
|
||||
|
||||
class TimeRestrictionApp(AppConfig):
|
||||
class PaypalApp(AppConfig):
|
||||
…
|
||||
|
||||
def ready(self):
|
||||
@@ -131,3 +130,4 @@ your Django app label.
|
||||
.. _signal dispatcher: https://docs.djangoproject.com/en/1.7/topics/signals/
|
||||
.. _namespace packages: http://legacy.python.org/dev/peps/pep-0420/
|
||||
.. _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
|
||||
=======================
|
||||
|
||||
Basic terminology
|
||||
-----------------
|
||||
Concepts and Terminology
|
||||
========================
|
||||
|
||||
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**
|
||||
This is the foundation below all other components. It is primarily
|
||||
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**
|
||||
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**
|
||||
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
|
||||
^^^^^^^^^^^^^^^^
|
||||
@@ -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 non-expired items currently in the shopping cart of users
|
||||
* 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
|
||||
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.
|
||||
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
|
||||
---------------
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Contribution guide
|
||||
==================
|
||||
Contributing to pretix
|
||||
======================
|
||||
|
||||
.. toctree::
|
||||
: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
|
||||
with a default configuration for flake8 that excludes migrations and other non-relevant
|
||||
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.
|
||||
* For templates and models, please take a look at the `Django Coding Style`_. We like Django's `class-based views`_ and
|
||||
kindly ask you to use them where appropriate.
|
||||
|
||||
* 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
|
||||
* F403: ``from module import *`` used; unable to detect undefined names
|
||||
* F401: module imported but unused
|
||||
* N802: function names should be lowercase
|
||||
* We expect all new code to come with proper tests. When writing new tests, please write them using `pytest-style`_
|
||||
test functions and raw ``assert`` statements. Use `fixtures`_ to prevent repetitive code. Some old parts of pretix'
|
||||
test suite are in the style of Python's unit test module. If you extend those files, you might continue in this style,
|
||||
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
|
||||
it makes sense*.
|
||||
|
||||
* 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.
|
||||
* Please keep the first line of your commit messages short. When referencing an issue, please phrase it like
|
||||
``Fix #123 -- Problems with order creation`` or ``Refs #123 -- Fix this part of that bug``.
|
||||
|
||||
|
||||
.. _PEP 8: http://legacy.python.org/dev/peps/pep-0008/
|
||||
.. _flake8: https://pypi.python.org/pypi/flake8
|
||||
.. _Django Coding Style: https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/coding-style/
|
||||
.. [#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
|
||||
=======================
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
concepts
|
||||
setup
|
||||
structure
|
||||
contribution/index
|
||||
implementation/index
|
||||
api/index
|
||||
structure
|
||||
|
||||
.. TODO::
|
||||
Document settings objects, ItemVariation objects, form fields.
|
||||
Document settings objects, ItemVariation objects, form fields.
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
.. _`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
|
||||
--------------------------------
|
||||
@@ -12,6 +16,8 @@ You can just clone our git repository::
|
||||
|
||||
External Dependencies
|
||||
---------------------
|
||||
Your should install the following on your system:
|
||||
|
||||
* Python 3.4 or newer
|
||||
* ``pip`` for Python 3 (Debian package: ``python3-pip``)
|
||||
* ``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
|
||||
the dependencies might fail::
|
||||
|
||||
pip3 install -U pip setuptools==28.6.1
|
||||
pip3 install -U pip setuptools
|
||||
|
||||
Working with the code
|
||||
---------------------
|
||||
@@ -49,7 +55,7 @@ The first thing you need are all the main application's dependencies::
|
||||
cd src/
|
||||
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
|
||||
|
||||
@@ -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
|
||||
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
|
||||
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
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
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 .
|
||||
isort -c -rc .
|
||||
python manage.py check
|
||||
|
||||
Execute the following command to run pretix' test suite (might take a coumple of minutes)::
|
||||
|
||||
py.test
|
||||
|
||||
.. 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
|
||||
cd $GIT_DIR/../src
|
||||
source ../env/bin/activate
|
||||
flake8 --ignore=E123,E128,F403,F401,N802,W503 .
|
||||
flake8 . || exit 1
|
||||
isort -q -rc -c . || exit 1
|
||||
|
||||
This keeps you from accidentally creating commits violating the sdtyle guide.
|
||||
|
||||
Working with mails
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
@@ -151,8 +165,14 @@ To build the documentation, run the following command from the ``doc/`` director
|
||||
|
||||
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
|
||||
.. _pretixdroid: https://github.com/pretix/pretixdroid
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
Project structure
|
||||
=================
|
||||
|
||||
Python source code
|
||||
------------------
|
||||
Directory structure
|
||||
===================
|
||||
|
||||
All the source code lives in ``src/``, which has several subdirectories.
|
||||
|
||||
pretix/
|
||||
This directory contains nearly all source code.
|
||||
This directory contains nearly all source code that belongs to pretix.
|
||||
|
||||
base/
|
||||
This is the Django app containing all the models and methods which are
|
||||
@@ -19,48 +16,31 @@ pretix/
|
||||
presale/
|
||||
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 contain a very few modules providing workarounds for low-level flaws in
|
||||
Django or installed 3rd-party packages.
|
||||
|
||||
locale/
|
||||
Contains translation file for pretix
|
||||
|
||||
multidomain/
|
||||
Additional code implementing our customized :ref:`URL handling <urlconf>`.
|
||||
|
||||
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/
|
||||
Contains some pretix plugins that ship with pretix itself
|
||||
testutils/
|
||||
Contains helper methods that are useful to write the test suite for pretix or test
|
||||
suites for pretix plugins.
|
||||
|
||||
tests/
|
||||
This is the root directory for all test codes. It includes subdirectories ``base``,
|
||||
``control``, ``presale``, ``helpers`` and ``plugins`` to mirror the structure of the
|
||||
``pretix`` source code as well as ``testdummy``, which is a pretix plugin used during
|
||||
This is the root directory for all test codes. It includes subdirectories ``api``, ``base``,
|
||||
``control``, ``presale``, ``helpers`, ``multidomain`` and ``plugins`` to mirror the structure
|
||||
of the pretix source code as well as ``testdummy``, which is a pretix plugin used during
|
||||
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
|
||||
pretixdroid
|
||||
banktransfer
|
||||
|
||||
@@ -15,7 +15,8 @@ ways that pretix itself is:
|
||||
* PDF ticket output
|
||||
|
||||
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`_
|
||||
* `Pages`_
|
||||
@@ -23,8 +24,17 @@ same team:
|
||||
* `Cartshare`_
|
||||
* `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
|
||||
no statements about their stability:
|
||||
no statements about their stability or compatibility:
|
||||
|
||||
* `esPass ticket output`_
|
||||
* `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.
|
||||
|
||||
.. 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
|
||||
a proper general-use API for pretix at a later point in time.
|
||||
guarantees on this API. We will not add features that are not required for the Android App. There is a
|
||||
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/
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
User Guide
|
||||
==========
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
: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
|
||||
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
|
||||
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
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
Accepting payments
|
||||
==================
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
: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_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(
|
||||
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
|
||||
from collections import OrderedDict
|
||||
from decimal import Decimal
|
||||
|
||||
import pytz
|
||||
from defusedcsv import csv
|
||||
from django import forms
|
||||
from django.db.models import Sum
|
||||
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 ..exporter import BaseExporter
|
||||
from ..signals import register_data_exporters, register_payment_providers
|
||||
from ..signals import register_data_exporters
|
||||
|
||||
|
||||
class OrderListExporter(BaseExporter):
|
||||
@@ -35,13 +35,13 @@ class OrderListExporter(BaseExporter):
|
||||
|
||||
def _get_all_tax_rates(self, qs):
|
||||
tax_rates = set(
|
||||
qs.exclude(payment_fee=0).values_list('payment_fee_tax_rate', flat=True)
|
||||
.distinct().order_by()
|
||||
qs.exclude(payment_fee=0).values_list('payment_fee_tax_rate', flat=True).distinct().order_by()
|
||||
)
|
||||
tax_rates |= set(
|
||||
a for a
|
||||
in OrderPosition.objects.filter(order__event=self.event)
|
||||
.values_list('tax_rate', flat=True).distinct().order_by()
|
||||
in OrderPosition.objects.filter(
|
||||
order__event=self.event
|
||||
).values_list('tax_rate', flat=True).distinct().order_by()
|
||||
)
|
||||
tax_rates = sorted(tax_rates)
|
||||
return tax_rates
|
||||
@@ -73,11 +73,10 @@ class OrderListExporter(BaseExporter):
|
||||
|
||||
writer.writerow(headers)
|
||||
|
||||
provider_names = {}
|
||||
responses = register_payment_providers.send(self.event)
|
||||
for rec, response in responses:
|
||||
provider = response(self.event)
|
||||
provider_names[provider.identifier] = provider.verbose_name
|
||||
provider_names = {
|
||||
k: v.verbose_name
|
||||
for k, v in self.event.get_payment_providers().items()
|
||||
}
|
||||
|
||||
sum_cache = {
|
||||
(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")
|
||||
|
||||
|
||||
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")
|
||||
def register_orderlist_exporter(sender, **kwargs):
|
||||
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):
|
||||
self.user = kwargs.pop('user')
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['email'].required = True
|
||||
|
||||
def clean_old_pw(self):
|
||||
old_pw = self.cleaned_data.get('old_pw')
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from collections import OrderedDict
|
||||
from urllib.parse import urlsplit
|
||||
|
||||
import pytz
|
||||
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.utils import timezone, translation
|
||||
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.translation import LANGUAGE_SESSION_KEY
|
||||
from django.utils.translation.trans_real import (
|
||||
@@ -13,6 +15,8 @@ from django.utils.translation.trans_real import (
|
||||
parse_accept_lang_header,
|
||||
)
|
||||
|
||||
from pretix.multidomain.urlreverse import get_domain
|
||||
|
||||
_supported = None
|
||||
|
||||
|
||||
@@ -158,6 +162,12 @@ def _merge_csp(a, b):
|
||||
|
||||
|
||||
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):
|
||||
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': ['{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"],
|
||||
'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-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
|
||||
@@ -187,6 +197,9 @@ class SecurityMiddleware(MiddlewareMixin):
|
||||
|
||||
staticdomain = "'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'):
|
||||
staticdomain += " " + settings.STATIC_URL[:settings.STATIC_URL.find('/', 9)]
|
||||
if settings.SITE_URL.startswith('http'):
|
||||
@@ -196,5 +209,16 @@ class SecurityMiddleware(MiddlewareMixin):
|
||||
else:
|
||||
staticdomain += " " + 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
|
||||
|
||||
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,
|
||||
help_text=_(
|
||||
"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=[
|
||||
RegexValidator(
|
||||
regex="^[a-zA-Z0-9.-]+$",
|
||||
@@ -311,6 +313,19 @@ class Event(LoggedModel):
|
||||
|
||||
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():
|
||||
return get_random_string(length=32, allowed_chars=string.ascii_lowercase + string.digits)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import string
|
||||
from datetime import date
|
||||
from decimal import Decimal
|
||||
|
||||
from django.db import DatabaseError, models, transaction
|
||||
from django.utils import timezone
|
||||
from django.utils.crypto import get_random_string
|
||||
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):
|
||||
"""
|
||||
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)
|
||||
invoice_from = 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')
|
||||
introductory_text = models.TextField(blank=True)
|
||||
additional_text = models.TextField(blank=True)
|
||||
|
||||
@@ -214,7 +214,7 @@ class Item(LoggedModel):
|
||||
allow_cancel = models.BooleanField(
|
||||
verbose_name=_('Allow product to be canceled'),
|
||||
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 '
|
||||
'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.urls import reverse
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.html import escape
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
@@ -66,7 +67,7 @@ class LogEntry(models.Model):
|
||||
'organizer': self.event.organizer.slug,
|
||||
'code': co.code
|
||||
}),
|
||||
'val': co.code,
|
||||
'val': escape(co.code),
|
||||
}
|
||||
elif isinstance(co, Voucher):
|
||||
a_text = _('Voucher {val}…')
|
||||
@@ -76,7 +77,7 @@ class LogEntry(models.Model):
|
||||
'organizer': self.event.organizer.slug,
|
||||
'voucher': co.id
|
||||
}),
|
||||
'val': co.code[:6],
|
||||
'val': escape(co.code[:6]),
|
||||
}
|
||||
elif isinstance(co, Item):
|
||||
a_text = _('Product {val}')
|
||||
@@ -86,7 +87,7 @@ class LogEntry(models.Model):
|
||||
'organizer': self.event.organizer.slug,
|
||||
'item': co.id
|
||||
}),
|
||||
'val': co.name,
|
||||
'val': escape(co.name),
|
||||
}
|
||||
elif isinstance(co, Quota):
|
||||
a_text = _('Quota {val}')
|
||||
@@ -96,7 +97,7 @@ class LogEntry(models.Model):
|
||||
'organizer': self.event.organizer.slug,
|
||||
'quota': co.id
|
||||
}),
|
||||
'val': co.name,
|
||||
'val': escape(co.name),
|
||||
}
|
||||
elif isinstance(co, ItemCategory):
|
||||
a_text = _('Category {val}')
|
||||
@@ -106,7 +107,7 @@ class LogEntry(models.Model):
|
||||
'organizer': self.event.organizer.slug,
|
||||
'category': co.id
|
||||
}),
|
||||
'val': co.name,
|
||||
'val': escape(co.name),
|
||||
}
|
||||
elif isinstance(co, Question):
|
||||
a_text = _('Question {val}')
|
||||
@@ -116,7 +117,7 @@ class LogEntry(models.Model):
|
||||
'organizer': self.event.organizer.slug,
|
||||
'question': co.id
|
||||
}),
|
||||
'val': co.question,
|
||||
'val': escape(co.question),
|
||||
}
|
||||
|
||||
if a_text and a_map:
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import copy
|
||||
import json
|
||||
import os
|
||||
import string
|
||||
from datetime import datetime
|
||||
@@ -183,6 +184,10 @@ class Order(LoggedModel):
|
||||
def __str__(self):
|
||||
return self.full_code
|
||||
|
||||
@cached_property
|
||||
def meta_info_data(self):
|
||||
return json.loads(self.meta_info)
|
||||
|
||||
@property
|
||||
def full_code(self):
|
||||
"""
|
||||
@@ -305,7 +310,7 @@ class Order(LoggedModel):
|
||||
|
||||
def _is_still_available(self, now_dt: datetime=None) -> Union[bool, str]:
|
||||
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()
|
||||
positions = self.positions.all().select_related('item', 'variation')
|
||||
@@ -327,7 +332,9 @@ class Order(LoggedModel):
|
||||
quota.cached_availability -= 1
|
||||
if quota.cached_availability < 0:
|
||||
# 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:
|
||||
return str(e)
|
||||
return True
|
||||
@@ -491,6 +498,10 @@ class OrderPosition(AbstractPosition):
|
||||
verbose_name_plural = _("Order positions")
|
||||
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
|
||||
def transform_cart_positions(cls, cp: List, order) -> list:
|
||||
from . import Voucher
|
||||
@@ -522,6 +533,13 @@ class OrderPosition(AbstractPosition):
|
||||
cartpos.delete()
|
||||
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):
|
||||
return '<OrderPosition: item %d, variation %d for order %s>' % (
|
||||
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)
|
||||
|
||||
|
||||
def generate_api_token():
|
||||
return get_random_string(length=64, allowed_chars=string.ascii_lowercase + string.digits)
|
||||
|
||||
|
||||
class Team(LoggedModel):
|
||||
"""
|
||||
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:
|
||||
return self.limit_events.filter(pk=event.pk).exists()
|
||||
|
||||
@property
|
||||
def active_tokens(self):
|
||||
return self.tokens.filter(active=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Team")
|
||||
verbose_name_plural = _("Teams")
|
||||
@@ -200,3 +208,81 @@ class TeamInvite(models.Model):
|
||||
return _("Invite to team '{team}' for '{email}'").format(
|
||||
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
|
||||
|
||||
|
||||
def _generate_random_code():
|
||||
def _generate_random_code(prefix=None):
|
||||
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)
|
||||
|
||||
|
||||
def generate_code():
|
||||
def generate_code(prefix=None):
|
||||
while True:
|
||||
code = _generate_random_code()
|
||||
code = _generate_random_code(prefix=prefix)
|
||||
if not Voucher.objects.filter(code=code).exists():
|
||||
return code
|
||||
|
||||
@@ -163,6 +165,7 @@ class Voucher(LoggedModel):
|
||||
verbose_name = _("Voucher")
|
||||
verbose_name_plural = _("Vouchers")
|
||||
unique_together = (("event", "code"),)
|
||||
ordering = ('code', )
|
||||
|
||||
def __str__(self):
|
||||
return self.code
|
||||
|
||||
@@ -80,6 +80,16 @@ class BasePaymentProvider:
|
||||
"""
|
||||
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
|
||||
def identifier(self) -> str:
|
||||
"""
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import copy
|
||||
import tempfile
|
||||
from collections import defaultdict
|
||||
from datetime import date
|
||||
from decimal import Decimal
|
||||
|
||||
from django.contrib.staticfiles import finders
|
||||
from django.core.files.base import ContentFile
|
||||
from django.db import transaction
|
||||
from django.utils import timezone
|
||||
from django.utils.formats import date_format, localize
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import pgettext, ugettext as _
|
||||
from i18nfield.strings import LazyI18nString
|
||||
from reportlab.lib import pagesizes
|
||||
@@ -25,7 +24,6 @@ from reportlab.platypus import (
|
||||
from pretix.base.i18n import language
|
||||
from pretix.base.models import Invoice, InvoiceAddress, InvoiceLine, Order
|
||||
from pretix.base.services.async import TransactionAwareTask
|
||||
from pretix.base.signals import register_payment_providers
|
||||
from pretix.celery_app import app
|
||||
from pretix.helpers.database import rolledback_transaction
|
||||
|
||||
@@ -33,12 +31,7 @@ from pretix.helpers.database import rolledback_transaction
|
||||
@transaction.atomic
|
||||
def build_invoice(invoice: Invoice) -> Invoice:
|
||||
with language(invoice.locale):
|
||||
responses = register_payment_providers.send(invoice.event)
|
||||
for receiver, response in responses:
|
||||
provider = response(invoice.event)
|
||||
if provider.identifier == invoice.order.payment_provider:
|
||||
payment_provider = provider
|
||||
break
|
||||
payment_provider = invoice.event.get_payment_providers().get(invoice.order.payment_provider)
|
||||
|
||||
invoice.invoice_from = invoice.event.settings.get('invoice_address_from')
|
||||
|
||||
@@ -68,7 +61,9 @@ def build_invoice(invoice: Invoice) -> Invoice:
|
||||
invoice.save()
|
||||
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)
|
||||
if p.variation:
|
||||
desc += " - " + str(p.variation.value)
|
||||
@@ -108,7 +103,7 @@ def generate_cancellation(invoice: Invoice):
|
||||
cancellation.invoice_no = None
|
||||
cancellation.refers = invoice
|
||||
cancellation.is_cancellation = True
|
||||
cancellation.date = date.today()
|
||||
cancellation.date = timezone.now().date()
|
||||
cancellation.payment_provider_text = ''
|
||||
cancellation.save()
|
||||
|
||||
@@ -135,7 +130,7 @@ def generate_invoice(order: Order):
|
||||
invoice = Invoice(
|
||||
order=order,
|
||||
event=order.event,
|
||||
date=date.today(),
|
||||
date=timezone.now().date(),
|
||||
locale=locale
|
||||
)
|
||||
invoice = build_invoice(invoice)
|
||||
@@ -430,11 +425,11 @@ def build_preview_invoice_pdf(event):
|
||||
locale = event.settings.locale
|
||||
|
||||
with rolledback_transaction(), language(locale):
|
||||
order = event.orders.create(status=Order.STATUS_PENDING, datetime=now(),
|
||||
expires=now(), code="PREVIEW", total=119)
|
||||
order = event.orders.create(status=Order.STATUS_PENDING, datetime=timezone.now(),
|
||||
expires=timezone.now(), code="PREVIEW", total=119)
|
||||
invoice = Invoice(
|
||||
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')
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import pytz
|
||||
from celery.exceptions import MaxRetriesExceededError
|
||||
from django.conf import settings
|
||||
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.utils.formats import date_format
|
||||
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.mail import SendMailException, mail
|
||||
from pretix.base.signals import (
|
||||
order_paid, order_placed, periodic_task, register_payment_providers,
|
||||
)
|
||||
from pretix.base.signals import order_paid, order_placed, periodic_task
|
||||
from pretix.celery_app import app
|
||||
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):
|
||||
|
||||
event = Event.objects.get(id=event)
|
||||
responses = register_payment_providers.send(event)
|
||||
pprov = None
|
||||
for rec, response in responses:
|
||||
provider = response(event)
|
||||
if provider.identifier == payment_provider:
|
||||
pprov = provider
|
||||
pprov = event.get_payment_providers().get(payment_provider)
|
||||
if not pprov:
|
||||
raise OrderError(error_messages['internal'])
|
||||
|
||||
@@ -414,7 +407,7 @@ def _perform_order(event: str, payment_provider: str, position_ids: List[str],
|
||||
'order': order.code,
|
||||
'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_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 '
|
||||
'no quota is available.'),
|
||||
'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'))
|
||||
PriceOperation = namedtuple('PriceOperation', ('position', 'price'))
|
||||
CancelOperation = namedtuple('CancelOperation', ('position',))
|
||||
AddOperation = namedtuple('AddOperation', ('item', 'variation', 'price', 'addon_to'))
|
||||
|
||||
def __init__(self, order: Order, user):
|
||||
self.order = order
|
||||
@@ -512,7 +508,7 @@ class OrderChangeManager:
|
||||
if (not variation and item.has_variations) or (variation and variation.item_id != item.pk):
|
||||
raise OrderError(self.error_messages['product_without_variation'])
|
||||
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'])
|
||||
self._totaldiff = price - position.price
|
||||
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._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):
|
||||
for quota, diff in self._quotadiff.items():
|
||||
if diff <= 0:
|
||||
@@ -552,6 +563,7 @@ class OrderChangeManager:
|
||||
raise OrderError(self.error_messages['paid_to_free_exceeded'])
|
||||
|
||||
def _perform_operations(self):
|
||||
nextposid = self.order.positions.aggregate(m=Max('positionid'))['m'] + 1
|
||||
for op in self._operations:
|
||||
if isinstance(op, self.ItemOperation):
|
||||
self.order.log_action('pretix.event.order.changed.item', user=self.user, data={
|
||||
@@ -600,13 +612,29 @@ class OrderChangeManager:
|
||||
'addon_to': None,
|
||||
})
|
||||
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):
|
||||
self.order.total = sum([p.price for p in self.order.positions.all()])
|
||||
if self.order.total == 0:
|
||||
payment_fee = Decimal('0.00')
|
||||
else:
|
||||
payment_fee = self._get_payment_provider().calculate_fee(self.order.total)
|
||||
payment_fee = Decimal('0.00')
|
||||
if self.order.total != 0:
|
||||
prov = self._get_payment_provider()
|
||||
if prov:
|
||||
payment_fee = prov.calculate_fee(self.order.total)
|
||||
self.order.payment_fee = payment_fee
|
||||
self.order.total += payment_fee
|
||||
self.order._calculate_tax()
|
||||
@@ -669,14 +697,10 @@ class OrderChangeManager:
|
||||
CachedTicket.objects.filter(order_position__order=self.order).delete()
|
||||
|
||||
def _get_payment_provider(self):
|
||||
responses = register_payment_providers.send(self.order.event)
|
||||
pprov = None
|
||||
for rec, response in responses:
|
||||
provider = response(self.order.event)
|
||||
if provider.identifier == self.order.payment_provider:
|
||||
return provider
|
||||
pprov = self.order.event.get_payment_providers().get(self.order.payment_provider)
|
||||
if not pprov:
|
||||
raise OrderError(error_messages['internal'])
|
||||
return pprov
|
||||
|
||||
|
||||
@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 pretix.base.models import Event, Item, ItemCategory, Order, OrderPosition
|
||||
from pretix.base.signals import register_payment_providers
|
||||
|
||||
|
||||
class DummyObject:
|
||||
@@ -182,11 +181,10 @@ def order_overview(event: Event) -> Tuple[List[Tuple[ItemCategory, List[Item]]],
|
||||
}
|
||||
num_total = dictsum(num_pending, num_paid)
|
||||
|
||||
provider_names = {}
|
||||
responses = register_payment_providers.send(event)
|
||||
for receiver, response in responses:
|
||||
provider = response(event)
|
||||
provider_names[provider.identifier] = provider.verbose_name
|
||||
provider_names = {
|
||||
k: v.verbose_name
|
||||
for k, v in event.get_payment_providers().items()
|
||||
}
|
||||
|
||||
for pprov, total in num_total.items():
|
||||
ppobj = DummyObject()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import os
|
||||
from datetime import timedelta
|
||||
|
||||
from django.core.files.base import ContentFile
|
||||
from django.utils.timezone import now
|
||||
@@ -85,3 +86,43 @@ def preview(event: int, provider: str):
|
||||
prov = response(event)
|
||||
if prov.identifier == provider:
|
||||
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',
|
||||
'type': bool
|
||||
},
|
||||
'order_email_asked_twice': {
|
||||
'default': 'False',
|
||||
'type': bool
|
||||
},
|
||||
'invoice_address_asked': {
|
||||
'default': 'True',
|
||||
'type': bool,
|
||||
@@ -240,7 +244,7 @@ Your {event} team"""))
|
||||
we successfully received your order for {event} with a total value
|
||||
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
|
||||
{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