Compare commits

..

1 Commits

Author SHA1 Message Date
Raphael Michel
9693625758 Fix failing tests 2025-08-22 09:23:07 +02:00
1114 changed files with 246799 additions and 374855 deletions

View File

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

View File

@@ -23,13 +23,13 @@ jobs:
name: Tests name: Tests
strategy: strategy:
matrix: matrix:
python-version: ["3.10", "3.11", "3.13"] python-version: ["3.9", "3.10", "3.11"]
database: [sqlite, postgres] database: [sqlite, postgres]
exclude: exclude:
- database: sqlite - database: sqlite
python-version: "3.10" python-version: "3.9"
- database: sqlite - database: sqlite
python-version: "3.11" python-version: "3.10"
services: services:
postgres: postgres:
image: postgres:15 image: postgres:15

View File

@@ -1,7 +1,7 @@
This file is part of pretix (Community Edition). This file is part of pretix (Community Edition).
Copyright (C) 2014-2020 Raphael Michel and contributors Copyright (C) 2014-2020 Raphael Michel and contributors
Copyright (C) 2020-today pretix GmbH and contributors Copyright (C) 2020-2021 rami.io GmbH and contributors
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
Public License as published by the Free Software Foundation in version 3 of the License. Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -6,14 +6,10 @@
{%- else %} {%- else %}
{%- set titlesuffix = "" %} {%- set titlesuffix = "" %}
{%- endif %} {%- endif %}
{%- set lang_attr = 'en' if language == None else (language | replace('_', '-')) %}
{# Build sphinx_version_info tuple from sphinx_version string in pure Jinja #}
{%- set (_ver_major, _ver_minor) = (sphinx_version.split('.') | list)[:2] | map('int') -%}
{%- set sphinx_version_info = (_ver_major, _ver_minor, -1) -%}
<!DOCTYPE html> <!DOCTYPE html>
<html class="writer-html5" lang="{{ lang_attr }}"{% if sphinx_version_info >= (7, 2) %} data-content_root="{{ content_root }}"{% endif %}> <!--[if IE 8]><html class="no-js lt-ie9" lang="en" > <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js" lang="en" > <!--<![endif]-->
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
{{ metatags }} {{ metatags }}
@@ -22,50 +18,59 @@
<title>{{ title|striptags|e }}{{ titlesuffix }}</title> <title>{{ title|striptags|e }}{{ titlesuffix }}</title>
{% endblock %} {% endblock %}
{#- CSS #}
{%- for css_file in css_files %} {#- CSS #}
{%- if css_file|attr("filename") %} {%- for css in css_files %}
{{ css_tag(css_file) }} {%- if css|attr("rel") %}
<link rel="{{ css.rel }}" href="{{ pathto(css.filename, 1) }}" type="text/css"{% if css.title is not none %} title="{{ css.title }}"{% endif %} />
{%- else %} {%- else %}
<link rel="stylesheet" href="{{ pathto(css_file, 1)|escape }}" type="text/css" /> <link rel="stylesheet" href="{{ pathto(css, 1) }}" type="text/css" />
{%- endif %} {%- endif %}
{%- endfor %} {%- endfor %}
{#- FAVICON #} {%- for cssfile in extra_css_files %}
{%- if favicon_url %} <link rel="stylesheet" href="{{ pathto(cssfile, 1) }}" type="text/css" />
<link rel="shortcut icon" href="{{ favicon_url }}"/> {%- endfor -%}
{%- endif %}
{#- CANONICAL URL (deprecated) #} {#- FAVICON
{%- if theme_canonical_url and not pageurl %} favicon_url is the only context var necessary since Sphinx 4.
In Sphinx<4, we use favicon but need to prepend path info.
#}
{%- set _favicon_url = favicon_url | default(pathto('_static/' + (favicon or ""), 1)) %}
{%- if favicon_url or favicon %}
<link rel="shortcut icon" href="{{ _favicon_url }}"/>
{%- endif %}
{#- CANONICAL URL (deprecated) #}
{%- if theme_canonical_url and not pageurl %}
<link rel="canonical" href="{{ theme_canonical_url }}{{ pagename }}.html"/> <link rel="canonical" href="{{ theme_canonical_url }}{{ pagename }}.html"/>
{%- endif -%} {%- endif -%}
{#- CANONICAL URL #} {#- CANONICAL URL #}
{%- if pageurl %} {%- if pageurl %}
<link rel="canonical" href="{{ pageurl|e }}" /> <link rel="canonical" href="{{ pageurl|e }}" />
{%- endif -%} {%- endif -%}
{#- JAVASCRIPTS #} {#- JAVASCRIPTS #}
{%- block scripts %} {%- block scripts %}
{%- if not embedded %} <!--[if lt IE 9]>
{%- for scriptfile in script_files %} <script src="{{ pathto('_static/js/html5shiv.min.js', 1) }}"></script>
{{ js_tag(scriptfile) }} <![endif]-->
{%- endfor %} {%- if not embedded %}
{# XXX Sphinx 1.8.0 made this an external js-file, quick fix until we refactor the template to inherert more blocks directly from sphinx #}
{%- for scriptfile in script_files %}
{{ js_tag(scriptfile) }}
{%- endfor %}
<script src="{{ pathto('_static/js/theme.js', 1) }}"></script> <script src="{{ pathto('_static/js/theme.js', 1) }}"></script>
{%- if READTHEDOCS or DEBUG %}
<script src="{{ pathto('_static/js/versions.js', 1) }}"></script>
{%- endif %}
{#- OPENSEARCH #} {#- OPENSEARCH #}
{%- if use_opensearch %} {%- if use_opensearch %}
<link rel="search" type="application/opensearchdescription+xml" <link rel="search" type="application/opensearchdescription+xml"
title="{% trans docstitle=docstitle|e %}Search within {{ docstitle }}{% endtrans %}" title="{% trans docstitle=docstitle|e %}Search within {{ docstitle }}{% endtrans %}"
href="{{ pathto('_static/opensearch.xml', 1) }}"/> href="{{ pathto('_static/opensearch.xml', 1) }}"/>
{%- endif %} {%- endif %}
{%- endif %} {%- endif %}
{%- endblock %} {%- endblock %}
{%- block linktags %} {%- block linktags %}
{%- if hasdoc('about') %} {%- if hasdoc('about') %}
@@ -118,23 +123,23 @@
{% endblock %} {% endblock %}
</div> </div>
{%- block navigation %} <div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="main navigation">
{#- Translators: This is an ARIA section label for the main navigation menu -#} {% block menu %}
<div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="{{ _('Navigation menu') }}"> {#
{%- block menu %} The singlehtml builder doesn't handle this toctree call when the
{%- set toctree = toctree(maxdepth=theme_navigation_depth|int, toctree is empty. Skip building this for now.
collapse=theme_collapse_navigation|tobool, #}
includehidden=theme_includehidden|tobool, {% if 'singlehtml' not in builder %}
titles_only=theme_titles_only|tobool) %} {% set global_toc = toctree(maxdepth=theme_navigation_depth|int, collapse=theme_collapse_navigation, includehidden=True) %}
{%- if toctree %} {% endif %}
{{ toctree }} {% if global_toc %}
{%- else %} {{ global_toc }}
{% else %}
<!-- Local TOC --> <!-- Local TOC -->
<div class="local-toc">{{ toc }}</div> <div class="local-toc">{{ toc }}</div>
{%- endif %} {% endif %}
{%- endblock %} {% endblock %}
</div> </div>
{%- endblock %}
{% if theme_display_version %} {% if theme_display_version %}
{%- set nav_version = version %} {%- set nav_version = version %}
@@ -153,42 +158,53 @@
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap"> <section data-toggle="wy-nav-shift" class="wy-nav-content-wrap">
{# MOBILE NAV, TRIGGLES SIDE NAV ON TOGGLE #} {# MOBILE NAV, TRIGGLES SIDE NAV ON TOGGLE #}
<nav class="wy-nav-top" aria-label="{{ _('Mobile navigation menu') }}" {% if theme_style_nav_header_background %} style="background: {{theme_style_nav_header_background}}" {% endif %}> <nav class="wy-nav-top" role="navigation" aria-label="top navigation">
{%- block mobile_nav %} {% block mobile_nav %}
<i data-toggle="wy-nav-top" class="fa fa-bars"></i> <i data-toggle="wy-nav-top" class="fa fa-bars"></i>
<a href="{{ pathto(master_doc) }}">{{ project }}</a> <a href="{{ pathto('index') }}">{{ project }}</a>
{%- endblock %} {% endblock %}
</nav> </nav>
<div class="wy-nav-content">
{%- block content %} {# PAGE CONTENT #}
{%- if theme_style_external_links|tobool %} <div class="wy-nav-content">
<div class="rst-content style-external-links"> <div class="rst-content">
{%- else %} {% include "breadcrumbs.html" %}
<div class="rst-content"> <div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
{%- endif %} <div itemprop="articleBody" class="section">
{% include "breadcrumbs.html" %} {% block body %}{% endblock %}
<div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article"> </div>
{%- block document %} <div class="articleComments">
<div itemprop="articleBody"> {% block comments %}{% endblock %}
{% block body %}{% endblock %} </div>
</div> </div>
{%- if self.comments()|trim %} {% include "footer.html" %}
<div class="articleComments">
{%- block comments %}{% endblock %}
</div>
{%- endif%}
</div>
{%- endblock %}
{% include "footer.html" %}
</div>
{%- endblock %}
</div> </div>
</div>
</section> </section>
</div> </div>
{% include "versions.html" %} {% 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 #} {# RTD hosts this file, so just load on non RTD builds #}
{% if not READTHEDOCS %} {% if not READTHEDOCS %}
<script type="text/javascript" src="{{ pathto('_static/js/theme.js', 1) }}"></script> <script type="text/javascript" src="{{ pathto('_static/js/theme.js', 1) }}"></script>
@@ -198,7 +214,7 @@
{% if theme_sticky_navigation %} {% if theme_sticky_navigation %}
<script type="text/javascript"> <script type="text/javascript">
jQuery(function () { jQuery(function () {
SphinxRtdTheme.Navigation.enable({{ 'true' if theme_sticky_navigation|tobool else 'false' }}); SphinxRtdTheme.StickyNav.enable();
}); });
</script> </script>
{% endif %} {% endif %}

View File

@@ -1,86 +1,136 @@
{# TEMPLATE VAR SETTINGS #} {#
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 ' &raquo;' 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) %} {%- set url_root = pathto('', 1) %}
{# XXX necessary? #}
{%- if url_root == '#' %}{% set url_root = '' %}{% endif %} {%- if url_root == '#' %}{% set url_root = '' %}{% endif %}
{%- if not embedded and docstitle %} {%- if not embedded and docstitle %}
{%- set titlesuffix = " &mdash; "|safe + docstitle|e %} {%- set titlesuffix = " &mdash; "|safe + docstitle|e %}
{%- else %} {%- else %}
{%- set titlesuffix = "" %} {%- set titlesuffix = "" %}
{%- endif %} {%- endif %}
{%- set lang_attr = 'en' if language == None else (language | replace('_', '-')) %}
{# Build sphinx_version_info tuple from sphinx_version string in pure Jinja #} {%- macro relbar() %}
{%- set (_ver_major, _ver_minor) = (sphinx_version.split('.') | list)[:2] | map('int') -%} <div class="related">
{%- set sphinx_version_info = (_ver_major, _ver_minor, -1) -%} <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 %}
<!DOCTYPE html> {%- macro sidebar() %}
<html class="writer-html5" lang="{{ lang_attr }}"{% if sphinx_version_info >= (7, 2) %} data-content_root="{{ content_root }}"{% endif %}> {%- if render_sidebar %}
<head> <div class="sphinxsidebar">
<meta charset="utf-8" /> <div class="sphinxsidebarwrapper">
{%- if READTHEDOCS and not embedded %} {%- block sidebarlogo %}
<meta name="readthedocs-addons-api-version" content="1"> {%- if logo %}
{%- endif %} <p class="logo"><a href="{{ pathto(master_doc) }}">
{{- metatags }} <img class="logo" src="{{ pathto('_static/' + logo, 1) }}" alt="Logo"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> </a></p>
{%- block htmltitle %} {%- endif %}
<title>{{ title|striptags|e }}{{ titlesuffix }}</title> {%- endblock %}
{%- 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 %}
{#- CSS #} {%- macro script() %}
{%- for css_file in css_files %} <script type="text/javascript">
{%- if css_file|attr("filename") %} var DOCUMENTATION_OPTIONS = {
{{ css_tag(css_file) }} URL_ROOT: '{{ url_root }}',
{%- else %} VERSION: '{{ release|e }}',
<link rel="stylesheet" href="{{ pathto(css_file, 1)|escape }}" type="text/css" /> COLLAPSE_INDEX: false,
{%- endif %} FILE_SUFFIX: '{{ '' if no_search_suffix else file_suffix }}',
{%- endfor %} HAS_SOURCE: {{ has_source|lower }},
SOURCELINK_SUFFIX: '{{ sourcelink_suffix }}'
{# };
"extra_css_files" is an undocumented Read the Docs theme specific option. </script>
There is no need to check for ``|attr("filename")`` here because it's always a string.
Note that this option should be removed in favor of regular ``html_css_files``:
https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-html_css_files
#}
{%- for css_file in extra_css_files %}
<link rel="stylesheet" href="{{ pathto(css_file, 1)|escape }}" type="text/css" />
{%- endfor -%}
{#- FAVICON #}
{%- if favicon_url %}
<link rel="shortcut icon" href="{{ favicon_url }}"/>
{%- endif %}
{#- CANONICAL URL (deprecated) #}
{%- if theme_canonical_url and not pageurl %}
<link rel="canonical" href="{{ theme_canonical_url }}{{ pagename }}.html"/>
{%- endif -%}
{#- CANONICAL URL #}
{%- if pageurl %}
<link rel="canonical" href="{{ pageurl|e }}" />
{%- endif -%}
{#- JAVASCRIPTS #}
{%- block scripts %}
{%- if not embedded %}
{%- for scriptfile in script_files %} {%- for scriptfile in script_files %}
{{ js_tag(scriptfile) }} <script type="text/javascript" src="{{ pathto(scriptfile, 1) }}"></script>
{%- endfor %} {%- endfor %}
<script src="{{ pathto('_static/js/theme.js', 1) }}"></script> {%- endmacro %}
{%- if READTHEDOCS or DEBUG %} {%- macro css() %}
<script src="{{ pathto('_static/js/versions.js', 1) }}"></script> <link rel="stylesheet" href="{{ pathto('_static/' + style, 1) }}" type="text/css" />
{%- endif %} <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 %}
{#- OPENSEARCH #} <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 %} {%- if use_opensearch %}
<link rel="search" type="application/opensearchdescription+xml" <link rel="search" type="application/opensearchdescription+xml"
title="{% trans docstitle=docstitle|e %}Search within {{ docstitle }}{% endtrans %}" title="{% trans docstitle=docstitle|e %}Search within {{ docstitle }}{% endtrans %}"
href="{{ pathto('_static/opensearch.xml', 1) }}"/> href="{{ pathto('_static/opensearch.xml', 1) }}"/>
{%- endif %} {%- endif %}
{%- endif %} {%- if favicon %}
{%- endblock %} <link rel="shortcut icon" href="{{ pathto('_static/' + favicon, 1) }}"/>
{%- endif %}
{%- block linktags %} {%- if theme_canonical_url %}
<link rel="canonical" href="{{ theme_canonical_url }}{{ pagename }}.html"/>
{%- endif %}
{%- endif %}
{%- block linktags %}
{%- if hasdoc('about') %} {%- if hasdoc('about') %}
<link rel="author" title="{{ _('About these documents') }}" href="{{ pathto('about') }}" /> <link rel="author" title="{{ _('About these documents') }}" href="{{ pathto('about') }}" />
{%- endif %} {%- endif %}
@@ -93,135 +143,67 @@
{%- if hasdoc('copyright') %} {%- if hasdoc('copyright') %}
<link rel="copyright" title="{{ _('Copyright') }}" href="{{ pathto('copyright') }}" /> <link rel="copyright" title="{{ _('Copyright') }}" href="{{ pathto('copyright') }}" />
{%- endif %} {%- endif %}
<link rel="top" title="{{ docstitle|e }}" href="{{ pathto('index') }}" />
{%- if parents %}
<link rel="up" title="{{ parents[-1].title|striptags|e }}" href="{{ parents[-1].link|e }}" />
{%- endif %}
{%- if next %} {%- if next %}
<link rel="next" title="{{ next.title|striptags|e }}" href="{{ next.link|e }}" /> <link rel="next" title="{{ next.title|striptags|e }}" href="{{ next.link|e }}" />
{%- endif %} {%- endif %}
{%- if prev %} {%- if prev %}
<link rel="prev" title="{{ prev.title|striptags|e }}" href="{{ prev.link|e }}" /> <link rel="prev" title="{{ prev.title|striptags|e }}" href="{{ prev.link|e }}" />
{%- endif %} {%- endif %}
{%- endblock %} {%- endblock %}
{%- block extrahead %} {% endblock %} {%- block extrahead %} {% endblock %}
</head> </head>
<body>
{%- block header %}{% endblock %}
<body class="wy-body-for-nav"> {%- block relbar1 %}{{ relbar() }}{% endblock %}
{%- block extrabody %} {% endblock %} {%- block content %}
<div class="wy-grid-for-nav"> {%- block sidebar1 %} {# possible location for sidebar #} {% endblock %}
{#- 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" {% if theme_style_nav_header_background %} style="background: {{theme_style_nav_header_background}}" {% endif %}>
{%- block sidebartitle %}
{# the logo helper function was removed in Sphinx 6 and deprecated since Sphinx 4 #} <div class="document">
{# the master_doc variable was renamed to root_doc in Sphinx 4 (master_doc still exists in later Sphinx versions) #} {%- block document %}
{%- set _logo_url = logo_url|default(pathto('_static/' + (logo or ""), 1)) %} <div class="documentwrapper">
{%- set _root_doc = root_doc|default(master_doc) %} {%- if render_sidebar %}
<a href="{{ pathto(_root_doc) }}"{% if not theme_logo_only %} class="icon icon-home"{% endif %}> <div class="bodywrapper">
{% if not theme_logo_only %}{{ project }}{% endif %} {%- endif %}
{%- if logo or logo_url %} <div class="body">
<img src="{{ _logo_url }}" class="logo" alt="{{ _('Logo') }}"/> {% block body %} {% endblock %}
{%- endif %}
</a>
{%- if READTHEDOCS or DEBUG %}
{%- if theme_version_selector or theme_language_selector %}
<div class="switch-menus">
<div class="version-switch"></div>
<div class="language-switch"></div>
</div>
{%- endif %}
{%- endif %}
{%- include "searchbox.html" %}
{%- endblock %}
</div>
{%- block navigation %}
{#- Translators: This is an ARIA section label for the main navigation menu -#}
<div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="{{ _('Navigation menu') }}">
{%- block menu %}
{%- set toctree = toctree(maxdepth=theme_navigation_depth|int,
collapse=theme_collapse_navigation|tobool,
includehidden=theme_includehidden|tobool,
titles_only=theme_titles_only|tobool) %}
{%- if toctree %}
{{ toctree }}
{%- else %}
<!-- Local TOC -->
<div class="local-toc">{{ toc }}</div>
{%- endif %}
{%- endblock %}
</div>
{%- endblock %}
</div>
</nav>
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap">
{#- MOBILE NAV, TRIGGLES SIDE NAV ON TOGGLE #}
{#- Translators: This is an ARIA section label for the navigation menu that is visible when viewing the page on mobile devices -#}
<nav class="wy-nav-top" aria-label="{{ _('Mobile navigation menu') }}" {% if theme_style_nav_header_background %} style="background: {{theme_style_nav_header_background}}" {% endif %}>
{%- block mobile_nav %}
<i data-toggle="wy-nav-top" class="fa fa-bars"></i>
<a href="{{ pathto(master_doc) }}">{{ project }}</a>
{%- endblock %}
</nav>
<div class="wy-nav-content">
{%- block content %}
{%- if theme_style_external_links|tobool %}
<div class="rst-content style-external-links">
{%- else %}
<div class="rst-content">
{%- endif %}
{% include "breadcrumbs.html" %}
<div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
{%- block document %}
<div itemprop="articleBody">
{% block body %}{% endblock %}
</div>
{%- if self.comments()|trim %}
<div class="articleComments">
{%- block comments %}{% endblock %}
</div>
{%- endif%}
</div> </div>
{%- endblock %} {%- if render_sidebar %}
{% include "footer.html" %}
</div> </div>
{%- endblock %} {%- endif %}
</div> </div>
</section> {%- endblock %}
</div>
{% include "versions.html" -%}
<script> {%- block sidebar2 %}{{ sidebar() }}{% endblock %}
jQuery(function () { <div class="clearer"></div>
SphinxRtdTheme.Navigation.enable({{ 'true' if theme_sticky_navigation|tobool else 'false' }}); </div>
}); {%- endblock %}
</script>
{#- Do not conflict with RTD insertion of analytics script #} {%- block relbar2 %}{{ relbar() }}{% endblock %}
{%- if not READTHEDOCS %}
{%- if theme_analytics_id %}
<!-- Theme Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id={{ theme_analytics_id }}"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '{{ theme_analytics_id }}', {
'anonymize_ip': {{ 'true' if theme_analytics_anonymize_ip|tobool else 'false' }},
});
</script>
{%- block footer %}
<div class="footer">
{%- if show_copyright %}
{%- if hasdoc('copyright') %}
{% trans path=pathto('copyright'), copyright=copyright|e %}&copy; <a href="{{ path }}">Copyright</a> {{ copyright }}.{% endtrans %}
{%- else %}
{% trans copyright=copyright|e %}&copy; Copyright {{ copyright }}.{% endtrans %}
{%- endif %}
{%- endif %} {%- endif %}
{%- endif %} {%- if last_updated %}
{% trans last_updated=last_updated|e %}Last updated on {{ last_updated }}.{% endtrans %}
{%- block footer %} {% endblock %} {%- endif %}
{%- if show_sphinx %}
</body> {% 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> </html>

View File

@@ -39,7 +39,7 @@ as well as the type of underlying hardware. Example:
"rsa_pubkey": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqh…nswIDAQAB\n-----END PUBLIC KEY-----\n" "rsa_pubkey": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqh…nswIDAQAB\n-----END PUBLIC KEY-----\n"
} }
The ``rsa_pubkey`` is optional any only required for certain features such as working with reusable The ``rsa_pubkey`` is optional any only required for certain fatures such as working with reusable
media and NFC cryptography. media and NFC cryptography.
Every initialization token can only be used once. On success, you will receive a response containing Every initialization token can only be used once. On success, you will receive a response containing
@@ -208,6 +208,20 @@ Additionally, when creating a device through the user interface or API, a user c
the device. These include an allow list of specific API calls that may be made by the device. pretix ships with security the device. These include an allow list of specific API calls that may be made by the device. pretix ships with security
policies for official pretix apps like pretixSCAN and pretixPOS. policies for official pretix apps like pretixSCAN and pretixPOS.
Removing a device
-----------------
If you want implement a way to to deprovision a device in your software, you can call the ``revoke`` endpoint to
invalidate your API key. There is no way to reverse this operation.
.. sourcecode:: http
POST /api/v1/device/revoke HTTP/1.1
Host: pretix.eu
Authorization: Device 1kcsh572fonm3hawalrncam4l1gktr2rzx25a22l8g9hx108o9oi0rztpcvwnfnd
This can also be done by the user through the web interface.
Event selection Event selection
--------------- ---------------

View File

@@ -117,7 +117,7 @@ List-level conditional fetching
If modification checks are not possible with this granularity, you can instead check for the full list. If modification checks are not possible with this granularity, you can instead check for the full list.
In this case, the list of objects may contain a regular HTTP header ``Last-Modified`` with the date of the In this case, the list of objects may contain a regular HTTP header ``Last-Modified`` with the date of the
last modification to any item of that resource. You can then pass this date back in your next request in the last modification to any item of that resource. You can then pass this date back in your next request in the
``If-Modified-Since`` header. If any object has changed in the meantime, you will receive back a full list ``If-Modified-Since`` header. If the any object has changed in the meantime, you will receive back a full list
(if something it missing, this means the object has been deleted). If nothing happened, we'll send back a (if something it missing, this means the object has been deleted). If nothing happened, we'll send back a
``304 Not Modified`` return code. ``304 Not Modified`` return code.

View File

@@ -421,94 +421,3 @@ Annulment of a check-in
:statuscode 401: Authentication failure :statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource. :statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
:statuscode 404: The requested nonce does not exist. :statuscode 404: The requested nonce does not exist.
Check-in history
----------------
.. rst-class:: rest-resource-table
===================================== ========================== =======================================================
Field Type Description
===================================== ========================== =======================================================
id integer Internal ID of the check-in
successful boolean Whether the check-in was successful
error_reason string Category of reason why the check-in was unsuccessful. Currently
``"canceled"``, ``"invalid"``, ``"unpaid"`` ``"product"``,
``"rules"``, ``"revoked"``, ``"incomplete"``, ``"already_redeemed"``,
``"ambiguous"``, ``"error"``, ``"blocked"``, ``"unapproved"``,
``"invalid_time"``, ``"annulled"`` or ``null``
error_explanation string Additional, human-readable reason for the check-in to be unsuccessful (or ``null``)
position integer Internal ID of the order position (or ``null`` for unknown scans)
datetime datetime Logical time when the check-in happened
created datetime Time when the check-in appeared on the server
list integer Internal ID of the check-in list
auto_checked_in boolean Whether the check-in was performed by the system automatically
gate integer Internal ID of the gate (or ``null``)
device integer Internal ID of the device (or ``null``)
device_id integer Organizer-internal ID of the device (or ``null``)
type string Type of check-in, currently ``"entry"`` or ``"exit"``
===================================== ========================== =======================================================
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/checkins/
Returns a list of all check-in events within a given event.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/checkins/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 1,
"successful": true,
"error_reason": null,
"error_explanation": null,
"position": 1234,
"datetime": "2017-12-25T12:45:23Z",
"created": "2017-12-25T12:45:23Z",
"list": 2,
"auto_checked_in": false,
"gate": null,
"device": null,
"device_id": null,
"type": "entry",
}
]
}
:query integer page: The page number in case of a multi-page result set, default is 1
:query datetime created_since: Only return check-ins that have been created since the given date (inclusive).
:query datetime created_before: Only return check-ins that have been created before the given date (exclusive).
:query datetime datetime_since: Only return check-ins that have happened since the given date (inclusive).
:query datetime datetime_before: Only return check-ins that have happened before the given date (exclusive).
:query boolean successful: Only return check-ins that have (not) been successful.
:query boolean error_reason: Only return check-ins with a specific error reason.
:query integer list: Only return check-ins from a specific list.
:query string type: Only return check-ins of a specific type.
:query integer gate: Only return check-ins from a specific gate.
:query integer device: Only return check-ins from a specific device.
:query boolean auto_checked_in: Only return check-ins that are (not) auto-checked in.
:query string ordering: Manually set the ordering of results. Valid fields to be used are ``datetime``, ``created``,
and ``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.

View File

@@ -19,7 +19,6 @@ at :ref:`plugin-docs`.
item_bundles item_bundles
item_add-ons item_add-ons
item_meta_properties item_meta_properties
item_program_times
questions questions
question_options question_options
quotas quotas

View File

@@ -22,7 +22,6 @@ invoice_from_name string Sender address:
invoice_from string Sender address: Address lines invoice_from string Sender address: Address lines
invoice_from_zipcode string Sender address: ZIP code invoice_from_zipcode string Sender address: ZIP code
invoice_from_city string Sender address: City invoice_from_city string Sender address: City
invoice_from_state string Sender address: State (only used in some countries)
invoice_from_country string Sender address: Country code invoice_from_country string Sender address: Country code
invoice_from_tax_id string Sender address: Local Tax ID invoice_from_tax_id string Sender address: Local Tax ID
invoice_from_vat_id string Sender address: EU VAT ID invoice_from_vat_id string Sender address: EU VAT ID
@@ -81,12 +80,17 @@ lines list of objects The actual invo
for all invoice lines for all invoice lines
created before this field was introduced as well as for created before this field was introduced as well as for
all lines not created by a fee (e.g. a product). all lines not created by a fee (e.g. a product).
period_start datetime Start date of the service or delivery period of the invoice line. event_date_from datetime Start date of the (sub)event this line was created for as it
Can be ``null`` if not known. was set during invoice creation. Can be ``null`` for all invoice
├ period_end datetime End date of the service or delivery period of the invoice line. lines created before this was introduced as well as for lines in
Can be ``null`` if not known. an event series not created by a product (e.g. shipping or
├ event_date_from datetime Deprecated alias of ``period_start``. cancellation fees).
├ event_date_to datetime Deprecated alias of ``period_end``. ├ event_date_to datetime End date of the (sub)event this line was created for as it
was set during invoice creation. Can be ``null`` for all invoice
lines created before this was introduced as well as for lines in
an event series not created by a product (e.g. shipping or
cancellation fees) as well as whenever the respective (sub)event
has no end date set.
├ event_location string Location of the (sub)event this line was created for as it ├ event_location string Location of the (sub)event this line was created for as it
was set during invoice creation. Can be ``null`` for all invoice was set during invoice creation. Can be ``null`` for all invoice
lines created before this was introduced as well as for lines in lines created before this was introduced as well as for lines in
@@ -159,10 +163,10 @@ transmission_email_address string Optional. An em
Business customers only. Business customers only.
===================================== ========================== ======================================================= ===================================== ========================== =======================================================
Peppol PEPPOL
"""""" """"""
The identifier ``"peppol"`` represents the transmission of XML invoices through the `Peppol`_ network. The identifier ``"peppol"`` represents the transmission of XML invoices through the `PEPPOL`_ network.
This is only available for business addresses. This is only available for business addresses.
This is not supported by pretix out of the box and requires the use of a suitable plugin. This is not supported by pretix out of the box and requires the use of a suitable plugin.
The ``transmission_info`` object may contain the following properties: The ``transmission_info`` object may contain the following properties:
@@ -172,7 +176,7 @@ The ``transmission_info`` object may contain the following properties:
===================================== ========================== ======================================================= ===================================== ========================== =======================================================
Field Type Description Field Type Description
===================================== ========================== ======================================================= ===================================== ========================== =======================================================
transmission_peppol_participant_id string Required. The Peppol participant ID of the recipient. transmission_peppol_participant_id string Required. The PEPPOL participant ID of the recipient.
===================================== ========================== ======================================================= ===================================== ========================== =======================================================
Italian Exchange System Italian Exchange System
@@ -234,7 +238,6 @@ List of all invoices
"invoice_from": "Demo street 12", "invoice_from": "Demo street 12",
"invoice_from_zipcode":"", "invoice_from_zipcode":"",
"invoice_from_city":"Demo town", "invoice_from_city":"Demo town",
"invoice_from_state":"CA",
"invoice_from_country":"US", "invoice_from_country":"US",
"invoice_from_tax_id":"", "invoice_from_tax_id":"",
"invoice_from_vat_id":"", "invoice_from_vat_id":"",
@@ -271,8 +274,6 @@ List of all invoices
"fee_internal_type": null, "fee_internal_type": null,
"event_date_from": "2017-12-27T10:00:00Z", "event_date_from": "2017-12-27T10:00:00Z",
"event_date_to": null, "event_date_to": null,
"period_start": "2017-12-27T10:00:00Z",
"period_end": "2017-12-27T10:00:00Z",
"event_location": "Heidelberg", "event_location": "Heidelberg",
"attendee_name": null, "attendee_name": null,
"gross_value": "23.00", "gross_value": "23.00",
@@ -383,7 +384,6 @@ Fetching individual invoices
"invoice_from": "Demo street 12", "invoice_from": "Demo street 12",
"invoice_from_zipcode":"", "invoice_from_zipcode":"",
"invoice_from_city":"Demo town", "invoice_from_city":"Demo town",
"invoice_from_state":"CA",
"invoice_from_country":"US", "invoice_from_country":"US",
"invoice_from_tax_id":"", "invoice_from_tax_id":"",
"invoice_from_vat_id":"", "invoice_from_vat_id":"",
@@ -420,8 +420,6 @@ Fetching individual invoices
"fee_internal_type": null, "fee_internal_type": null,
"event_date_from": "2017-12-27T10:00:00Z", "event_date_from": "2017-12-27T10:00:00Z",
"event_date_to": null, "event_date_to": null,
"period_start": "2017-12-27T10:00:00Z",
"period_end": "2017-12-27T10:00:00Z",
"event_location": "Heidelberg", "event_location": "Heidelberg",
"attendee_name": null, "attendee_name": null,
"gross_value": "23.00", "gross_value": "23.00",
@@ -607,5 +605,5 @@ but in other cases transmission may need to be triggered manually.
:statuscode 409: The invoice is currently in transmission :statuscode 409: The invoice is currently in transmission
.. _Peppol: https://en.wikipedia.org/wiki/PEPPOL .. _PEPPOL: https://en.wikipedia.org/wiki/PEPPOL
.. _Sistema di Interscambio: https://it.wikipedia.org/wiki/Fattura_elettronica_in_Italia .. _Sistema di Interscambio: https://it.wikipedia.org/wiki/Fattura_elettronica_in_Italia

View File

@@ -1,223 +0,0 @@
Item program times
==================
Resource description
--------------------
Program times for products (items) that can be set in addition to event times, e.g. to display seperate schedules within an event.
Note that ``program_times`` are not available for items inside event series.
The program times resource contains the following public fields:
.. rst-class:: rest-resource-table
===================================== ========================== =======================================================
Field Type Description
===================================== ========================== =======================================================
id integer Internal ID of the program time
start datetime The start date time for this program time slot.
end datetime The end date time for this program time slot.
===================================== ========================== =======================================================
.. versionchanged:: TODO
The resource has been added.
Endpoints
---------
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/items/(item)/program_times/
Returns a list of all program times for a given item.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/items/11/program_times/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"count": 3,
"next": null,
"previous": null,
"results": [
{
"id": 2,
"start": "2025-08-14T22:00:00Z",
"end": "2025-08-15T00:00:00Z"
},
{
"id": 3,
"start": "2025-08-12T22:00:00Z",
"end": "2025-08-13T22:00:00Z"
},
{
"id": 14,
"start": "2025-08-15T22:00:00Z",
"end": "2025-08-17T22:00:00Z"
}
]
}
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch
:param item: The ``id`` field of the item to fetch
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event/item does not exist **or** you have no permission to view this resource.
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/items/(item)/program_times/(id)/
Returns information on one program time, identified by its ID.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/sampleconf/items/1/program_times/1/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"id": 1,
"start": "2025-08-15T22:00:00Z",
"end": "2025-10-27T23:00:00Z"
}
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch
:param item: The ``id`` field of the item to fetch
:param id: The ``id`` field of the program time 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:post:: /api/v1/organizers/(organizer)/events/(event)/items/(item)/program_times/
Creates a new program time
**Example request**:
.. sourcecode:: http
POST /api/v1/organizers/bigevents/events/sampleconf/items/1/program_times/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
{
"start": "2025-08-15T10:00:00Z",
"end": "2025-08-15T22:00:00Z"
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 201 Created
Vary: Accept
Content-Type: application/json
{
"id": 17,
"start": "2025-08-15T10:00:00Z",
"end": "2025-08-15T22:00:00Z"
}
:param organizer: The ``slug`` field of the organizer of the event/item to create a program time for
:param event: The ``slug`` field of the event to create a program time for
:param item: The ``id`` field of the item to create a program time for
:statuscode 201: no error
:statuscode 400: The program time could not be created due to invalid submitted data.
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to create this resource.
.. http:patch:: /api/v1/organizers/(organizer)/events/(event)/items/(item)/program_times/(id)/
Update a program time. You can also use ``PUT`` instead of ``PATCH``. With ``PUT``, you have to provide all fields of
the resource, other fields will be reset to default. With ``PATCH``, you only need to provide the fields that you
want to change.
You can change all fields of the resource except the ``id`` field.
**Example request**:
.. sourcecode:: http
PATCH /api/v1/organizers/bigevents/events/sampleconf/items/1/program_times/1/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
Content-Length: 94
{
"start": "2025-08-14T10:00:00Z"
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"id": 1,
"start": "2025-08-14T10:00:00Z",
"end": "2025-08-15T12:00:00Z"
}
:param organizer: The ``slug`` field of the organizer to modify
:param event: The ``slug`` field of the event to modify
:param id: The ``id`` field of the item to modify
:param id: The ``id`` field of the program time to modify
:statuscode 200: no error
:statuscode 400: The program time could not be modified due to invalid submitted data
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to change this resource.
.. http:delete:: /api/v1/organizers/(organizer)/events/(event)/items/(id)/program_times/(id)/
Delete a program time.
**Example request**:
.. sourcecode:: http
DELETE /api/v1/organizers/bigevents/events/sampleconf/items/1/program_times/1/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 204 No Content
Vary: Accept
:param organizer: The ``slug`` field of the organizer to modify
:param event: The ``slug`` field of the event to modify
:param id: The ``id`` field of the item to modify
:param id: The ``id`` field of the program time to delete
:statuscode 204: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to delete this resource.

View File

@@ -139,10 +139,6 @@ has_variations boolean Shows whether
variations list of objects A list with one object for each variation of this item. variations list of objects A list with one object for each variation of this item.
Can be empty. Only writable during creation, Can be empty. Only writable during creation,
use separate endpoint to modify this later. use separate endpoint to modify this later.
program_times list of objects A list with one object for each program time of this item.
Can be empty. Only writable during creation,
use separate endpoint to modify this later.
Not available for items in event series.
├ id integer Internal ID of the variation ├ id integer Internal ID of the variation
├ value multi-lingual string The "name" of the variation ├ value multi-lingual string The "name" of the variation
├ default_price money (string) The price set directly for this variation or ``null`` ├ default_price money (string) The price set directly for this variation or ``null``
@@ -229,10 +225,6 @@ meta_data object Values set fo
The ``hidden_if_item_available_mode`` attributes has been added. The ``hidden_if_item_available_mode`` attributes has been added.
.. versionchanged:: 2025.9
The ``program_times`` attribute has been added.
Notes Notes
----- -----
@@ -240,11 +232,9 @@ Please note that an item either always has variations or never has. Once created
change to an item without and vice versa. To create an item with variations ensure that you POST an item with at least change to an item without and vice versa. To create an item with variations ensure that you POST an item with at least
one variation. one variation.
Also note that ``variations``, ``bundles``, ``addons`` and ``program_times`` are only supported on ``POST``. To update/delete variations, Also note that ``variations``, ``bundles``, and ``addons`` are only supported on ``POST``. To update/delete variations,
bundles, add-ons and program times please use the dedicated nested endpoints. By design this endpoint does not support ``PATCH`` and ``PUT`` bundles, and add-ons please use the dedicated nested endpoints. By design this endpoint does not support ``PATCH`` and ``PUT``
with nested ``variations``, ``bundles``, ``addons`` and/or ``program_times``. with nested ``variations``, ``bundles`` and/or ``addons``.
``program_times`` is not available to items in event series.
Endpoints Endpoints
--------- ---------
@@ -383,8 +373,7 @@ Endpoints
} }
], ],
"addons": [], "addons": [],
"bundles": [], "bundles": []
"program_times": []
} }
] ]
} }
@@ -536,8 +525,7 @@ Endpoints
} }
], ],
"addons": [], "addons": [],
"bundles": [], "bundles": []
"program_times": []
} }
:param organizer: The ``slug`` field of the organizer to fetch :param organizer: The ``slug`` field of the organizer to fetch
@@ -665,13 +653,7 @@ Endpoints
} }
], ],
"addons": [], "addons": [],
"bundles": [], "bundles": []
"program_times": [
{
"start": "2025-08-14T22:00:00Z",
"end": "2025-08-15T00:00:00Z"
}
]
} }
**Example response**: **Example response**:
@@ -791,13 +773,7 @@ Endpoints
} }
], ],
"addons": [], "addons": [],
"bundles": [], "bundles": []
"program_times": [
{
"start": "2025-08-14T22:00:00Z",
"end": "2025-08-15T00:00:00Z"
}
]
} }
:param organizer: The ``slug`` field of the organizer of the event to create an item for :param organizer: The ``slug`` field of the organizer of the event to create an item for
@@ -813,9 +789,8 @@ Endpoints
the resource, other fields will be reset to default. With ``PATCH``, you only need to provide the fields that you the resource, other fields will be reset to default. With ``PATCH``, you only need to provide the fields that you
want to change. want to change.
You can change all fields of the resource except the ``has_variations``, ``variations``, ``addon`` and the You can change all fields of the resource except the ``has_variations``, ``variations`` and the ``addon`` field. If
``program_times`` field. If you need to update/delete variations, add-ons or program times, please use the nested you need to update/delete variations or add-ons please use the nested dedicated endpoints.
dedicated endpoints.
**Example request**: **Example request**:
@@ -949,8 +924,7 @@ Endpoints
} }
], ],
"addons": [], "addons": [],
"bundles": [], "bundles": []
"program_times": []
} }
:param organizer: The ``slug`` field of the organizer to modify :param organizer: The ``slug`` field of the organizer to modify

View File

@@ -41,7 +41,6 @@ expires datetime The order will
payment_date date **DEPRECATED AND INACCURATE** Date of payment receipt payment_date date **DEPRECATED AND INACCURATE** Date of payment receipt
payment_provider string **DEPRECATED AND INACCURATE** Payment provider used for this order payment_provider string **DEPRECATED AND INACCURATE** Payment provider used for this order
total money (string) Total value of this order total money (string) Total value of this order
tax_rounding_mode string Tax rounding mode, see :ref:`algorithms-rounding`
comment string Internal comment on this order comment string Internal comment on this order
api_meta object Meta data for that order. Only available through API, no guarantees api_meta object Meta data for that order. Only available through API, no guarantees
on the content structure. You can use this to save references to your system. on the content structure. You can use this to save references to your system.
@@ -152,10 +151,6 @@ plugin_data object Additional data
The ``invoice_address.transmission_type`` and ``invoice_address.transmission_info`` attributes have been added. The ``invoice_address.transmission_type`` and ``invoice_address.transmission_info`` attributes have been added.
.. versionchanged:: 2025.10
The ``tax_rounding_mode`` attribute has been added.
.. _order-position-resource: .. _order-position-resource:
Order position resource Order position resource
@@ -363,7 +358,6 @@ List of all orders
"payment_provider": "banktransfer", "payment_provider": "banktransfer",
"fees": [], "fees": [],
"total": "23.00", "total": "23.00",
"tax_rounding_mode": "line",
"comment": "", "comment": "",
"custom_followup_at": null, "custom_followup_at": null,
"checkin_attention": false, "checkin_attention": false,
@@ -424,7 +418,6 @@ List of all orders
"seat": null, "seat": null,
"checkins": [ "checkins": [
{ {
"id": 1337,
"list": 44, "list": 44,
"type": "entry", "type": "entry",
"gate": null, "gate": null,
@@ -608,7 +601,6 @@ Fetching individual orders
"payment_provider": "banktransfer", "payment_provider": "banktransfer",
"fees": [], "fees": [],
"total": "23.00", "total": "23.00",
"tax_rounding_mode": "line",
"comment": "", "comment": "",
"api_meta": {}, "api_meta": {},
"custom_followup_at": null, "custom_followup_at": null,
@@ -670,7 +662,6 @@ Fetching individual orders
"seat": null, "seat": null,
"checkins": [ "checkins": [
{ {
"id": 1337,
"list": 44, "list": 44,
"type": "entry", "type": "entry",
"gate": null, "gate": null,
@@ -1018,7 +1009,6 @@ Creating orders
provider will not be called to do anything about this (i.e. if you pass a bank account to a debit provider, *no* provider will not be called to do anything about this (i.e. if you pass a bank account to a debit provider, *no*
charge will be created), this is just informative in case you *handled the payment already*. charge will be created), this is just informative in case you *handled the payment already*.
* ``payment_date`` (optional) Date and time of the completion of the payment. * ``payment_date`` (optional) Date and time of the completion of the payment.
* ``tax_rounding_mode`` (optional)
* ``comment`` (optional) * ``comment`` (optional)
* ``custom_followup_at`` (optional) * ``custom_followup_at`` (optional)
* ``checkin_attention`` (optional) * ``checkin_attention`` (optional)
@@ -1038,8 +1028,8 @@ Creating orders
* ``internal_reference`` * ``internal_reference``
* ``vat_id`` * ``vat_id``
* ``vat_id_validated`` (optional) If you need support for reverse charge (rarely the case), you need to check * ``vat_id_validated`` (optional) If you need support for reverse charge (rarely the case), you need to check
yourself if the passed VAT ID is a valid EU VAT ID. In that case, set this to ``true``. Only valid VAT IDs will yourself if the passed VAT ID is a valid EU VAT ID. In that case, set this to ``true``. Only valid VAT IDs will
trigger reverse charge taxation. Don't forget to set ``is_business`` as well! trigger reverse charge taxation. Don't forget to set ``is_business`` as well!
* ``transmission_type`` (optional, defaults to ``email``) * ``transmission_type`` (optional, defaults to ``email``)
* ``transmission_info`` (optional, see also :ref:`rest-transmission-types`) * ``transmission_info`` (optional, see also :ref:`rest-transmission-types`)
@@ -1066,7 +1056,6 @@ Creating orders
* ``valid_until`` (optional, if both ``valid_from`` and ``valid_until`` are **missing** (not ``null``) the availability will be computed from the given product) * ``valid_until`` (optional, if both ``valid_from`` and ``valid_until`` are **missing** (not ``null``) the availability will be computed from the given product)
* ``requested_valid_from`` (optional, can be set **instead** of ``valid_from`` and ``valid_until`` to signal a user choice for the start time that may or may not be respected) * ``requested_valid_from`` (optional, can be set **instead** of ``valid_from`` and ``valid_until`` to signal a user choice for the start time that may or may not be respected)
* ``use_reusable_medium`` (optional, causes the new ticket to take over the given reusable medium, identified by its ID) * ``use_reusable_medium`` (optional, causes the new ticket to take over the given reusable medium, identified by its ID)
* ``discount`` (optional, only possible if ``price`` is set; attention: if this is set to not-``null`` on any position, automatic calculation of discounts will not run)
* ``answers`` * ``answers``
* ``question`` * ``question``
@@ -1643,7 +1632,6 @@ List of all order positions
"blocked": null, "blocked": null,
"checkins": [ "checkins": [
{ {
"id": 1337,
"list": 44, "list": 44,
"type": "entry", "type": "entry",
"gate": null, "gate": null,
@@ -1772,7 +1760,6 @@ Fetching individual positions
"seat": null, "seat": null,
"checkins": [ "checkins": [
{ {
"id": 1337,
"list": 44, "list": 44,
"type": "entry", "type": "entry",
"gate": null, "gate": null,
@@ -2517,7 +2504,6 @@ Order payment endpoints
{ {
"amount": "23.00", "amount": "23.00",
"comment": "Overpayment",
"mark_canceled": false "mark_canceled": false
} }

View File

@@ -60,9 +60,6 @@ The following values for ``action_types`` are valid with pretix core:
* ``pretix.event.added`` * ``pretix.event.added``
* ``pretix.event.changed`` * ``pretix.event.changed``
* ``pretix.event.deleted`` * ``pretix.event.deleted``
* ``pretix.giftcards.created``
* ``pretix.giftcards.modified``
* ``pretix.giftcards.transaction.*``
* ``pretix.voucher.added`` * ``pretix.voucher.added``
* ``pretix.voucher.changed`` * ``pretix.voucher.changed``
* ``pretix.voucher.deleted`` * ``pretix.voucher.deleted``

View File

@@ -178,124 +178,3 @@ Flowchart
--------- ---------
.. image:: /images/cart_pricing.png .. image:: /images/cart_pricing.png
.. _`algorithms-rounding`:
Rounding of taxes
-----------------
pretix internally always stores taxes on a per-line level, like this:
========== ========== =========== ======= =============
Product Tax rate Net price Tax Gross price
========== ========== =========== ======= =============
Ticket A 19 % 84.03 15.97 100.00
Ticket B 19 % 84.03 15.97 100.00
Ticket C 19 % 84.03 15.97 100.00
Ticket D 19 % 84.03 15.97 100.00
Ticket E 19 % 84.03 15.97 100.00
Sum 420.15 79.85 500.00
========== ========== =========== ======= =============
Whether the net price is computed from the gross price or vice versa is configured on the tax rule and may differ for every line.
The line-based computation has a few significant advantages:
- We can report both net and gross prices for every individual ticket.
- We can report both net and gross prices for every filter imaginable, such as the gross sum of all sales of Ticket A
or the net sum of all sales for a specific date in an event series. All numbers will be exact.
- When splitting the order into two, both net price and gross price are split without any changes in rounding.
The main disadvantage is that the tax looks "wrong" when computed from the sum. Taking the sum of net prices (420.15)
and multiplying it with the tax rate (19%) yields a tax amount of 79.83 (instead of 79.85) and a gross sum of 499.98
(instead of 500.00). This becomes a problem when juristictions, data formats, or external systems expect this calculation
to work on the level of the entire order. A prominent example is the EN 16931 standard for e-invoicing that
does not allow the computation as created by pretix.
However, calculating the tax rate from the net total has significant disadvantages:
- It is impossible to guarantee a stable gross price this way, i.e. if you advertise a price of €100 per ticket to
consumers, they will be confused when they only need to pay €499.98 for 5 tickets.
- Some prices are impossible, e.g. you cannot sell a ticket for a gross price of €99.99 at a 19% tax rate, since there
is no two-decimal net price that would be computed to a gross price of €99.99.
- When splitting an order into two, the combined of the new orders is not guaranteed to be the same as the total of the
original order. Therefore, additional payments or refunds of very small amounts might be necessary.
To allow organizers to make their own choices on this matter, pretix provides the following options:
Compute taxes for every line individually
"""""""""""""""""""""""""""""""""""""""""
Algorithm identifier: ``line``
This is our original algorithm where the tax value is rounded for every line individually.
**This is our current default algorithm and we recommend it whenever you do not have different requirements** (see below).
For the example above:
========== ========== =========== ======= =============
Product Tax rate Net price Tax Gross price
========== ========== =========== ======= =============
Ticket A 19 % 84.03 15.97 100.00
Ticket B 19 % 84.03 15.97 100.00
Ticket C 19 % 84.03 15.97 100.00
Ticket D 19 % 84.03 15.97 100.00
Ticket E 19 % 84.03 15.97 100.00
Sum 420.15 79.85 500.00
========== ========== =========== ======= =============
Compute taxes based on net total
""""""""""""""""""""""""""""""""
Algorithm identifier: ``sum_by_net``
In this algorithm, the tax value and gross total are computed from the sum of the net prices. To accomplish this within
our data model, the gross price and tax of some of the tickets will be changed by the minimum currency unit (e.g. €0.01).
The net price of the tickets always stay the same.
**This is the algorithm intended by EN 16931 invoices and our recommendation to use for e-invoicing when (primarily) business customers are involved.**
The main downside is that it might be confusing when selling to consumers, since the amounts to be paid change in unexpected ways.
For the example above, the customer expects to pay 5 times 100.00, but they are are in fact charged 499.98:
========== ========== =========== ============================== ==============================
Product Tax rate Net price Tax Gross price
========== ========== =========== ============================== ==============================
Ticket A 19 % 84.03 15.96 (incl. -0.01 rounding) 99.99 (incl. -0.01 rounding)
Ticket B 19 % 84.03 15.96 (incl. -0.01 rounding) 99.99 (incl. -0.01 rounding)
Ticket C 19 % 84.03 15.97 100.00
Ticket D 19 % 84.03 15.97 100.00
Ticket E 19 % 84.03 15.97 100.00
Sum 420.15 78.83 499.98
========== ========== =========== ============================== ==============================
Compute taxes based on net total with stable gross prices
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""
Algorithm identifier: ``sum_by_net_keep_gross``
In this algorithm, the tax value and gross total are computed from the sum of the net prices. However, the net prices
of some of the tickets will be changed automatically by the minimum currency unit (e.g. €0.01) such that the resulting
gross prices stay the same.
**This is less confusing to consumers and the end result is still compliant to EN 16931, so we recommend this for e-invoicing when (primarily) consumers are involved.**
The main downside is that it might be confusing when selling to business customers, since the prices of the identical tickets appear to be different.
Full computation for the example above:
========== ========== ============================= ============================== =============
Product Tax rate Net price Tax Gross price
========== ========== ============================= ============================== =============
Ticket A 19 % 84.04 (incl. 0.01 rounding) 15.96 (incl. -0.01 rounding) 100.00
Ticket B 19 % 84.04 (incl. 0.01 rounding) 15.96 (incl. -0.01 rounding) 100.00
Ticket C 19 % 84.03 15.97 100.00
Ticket D 19 % 84.03 15.97 100.00
Ticket E 19 % 84.03 15.97 100.00
Sum 420.17 79.83 500.00
========== ========== ============================= ============================== =============

View File

@@ -23,7 +23,7 @@ There are multiple signals that will be sent out in the ordering cycle:
.. automodule:: pretix.base.signals .. automodule:: pretix.base.signals
:no-index: :no-index:
:members: validate_cart, validate_cart_addons, validate_order, order_valid_if_pending, order_fee_calculation, order_paid, order_placed, order_canceled, order_reactivated, order_expired, order_expiry_changed, order_modified, order_changed, order_approved, order_denied, order_fee_type_name, allow_ticket_download, order_split, order_gracefully_delete, build_invoice_data, invoice_line_text :members: validate_cart, validate_cart_addons, validate_order, order_valid_if_pending, order_fee_calculation, order_paid, order_placed, order_canceled, order_reactivated, order_expired, order_expiry_changed, order_modified, order_changed, order_approved, order_denied, order_fee_type_name, allow_ticket_download, order_split, order_gracefully_delete, invoice_line_text
Check-ins Check-ins
""""""""" """""""""
@@ -37,7 +37,7 @@ Frontend
-------- --------
.. automodule:: pretix.presale.signals .. automodule:: pretix.presale.signals
:members: html_head, html_footer, footer_link, global_footer_link, front_page_top, front_page_bottom, front_page_bottom_widget, fee_calculation_for_cart, contact_form_fields, question_form_fields, contact_form_fields_overrides, question_form_fields_overrides, checkout_confirm_messages, checkout_confirm_page_content, checkout_all_optional, html_page_header, render_seating_plan, checkout_flow_steps, position_info, position_info_top, item_description, global_html_head, global_html_footer, global_html_page_header, seatingframe_html_head, filter_subevents :members: html_head, html_footer, footer_link, global_footer_link, front_page_top, front_page_bottom, front_page_bottom_widget, fee_calculation_for_cart, contact_form_fields, question_form_fields, contact_form_fields_overrides, question_form_fields_overrides, checkout_confirm_messages, checkout_confirm_page_content, checkout_all_optional, html_page_header, render_seating_plan, checkout_flow_steps, position_info, position_info_top, item_description, global_html_head, global_html_footer, global_html_page_header, seatingframe_html_head
.. automodule:: pretix.presale.signals .. automodule:: pretix.presale.signals

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 54 KiB

View File

@@ -23,7 +23,6 @@ partition "For every cart position" {
--> "Store as line_price (gross), tax_rate" --> "Store as line_price (gross), tax_rate"
} }
--> "Apply discount engine" --> "Apply discount engine"
--> "Apply tax rounding"
--> "Store as price (gross)" --> "Store as price (gross)"
@enduml @enduml

View File

@@ -1,8 +1,9 @@
sphinx==9.1.* sphinx==7.4.*
sphinx-rtd-theme~=3.1.0 jinja2==3.1.*
sphinxcontrib-httpdomain~=2.0.0 sphinx-rtd-theme
sphinxcontrib-images~=1.0.1 sphinxcontrib-httpdomain
sphinxcontrib-jquery~=4.1 sphinxcontrib-images
sphinxcontrib-spelling~=8.0.2 sphinxcontrib-jquery
sphinxemoji~=0.3.2 sphinxcontrib-spelling==8.*
pyenchant==3.3.* sphinxemoji
pyenchant==3.2.*

View File

@@ -1,9 +1,10 @@
-e ../ -e ../
sphinx==9.1.* sphinx==7.4.*
sphinx-rtd-theme~=3.1.0 jinja2==3.1.*
sphinxcontrib-httpdomain~=2.0.0 sphinx-rtd-theme
sphinxcontrib-images~=1.0.1 sphinxcontrib-httpdomain
sphinxcontrib-jquery~=4.1 sphinxcontrib-images
sphinxcontrib-spelling~=8.0.2 sphinxcontrib-jquery
sphinxemoji~=0.3.2 sphinxcontrib-spelling==8.*
pyenchant==3.3.* sphinxemoji
pyenchant==3.2.*

View File

@@ -3,7 +3,7 @@ name = "pretix"
dynamic = ["version"] dynamic = ["version"]
description = "Reinventing presales, one ticket at a time" description = "Reinventing presales, one ticket at a time"
readme = "README.rst" readme = "README.rst"
requires-python = ">=3.10" requires-python = ">=3.9"
license = {file = "LICENSE"} license = {file = "LICENSE"}
keywords = ["tickets", "web", "shop", "ecommerce"] keywords = ["tickets", "web", "shop", "ecommerce"]
authors = [ authors = [
@@ -28,44 +28,43 @@ classifiers = [
dependencies = [ dependencies = [
"arabic-reshaper==3.0.0", # Support for Arabic in reportlab "arabic-reshaper==3.0.0", # Support for Arabic in reportlab
"babel", "babel",
"BeautifulSoup4==4.14.*", "BeautifulSoup4==4.13.*",
"bleach==6.3.*", "bleach==6.2.*",
"celery==5.6.*", "celery==5.5.*",
"chardet==5.2.*", "chardet==5.2.*",
"cryptography>=44.0.0", "cryptography>=44.0.0",
"css-inline==0.20.*", "css-inline==0.17.*",
"defusedcsv>=1.1.0", "defusedcsv>=1.1.0",
"dnspython==2.*", "Django[argon2]==4.2.*,>=4.2.15",
"Django[argon2]==4.2.*,>=4.2.26", "django-bootstrap3==25.2",
"django-bootstrap3==26.1", "django-compressor==4.5.1",
"django-compressor==4.6.0", "django-countries==7.6.*",
"django-countries==8.2.*",
"django-filter==25.1", "django-filter==25.1",
"django-formset-js-improved==0.5.0.5", "django-formset-js-improved==0.5.0.3",
"django-formtools==2.5.1", "django-formtools==2.5.1",
"django-hierarkey==2.0.*,>=2.0.1", "django-hierarkey==2.0.*",
"django-hijack==3.7.*", "django-hijack==3.7.*",
"django-i18nfield==1.11.*", "django-i18nfield==1.10.*",
"django-libsass==0.9", "django-libsass==0.9",
"django-localflavor==5.0", "django-localflavor==5.0",
"django-markup", "django-markup",
"django-oauth-toolkit==2.3.*", "django-oauth-toolkit==2.3.*",
"django-otp==1.7.*", "django-otp==1.6.*",
"django-phonenumber-field==8.4.*", "django-phonenumber-field==7.3.*",
"django-redis==6.0.*", "django-redis==6.0.*",
"django-scopes==2.0.*", "django-scopes==2.0.*",
"django-statici18n==2.6.*", "django-statici18n==2.6.*",
"djangorestframework==3.16.*", "djangorestframework==3.16.*",
"dnspython==2.8.*", "dnspython==2.7.*",
"drf_ujson2==1.7.*", "drf_ujson2==1.7.*",
"geoip2==5.*", "geoip2==5.*",
"importlib_metadata==8.*", # Polyfill, we can probably drop this once we require Python 3.10+ "importlib_metadata==8.*", # Polyfill, we can probably drop this once we require Python 3.10+
"isoweek", "isoweek",
"jsonschema", "jsonschema",
"kombu==5.6.*", "kombu==5.5.*",
"libsass==0.23.*", "libsass==0.23.*",
"lxml", "lxml",
"markdown==3.10.2", # 3.3.5 requires importlib-metadata>=4.4, but django-bootstrap3 requires importlib-metadata<3. "markdown==3.8.2", # 3.3.5 requires importlib-metadata>=4.4, but django-bootstrap3 requires importlib-metadata<3.
# We can upgrade markdown again once django-bootstrap3 upgrades or once we drop Python 3.6 and 3.7 # We can upgrade markdown again once django-bootstrap3 upgrades or once we drop Python 3.6 and 3.7
"mt-940==4.30.*", "mt-940==4.30.*",
"oauthlib==3.3.*", "oauthlib==3.3.*",
@@ -73,57 +72,58 @@ dependencies = [
"packaging", "packaging",
"paypalrestsdk==1.13.*", "paypalrestsdk==1.13.*",
"paypal-checkout-serversdk==1.0.*", "paypal-checkout-serversdk==1.0.*",
"PyJWT==2.11.*", "PyJWT==2.10.*",
"phonenumberslite==9.0.*", "phonenumberslite==9.0.*",
"Pillow==12.1.*", "Pillow==11.3.*",
"pretix-plugin-build", "pretix-plugin-build",
"protobuf==6.33.*", "protobuf==6.32.*",
"psycopg2-binary", "psycopg2-binary",
"pycountry", "pycountry",
"pycparser==3.0", "pycparser==2.22",
"pycryptodome==3.23.*", "pycryptodome==3.23.*",
"pypdf==6.5.*", "pypdf==6.0.*",
"python-bidi==0.6.*", # Support for Arabic in reportlab "python-bidi==0.6.*", # Support for Arabic in reportlab
"python-dateutil==2.9.*", "python-dateutil==2.9.*",
"pytz", "pytz",
"pytz-deprecation-shim==0.1.*", "pytz-deprecation-shim==0.1.*",
"pyuca", "pyuca",
"qrcode==8.2", "qrcode==8.2",
"redis==7.1.*", "redis==6.4.*",
"reportlab==4.4.*", "reportlab==4.4.*",
"requests==2.32.*", "requests==2.31.*",
"sentry-sdk==2.52.*", "sentry-sdk==2.35.*",
"sepaxml==2.7.*", "sepaxml==2.6.*",
"stripe==7.9.*", "stripe==7.9.*",
"text-unidecode==1.*", "text-unidecode==1.*",
"tlds>=2020041600", "tlds>=2020041600",
"tqdm==4.*", "tqdm==4.*",
"ua-parser==1.0.*", "ua-parser==1.0.*",
"vat_moss_forked==2020.3.20.0.11.0",
"vobject==0.9.*", "vobject==0.9.*",
"webauthn==2.7.*", "webauthn==2.6.*",
"zeep==4.3.*" "zeep==4.3.*"
] ]
[project.optional-dependencies] [project.optional-dependencies]
memcached = ["pylibmc"] memcached = ["pylibmc"]
dev = [ dev = [
"aiohttp==3.13.*", "aiohttp==3.12.*",
"coverage", "coverage",
"coveralls", "coveralls",
"fakeredis==2.33.*", "fakeredis==2.31.*",
"flake8==7.3.*", "flake8==7.3.*",
"freezegun", "freezegun",
"isort==7.0.*", "isort==6.0.*",
"pep8-naming==0.15.*", "pep8-naming==0.15.*",
"potypo", "potypo",
"pytest-asyncio>=0.24", "pytest-asyncio>=0.24",
"pytest-cache", "pytest-cache",
"pytest-cov", "pytest-cov",
"pytest-django==4.*", "pytest-django==4.*",
"pytest-mock==3.15.*", "pytest-mock==3.14.*",
"pytest-sugar", "pytest-sugar",
"pytest-xdist==3.8.*", "pytest-xdist==3.8.*",
"pytest==9.0.*", "pytest==8.4.*",
"responses", "responses",
] ]

View File

@@ -25,8 +25,8 @@ coverage:
coverage run -m py.test coverage run -m py.test
npminstall: npminstall:
# keep this in sync with pretix/_build.py! # keep this in sync with setup.py!
mkdir -p pretix/static.dist/node_prefix/ mkdir -p pretix/static.dist/node_prefix/
cp -r pretix/static/npm_dir/* pretix/static.dist/node_prefix/ cp -r pretix/static/npm_dir/* pretix/static.dist/node_prefix/
npm ci --prefix=pretix/static.dist/node_prefix npm install --prefix=pretix/static.dist/node_prefix

View File

@@ -2,8 +2,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.
@@ -19,4 +19,4 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see # You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>. # <https://www.gnu.org/licenses/>.
# #
__version__ = "2026.2.0.dev0" __version__ = "2025.8.0.dev0"

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.
@@ -39,7 +39,7 @@ def npm_install():
node_prefix = os.path.join(here, 'static.dist', 'node_prefix') node_prefix = os.path.join(here, 'static.dist', 'node_prefix')
os.makedirs(node_prefix, exist_ok=True) os.makedirs(node_prefix, exist_ok=True)
shutil.copytree(os.path.join(here, 'static', 'npm_dir'), node_prefix, dirs_exist_ok=True) shutil.copytree(os.path.join(here, 'static', 'npm_dir'), node_prefix, dirs_exist_ok=True)
subprocess.check_call('npm ci', shell=True, cwd=node_prefix) subprocess.check_call('npm install', shell=True, cwd=node_prefix)
npm_installed = True npm_installed = True

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,23 +0,0 @@
# Generated by Django 4.2.24 on 2025-11-14 16:21
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("pretixapi", "0013_alter_webhookcallretry_retry_not_before"),
]
operations = [
migrations.AlterField(
model_name="webhook",
name="target_url",
field=models.URLField(max_length=1024),
),
migrations.AlterField(
model_name="webhookcall",
name="target_url",
field=models.URLField(max_length=1024),
),
]

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.
@@ -114,7 +114,7 @@ class OAuthRefreshToken(AbstractRefreshToken):
class WebHook(models.Model): class WebHook(models.Model):
organizer = models.ForeignKey('pretixbase.Organizer', on_delete=models.CASCADE, related_name='webhooks') organizer = models.ForeignKey('pretixbase.Organizer', on_delete=models.CASCADE, related_name='webhooks')
enabled = models.BooleanField(default=True, verbose_name=_("Enable webhook")) enabled = models.BooleanField(default=True, verbose_name=_("Enable webhook"))
target_url = models.URLField(verbose_name=_("Target URL"), max_length=1024) target_url = models.URLField(verbose_name=_("Target URL"), max_length=255)
all_events = models.BooleanField(default=True, verbose_name=_("All events (including newly created ones)")) all_events = models.BooleanField(default=True, verbose_name=_("All events (including newly created ones)"))
limit_events = models.ManyToManyField('pretixbase.Event', verbose_name=_("Limit to events"), blank=True) limit_events = models.ManyToManyField('pretixbase.Event', verbose_name=_("Limit to events"), blank=True)
comment = models.CharField(verbose_name=_("Comment"), max_length=255, null=True, blank=True) comment = models.CharField(verbose_name=_("Comment"), max_length=255, null=True, blank=True)
@@ -140,7 +140,7 @@ class WebHookEventListener(models.Model):
class WebHookCall(models.Model): class WebHookCall(models.Model):
webhook = models.ForeignKey('WebHook', on_delete=models.CASCADE, related_name='calls') webhook = models.ForeignKey('WebHook', on_delete=models.CASCADE, related_name='calls')
datetime = models.DateTimeField(auto_now_add=True) datetime = models.DateTimeField(auto_now_add=True)
target_url = models.URLField(max_length=1024) target_url = models.URLField(max_length=255)
action_type = models.CharField(max_length=255) action_type = models.CharField(max_length=255)
is_retry = models.BooleanField(default=False) is_retry = models.BooleanField(default=False)
execution_time = models.FloatField(null=True) execution_time = models.FloatField(null=True)

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.
@@ -795,7 +795,6 @@ class EventSettingsSerializer(SettingsSerializer):
'invoice_address_asked', 'invoice_address_asked',
'invoice_address_required', 'invoice_address_required',
'invoice_address_vatid', 'invoice_address_vatid',
'invoice_address_vatid_required_countries',
'invoice_address_company_required', 'invoice_address_company_required',
'invoice_address_beneficiary', 'invoice_address_beneficiary',
'invoice_address_custom_field', 'invoice_address_custom_field',
@@ -806,8 +805,6 @@ class EventSettingsSerializer(SettingsSerializer):
'invoice_reissue_after_modify', 'invoice_reissue_after_modify',
'invoice_include_free', 'invoice_include_free',
'invoice_generate', 'invoice_generate',
'invoice_generate_only_business',
'invoice_period',
'invoice_numbers_consecutive', 'invoice_numbers_consecutive',
'invoice_numbers_prefix', 'invoice_numbers_prefix',
'invoice_numbers_prefix_cancellations', 'invoice_numbers_prefix_cancellations',
@@ -822,7 +819,6 @@ class EventSettingsSerializer(SettingsSerializer):
'invoice_address_from', 'invoice_address_from',
'invoice_address_from_zipcode', 'invoice_address_from_zipcode',
'invoice_address_from_city', 'invoice_address_from_city',
'invoice_address_from_state',
'invoice_address_from_country', 'invoice_address_from_country',
'invoice_address_from_tax_id', 'invoice_address_from_tax_id',
'invoice_address_from_vat_id', 'invoice_address_from_vat_id',
@@ -832,7 +828,6 @@ class EventSettingsSerializer(SettingsSerializer):
'invoice_eu_currencies', 'invoice_eu_currencies',
'invoice_logo_image', 'invoice_logo_image',
'invoice_renderer_highlight_order_code', 'invoice_renderer_highlight_order_code',
'tax_rounding',
'cancel_allow_user', 'cancel_allow_user',
'cancel_allow_user_until', 'cancel_allow_user_until',
'cancel_allow_user_unpaid_keep', 'cancel_allow_user_unpaid_keep',
@@ -945,7 +940,6 @@ class DeviceEventSettingsSerializer(EventSettingsSerializer):
'invoice_address_asked', 'invoice_address_asked',
'invoice_address_required', 'invoice_address_required',
'invoice_address_vatid', 'invoice_address_vatid',
'invoice_address_vatid_required_countries',
'invoice_address_company_required', 'invoice_address_company_required',
'invoice_address_beneficiary', 'invoice_address_beneficiary',
'invoice_address_custom_field', 'invoice_address_custom_field',
@@ -956,7 +950,6 @@ class DeviceEventSettingsSerializer(EventSettingsSerializer):
'invoice_address_from', 'invoice_address_from',
'invoice_address_from_zipcode', 'invoice_address_from_zipcode',
'invoice_address_from_city', 'invoice_address_from_city',
'invoice_address_from_state',
'invoice_address_from_country', 'invoice_address_from_country',
'invoice_address_from_tax_id', 'invoice_address_from_tax_id',
'invoice_address_from_vat_id', 'invoice_address_from_vat_id',

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.
@@ -47,9 +47,8 @@ from pretix.api.serializers.event import MetaDataField
from pretix.api.serializers.fields import UploadedFileField from pretix.api.serializers.fields import UploadedFileField
from pretix.api.serializers.i18n import I18nAwareModelSerializer from pretix.api.serializers.i18n import I18nAwareModelSerializer
from pretix.base.models import ( from pretix.base.models import (
Item, ItemAddOn, ItemBundle, ItemCategory, ItemMetaValue, ItemProgramTime, Item, ItemAddOn, ItemBundle, ItemCategory, ItemMetaValue, ItemVariation,
ItemVariation, ItemVariationMetaValue, Question, QuestionOption, Quota, ItemVariationMetaValue, Question, QuestionOption, Quota, SalesChannel,
SalesChannel,
) )
@@ -188,12 +187,6 @@ class InlineItemAddOnSerializer(serializers.ModelSerializer):
'position', 'price_included', 'multi_allowed') 'position', 'price_included', 'multi_allowed')
class InlineItemProgramTimeSerializer(serializers.ModelSerializer):
class Meta:
model = ItemProgramTime
fields = ('start', 'end')
class ItemBundleSerializer(serializers.ModelSerializer): class ItemBundleSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = ItemBundle model = ItemBundle
@@ -219,37 +212,6 @@ class ItemBundleSerializer(serializers.ModelSerializer):
return data return data
class ItemProgramTimeSerializer(serializers.ModelSerializer):
class Meta:
model = ItemProgramTime
fields = ('id', 'start', 'end')
def validate(self, data):
data = super().validate(data)
full_data = self.to_internal_value(self.to_representation(self.instance)) if self.instance else {}
full_data.update(data)
start = full_data.get('start')
if not start:
raise ValidationError(_("The program start must not be empty."))
end = full_data.get('end')
if not end:
raise ValidationError(_("The program end must not be empty."))
if start > end:
raise ValidationError(_("The program end must not be before the program start."))
event = self.context['event']
if event.has_subevents:
raise ValidationError({
_("You cannot use program times on an event series.")
})
return data
class ItemAddOnSerializer(serializers.ModelSerializer): class ItemAddOnSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = ItemAddOn model = ItemAddOn
@@ -288,7 +250,6 @@ class ItemSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer):
addons = InlineItemAddOnSerializer(many=True, required=False) addons = InlineItemAddOnSerializer(many=True, required=False)
bundles = InlineItemBundleSerializer(many=True, required=False) bundles = InlineItemBundleSerializer(many=True, required=False)
variations = InlineItemVariationSerializer(many=True, required=False) variations = InlineItemVariationSerializer(many=True, required=False)
program_times = InlineItemProgramTimeSerializer(many=True, required=False)
tax_rate = ItemTaxRateField(source='*', read_only=True) tax_rate = ItemTaxRateField(source='*', read_only=True)
meta_data = MetaDataField(required=False, source='*') meta_data = MetaDataField(required=False, source='*')
picture = UploadedFileField(required=False, allow_null=True, allowed_types=( picture = UploadedFileField(required=False, allow_null=True, allowed_types=(
@@ -310,7 +271,7 @@ class ItemSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer):
'available_from', 'available_from_mode', 'available_until', 'available_until_mode', 'available_from', 'available_from_mode', 'available_until', 'available_until_mode',
'require_voucher', 'hide_without_voucher', 'allow_cancel', 'require_bundling', 'require_voucher', 'hide_without_voucher', 'allow_cancel', 'require_bundling',
'min_per_order', 'max_per_order', 'checkin_attention', 'checkin_text', 'has_variations', 'variations', 'min_per_order', 'max_per_order', 'checkin_attention', 'checkin_text', 'has_variations', 'variations',
'addons', 'bundles', 'program_times', 'original_price', 'require_approval', 'generate_tickets', 'addons', 'bundles', 'original_price', 'require_approval', 'generate_tickets',
'show_quota_left', 'hidden_if_available', 'hidden_if_item_available', 'hidden_if_item_available_mode', 'allow_waitinglist', 'show_quota_left', 'hidden_if_available', 'hidden_if_item_available', 'hidden_if_item_available_mode', 'allow_waitinglist',
'issue_giftcard', 'meta_data', 'issue_giftcard', 'meta_data',
'require_membership', 'require_membership_types', 'require_membership_hidden', 'grant_membership_type', 'require_membership', 'require_membership_types', 'require_membership_hidden', 'grant_membership_type',
@@ -333,9 +294,9 @@ class ItemSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer):
def validate(self, data): def validate(self, data):
data = super().validate(data) data = super().validate(data)
if self.instance and ('addons' in data or 'variations' in data or 'bundles' in data or 'program_times' in data): if self.instance and ('addons' in data or 'variations' in data or 'bundles' in data):
raise ValidationError(_('Updating add-ons, bundles, program times or variations via PATCH/PUT is not ' raise ValidationError(_('Updating add-ons, bundles, or variations via PATCH/PUT is not supported. Please use the '
'supported. Please use the dedicated nested endpoint.')) 'dedicated nested endpoint.'))
Item.clean_per_order(data.get('min_per_order'), data.get('max_per_order')) Item.clean_per_order(data.get('min_per_order'), data.get('max_per_order'))
Item.clean_available(data.get('available_from'), data.get('available_until')) Item.clean_available(data.get('available_from'), data.get('available_until'))
@@ -386,13 +347,6 @@ class ItemSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer):
ItemAddOn.clean_max_min_count(addon_data.get('max_count', 0), addon_data.get('min_count', 0)) ItemAddOn.clean_max_min_count(addon_data.get('max_count', 0), addon_data.get('min_count', 0))
return value return value
def validate_program_times(self, value):
if not self.instance:
for program_time_data in value:
ItemProgramTime.clean_start_end(self, start=program_time_data.get('start', None),
end=program_time_data.get('end', None))
return value
@cached_property @cached_property
def item_meta_properties(self): def item_meta_properties(self):
return { return {
@@ -410,7 +364,6 @@ class ItemSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer):
variations_data = validated_data.pop('variations') if 'variations' in validated_data else {} variations_data = validated_data.pop('variations') if 'variations' in validated_data else {}
addons_data = validated_data.pop('addons') if 'addons' in validated_data else {} addons_data = validated_data.pop('addons') if 'addons' in validated_data else {}
bundles_data = validated_data.pop('bundles') if 'bundles' in validated_data else {} bundles_data = validated_data.pop('bundles') if 'bundles' in validated_data else {}
program_times_data = validated_data.pop('program_times') if 'program_times' in validated_data else {}
meta_data = validated_data.pop('meta_data', None) meta_data = validated_data.pop('meta_data', None)
picture = validated_data.pop('picture', None) picture = validated_data.pop('picture', None)
require_membership_types = validated_data.pop('require_membership_types', []) require_membership_types = validated_data.pop('require_membership_types', [])
@@ -445,8 +398,6 @@ class ItemSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer):
ItemAddOn.objects.create(base_item=item, **addon_data) ItemAddOn.objects.create(base_item=item, **addon_data)
for bundle_data in bundles_data: for bundle_data in bundles_data:
ItemBundle.objects.create(base_item=item, **bundle_data) ItemBundle.objects.create(base_item=item, **bundle_data)
for program_time_data in program_times_data:
ItemProgramTime.objects.create(item=item, **program_time_data)
# Meta data # Meta data
if meta_data is not None: if meta_data is not None:
@@ -599,7 +550,7 @@ class QuestionSerializer(I18nAwareModelSerializer):
if full_data.get('show_during_checkin') and full_data.get('type') in Question.SHOW_DURING_CHECKIN_UNSUPPORTED: if full_data.get('show_during_checkin') and full_data.get('type') in Question.SHOW_DURING_CHECKIN_UNSUPPORTED:
raise ValidationError(_('This type of question cannot be shown during check-in.')) raise ValidationError(_('This type of question cannot be shown during check-in.'))
Question.clean_items(event, full_data.get('items') or []) Question.clean_items(event, full_data.get('items'))
return data return data
def validate_options(self, value): def validate_options(self, value):
@@ -615,7 +566,7 @@ class QuestionSerializer(I18nAwareModelSerializer):
@transaction.atomic @transaction.atomic
def create(self, validated_data): def create(self, validated_data):
options_data = validated_data.pop('options') if 'options' in validated_data else [] options_data = validated_data.pop('options') if 'options' in validated_data else []
items = validated_data.pop('items', []) items = validated_data.pop('items')
question = Question.objects.create(**validated_data) question = Question.objects.create(**validated_data)
question.items.set(items) question.items.set(items)

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.
@@ -52,10 +52,9 @@ from pretix.base.decimal import round_decimal
from pretix.base.i18n import language from pretix.base.i18n import language
from pretix.base.invoicing.transmission import get_transmission_types from pretix.base.invoicing.transmission import get_transmission_types
from pretix.base.models import ( from pretix.base.models import (
CachedFile, Checkin, Customer, Device, Invoice, InvoiceAddress, CachedFile, Checkin, Customer, Invoice, InvoiceAddress, InvoiceLine, Item,
InvoiceLine, Item, ItemVariation, Order, OrderPosition, Question, ItemVariation, Order, OrderPosition, Question, QuestionAnswer,
QuestionAnswer, ReusableMedium, SalesChannel, Seat, SubEvent, TaxRule, ReusableMedium, SalesChannel, Seat, SubEvent, TaxRule, Voucher,
Voucher,
) )
from pretix.base.models.orders import ( from pretix.base.models.orders import (
BlockedTicketSecret, CartPosition, OrderFee, OrderPayment, OrderRefund, BlockedTicketSecret, CartPosition, OrderFee, OrderPayment, OrderRefund,
@@ -65,13 +64,10 @@ from pretix.base.pdf import get_images, get_variables
from pretix.base.services.cart import error_messages from pretix.base.services.cart import error_messages
from pretix.base.services.locking import LOCK_TRUST_WINDOW, lock_objects from pretix.base.services.locking import LOCK_TRUST_WINDOW, lock_objects
from pretix.base.services.pricing import ( from pretix.base.services.pricing import (
apply_discounts, apply_rounding, get_line_price, get_listed_price, apply_discounts, get_line_price, get_listed_price, is_included_for_free,
is_included_for_free,
) )
from pretix.base.services.quotas import QuotaAvailability from pretix.base.services.quotas import QuotaAvailability
from pretix.base.settings import ( from pretix.base.settings import COUNTRIES_WITH_STATE_IN_ADDRESS
COUNTRIES_WITH_STATE_IN_ADDRESS, ROUNDING_MODES,
)
from pretix.base.signals import register_ticket_outputs from pretix.base.signals import register_ticket_outputs
from pretix.helpers.countries import CachedCountries from pretix.helpers.countries import CachedCountries
from pretix.multidomain.urlreverse import build_absolute_uri from pretix.multidomain.urlreverse import build_absolute_uri
@@ -191,7 +187,7 @@ class InvoiceAddressSerializer(I18nAwareModelSerializer):
{"transmission_info": {r: "This field is required for the selected type of invoice transmission."}} {"transmission_info": {r: "This field is required for the selected type of invoice transmission."}}
) )
break # do not call else branch of for loop break # do not call else branch of for loop
elif t.is_exclusive(self.context["request"].event, data.get("country"), data.get("is_business")): elif t.exclusive:
if t.is_available(self.context["request"].event, data.get("country"), data.get("is_business")): if t.is_available(self.context["request"].event, data.get("country"), data.get("is_business")):
raise ValidationError({ raise ValidationError({
"transmission_type": "The transmission type '%s' must be used for this country or address type." % ( "transmission_type": "The transmission type '%s' must be used for this country or address type." % (
@@ -329,18 +325,6 @@ class AnswerSerializer(I18nAwareModelSerializer):
return data return data
class InlineCheckinSerializer(I18nAwareModelSerializer):
device_id = serializers.SlugRelatedField(
source='device',
slug_field='device_id',
read_only=True,
)
class Meta:
model = Checkin
fields = ('id', 'datetime', 'list', 'auto_checked_in', 'gate', 'device', 'device_id', 'type')
class CheckinSerializer(I18nAwareModelSerializer): class CheckinSerializer(I18nAwareModelSerializer):
device_id = serializers.SlugRelatedField( device_id = serializers.SlugRelatedField(
source='device', source='device',
@@ -350,10 +334,7 @@ class CheckinSerializer(I18nAwareModelSerializer):
class Meta: class Meta:
model = Checkin model = Checkin
fields = ( fields = ('id', 'datetime', 'list', 'auto_checked_in', 'gate', 'device', 'device_id', 'type')
'id', 'successful', 'error_reason', 'error_explanation', 'position', 'datetime', 'list', 'created',
'auto_checked_in', 'gate', 'device', 'device_id', 'type'
)
class PrintLogSerializer(serializers.ModelSerializer): class PrintLogSerializer(serializers.ModelSerializer):
@@ -579,7 +560,7 @@ class OrderPositionPluginDataField(serializers.Field):
class OrderPositionSerializer(I18nAwareModelSerializer): class OrderPositionSerializer(I18nAwareModelSerializer):
checkins = InlineCheckinSerializer(many=True, read_only=True) checkins = CheckinSerializer(many=True, read_only=True)
print_logs = PrintLogSerializer(many=True, read_only=True) print_logs = PrintLogSerializer(many=True, read_only=True)
answers = AnswerSerializer(many=True) answers = AnswerSerializer(many=True)
downloads = PositionDownloadsField(source='*', read_only=True) downloads = PositionDownloadsField(source='*', read_only=True)
@@ -704,16 +685,6 @@ class CheckinListOrderPositionSerializer(OrderPositionSerializer):
if 'answers.question' in self.context['expand']: if 'answers.question' in self.context['expand']:
self.fields['answers'].child.fields['question'] = QuestionSerializer(read_only=True) self.fields['answers'].child.fields['question'] = QuestionSerializer(read_only=True)
if 'addons' in self.context['expand']:
# Experimental feature, undocumented on purpose for now in case we need to remove it again
# for performance reasons
subl = CheckinListOrderPositionSerializer(read_only=True, many=True, context={
**self.context,
'expand': [v for v in self.context['expand'] if v != 'addons'],
'pdf_data': False,
})
self.fields['addons'] = subl
class OrderPaymentTypeField(serializers.Field): class OrderPaymentTypeField(serializers.Field):
# TODO: Remove after pretix 2.2 # TODO: Remove after pretix 2.2
@@ -862,15 +833,14 @@ class OrderSerializer(I18nAwareModelSerializer):
list_serializer_class = OrderListSerializer list_serializer_class = OrderListSerializer
fields = ( fields = (
'code', 'event', 'status', 'testmode', 'secret', 'email', 'phone', 'locale', 'datetime', 'expires', 'payment_date', 'code', 'event', 'status', 'testmode', 'secret', 'email', 'phone', 'locale', 'datetime', 'expires', 'payment_date',
'payment_provider', 'fees', 'total', 'tax_rounding_mode', 'comment', 'custom_followup_at', 'invoice_address', 'payment_provider', 'fees', 'total', 'comment', 'custom_followup_at', 'invoice_address', 'positions', 'downloads',
'positions', 'downloads', 'checkin_attention', 'checkin_text', 'last_modified', 'payments', 'refunds', 'checkin_attention', 'checkin_text', 'last_modified', 'payments', 'refunds', 'require_approval', 'sales_channel',
'require_approval', 'sales_channel', 'url', 'customer', 'valid_if_pending', 'api_meta', 'cancellation_date', 'url', 'customer', 'valid_if_pending', 'api_meta', 'cancellation_date', 'plugin_data',
'plugin_data',
) )
read_only_fields = ( read_only_fields = (
'code', 'status', 'testmode', 'secret', 'datetime', 'expires', 'payment_date', 'code', 'status', 'testmode', 'secret', 'datetime', 'expires', 'payment_date',
'payment_provider', 'fees', 'total', 'tax_rounding_mode', 'positions', 'downloads', 'customer', 'payment_provider', 'fees', 'total', 'positions', 'downloads', 'customer',
'last_modified', 'payments', 'refunds', 'require_approval', 'sales_channel', 'cancellation_date', 'last_modified', 'payments', 'refunds', 'require_approval', 'sales_channel', 'cancellation_date'
) )
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@@ -1035,7 +1005,7 @@ class OrderPositionCreateSerializer(I18nAwareModelSerializer):
fields = ('positionid', 'item', 'variation', 'price', 'attendee_name', 'attendee_name_parts', 'attendee_email', fields = ('positionid', 'item', 'variation', 'price', 'attendee_name', 'attendee_name_parts', 'attendee_email',
'company', 'street', 'zipcode', 'city', 'country', 'state', 'is_bundled', 'company', 'street', 'zipcode', 'city', 'country', 'state', 'is_bundled',
'secret', 'addon_to', 'subevent', 'answers', 'seat', 'voucher', 'valid_from', 'valid_until', 'secret', 'addon_to', 'subevent', 'answers', 'seat', 'voucher', 'valid_from', 'valid_until',
'requested_valid_from', 'use_reusable_medium', 'discount') 'requested_valid_from', 'use_reusable_medium')
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@@ -1131,10 +1101,6 @@ class OrderPositionCreateSerializer(I18nAwareModelSerializer):
{'state': ['"{}" is not a known subdivision of the country "{}".'.format(data.get('state'), cc)]} {'state': ['"{}" is not a known subdivision of the country "{}".'.format(data.get('state'), cc)]}
) )
if data.get('price') is None and data.get('discount'):
raise ValidationError(
{'discount': ['You can only specify a discount if you do the price computation, but price is not set.']}
)
return data return data
@@ -1189,13 +1155,11 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
queryset=SalesChannel.objects.none(), queryset=SalesChannel.objects.none(),
required=False, required=False,
) )
tax_rounding_mode = serializers.ChoiceField(choices=ROUNDING_MODES, allow_null=True, required=False,)
locale = serializers.ChoiceField(choices=[], required=False, allow_null=True) locale = serializers.ChoiceField(choices=[], required=False, allow_null=True)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.fields['positions'].child.fields['voucher'].queryset = self.context['event'].vouchers.all() self.fields['positions'].child.fields['voucher'].queryset = self.context['event'].vouchers.all()
self.fields['positions'].child.fields['discount'].queryset = self.context['event'].discounts.all()
self.fields['customer'].queryset = self.context['event'].organizer.customers.all() self.fields['customer'].queryset = self.context['event'].organizer.customers.all()
self.fields['expires'].required = False self.fields['expires'].required = False
self.fields["sales_channel"].queryset = self.context["event"].organizer.sales_channels.all() self.fields["sales_channel"].queryset = self.context["event"].organizer.sales_channels.all()
@@ -1206,7 +1170,7 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
fields = ('code', 'status', 'testmode', 'email', 'phone', 'locale', 'payment_provider', 'fees', 'comment', 'sales_channel', fields = ('code', 'status', 'testmode', 'email', 'phone', 'locale', 'payment_provider', 'fees', 'comment', 'sales_channel',
'invoice_address', 'positions', 'checkin_attention', 'checkin_text', 'payment_info', 'payment_date', 'invoice_address', 'positions', 'checkin_attention', 'checkin_text', 'payment_info', 'payment_date',
'consume_carts', 'force', 'send_email', 'simulate', 'customer', 'custom_followup_at', 'consume_carts', 'force', 'send_email', 'simulate', 'customer', 'custom_followup_at',
'require_approval', 'valid_if_pending', 'expires', 'api_meta', 'tax_rounding_mode') 'require_approval', 'valid_if_pending', 'expires', 'api_meta')
def validate_payment_provider(self, pp): def validate_payment_provider(self, pp):
if pp is None: if pp is None:
@@ -1603,22 +1567,19 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
pos.voucher_budget_use = max(listed_price - price_after_voucher, Decimal('0.00')) pos.voucher_budget_use = max(listed_price - price_after_voucher, Decimal('0.00'))
order_positions = [pos_data['__instance'] for pos_data in positions_data] order_positions = [pos_data['__instance'] for pos_data in positions_data]
if not any([p.get("discount") for p in positions_data]): discount_results = apply_discounts(
# If any discount is set by the client (i.e. pretixPOS), we do not recalculate but believe the client self.context['event'],
# to avoid differences in end results. order.sales_channel,
discount_results = apply_discounts( [
self.context['event'], (cp.item_id, cp.subevent_id, cp.subevent.date_from if cp.subevent_id else None, cp.price,
order.sales_channel, bool(cp.addon_to), cp.is_bundled, pos._voucher_discount)
[ for cp in order_positions
(cp.item_id, cp.subevent_id, cp.subevent.date_from if cp.subevent_id else None, cp.price, ]
cp.addon_to, cp.is_bundled, pos._voucher_discount) )
for cp in order_positions for cp, (new_price, discount) in zip(order_positions, discount_results):
] if new_price != pos.price and pos._auto_generated_price:
) pos.price = new_price
for cp, (new_price, discount) in zip(order_positions, discount_results): pos.discount = discount
if new_price != pos.price and pos._auto_generated_price:
pos.price = new_price
pos.discount = discount
# Save instances # Save instances
for pos_data in positions_data: for pos_data in positions_data:
@@ -1732,32 +1693,7 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
else: else:
f.save() f.save()
rounding_mode = validated_data.get("tax_rounding_mode") order.total += sum([f.value for f in fees])
if not rounding_mode:
if isinstance(self.context.get("auth"), Device):
# Safety fallback to avoid differences in tax reporting
brand = self.context.get("auth").software_brand or ""
if "pretixPOS" in brand or "pretixKIOSK" in brand:
rounding_mode = "line"
if not rounding_mode:
rounding_mode = self.context["event"].settings.tax_rounding
changed = apply_rounding(
rounding_mode,
ia,
self.context["event"].currency,
[*pos_map.values(), *fees]
)
for line in changed:
if isinstance(line, OrderPosition):
line.save(update_fields=[
"price", "price_includes_rounding_correction", "tax_value", "tax_value_includes_rounding_correction"
])
elif isinstance(line, OrderFee):
line.save(update_fields=[
"value", "value_includes_rounding_correction", "tax_value", "tax_value_includes_rounding_correction"
])
order.total = sum([c.price for c in pos_map.values()]) + sum([f.value for f in fees])
if simulate: if simulate:
order.fees = fees order.fees = fees
order.positions = pos_map.values() order.positions = pos_map.values()
@@ -1821,14 +1757,12 @@ class LinePositionField(serializers.IntegerField):
class InlineInvoiceLineSerializer(I18nAwareModelSerializer): class InlineInvoiceLineSerializer(I18nAwareModelSerializer):
position = LinePositionField(read_only=True) position = LinePositionField(read_only=True)
event_date_from = serializers.DateTimeField(read_only=True, source="period_start")
event_date_to = serializers.DateTimeField(read_only=True, source="period_end")
class Meta: class Meta:
model = InvoiceLine model = InvoiceLine
fields = ('position', 'description', 'item', 'variation', 'subevent', 'attendee_name', 'event_date_from', fields = ('position', 'description', 'item', 'variation', 'subevent', 'attendee_name', 'event_date_from',
'event_date_to', 'period_start', 'period_end', 'gross_value', 'tax_value', 'tax_rate', 'tax_code', 'event_date_to', 'gross_value', 'tax_value', 'tax_rate', 'tax_code', 'tax_name', 'fee_type',
'tax_name', 'fee_type', 'fee_internal_type', 'event_location') 'fee_internal_type', 'event_location')
class InvoiceSerializer(I18nAwareModelSerializer): class InvoiceSerializer(I18nAwareModelSerializer):
@@ -1842,7 +1776,7 @@ class InvoiceSerializer(I18nAwareModelSerializer):
class Meta: class Meta:
model = Invoice model = Invoice
fields = ('event', 'order', 'number', 'is_cancellation', 'invoice_from', 'invoice_from_name', 'invoice_from_zipcode', fields = ('event', 'order', 'number', 'is_cancellation', 'invoice_from', 'invoice_from_name', 'invoice_from_zipcode',
'invoice_from_city', 'invoice_from_state', 'invoice_from_country', 'invoice_from_tax_id', 'invoice_from_vat_id', 'invoice_from_city', 'invoice_from_country', 'invoice_from_tax_id', 'invoice_from_vat_id',
'invoice_to', 'invoice_to_is_business', 'invoice_to_company', 'invoice_to_name', 'invoice_to_street', 'invoice_to', 'invoice_to_is_business', 'invoice_to_company', 'invoice_to_name', 'invoice_to_street',
'invoice_to_zipcode', 'invoice_to_city', 'invoice_to_state', 'invoice_to_country', 'invoice_to_vat_id', 'invoice_to_zipcode', 'invoice_to_city', 'invoice_to_state', 'invoice_to_country', 'invoice_to_vat_id',
'invoice_to_beneficiary', 'invoice_to_transmission_info', 'custom_field', 'date', 'refers', 'locale', 'invoice_to_beneficiary', 'invoice_to_transmission_info', 'custom_field', 'date', 'refers', 'locale',

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.
@@ -33,7 +33,7 @@ from pretix.api.serializers.order import (
OrderFeeCreateSerializer, OrderPositionCreateSerializer, OrderFeeCreateSerializer, OrderPositionCreateSerializer,
) )
from pretix.base.models import ItemVariation, Order, OrderFee, OrderPosition from pretix.base.models import ItemVariation, Order, OrderFee, OrderPosition
from pretix.base.services.orders import OrderChangeManager, OrderError from pretix.base.services.orders import OrderError
from pretix.base.settings import COUNTRIES_WITH_STATE_IN_ADDRESS from pretix.base.settings import COUNTRIES_WITH_STATE_IN_ADDRESS
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -82,11 +82,11 @@ class OrderPositionCreateForExistingOrderSerializer(OrderPositionCreateSerialize
return data return data
def create(self, validated_data): def create(self, validated_data):
ocm: OrderChangeManager = self.context['ocm'] ocm = self.context['ocm']
check_quotas = self.context.get('check_quotas', True) check_quotas = self.context.get('check_quotas', True)
try: try:
new_position = ocm.add_position( ocm.add_position(
item=validated_data['item'], item=validated_data['item'],
variation=validated_data.get('variation'), variation=validated_data.get('variation'),
price=validated_data.get('price'), price=validated_data.get('price'),
@@ -98,7 +98,7 @@ class OrderPositionCreateForExistingOrderSerializer(OrderPositionCreateSerialize
) )
if self.context.get('commit', True): if self.context.get('commit', True):
ocm.commit(check_quotas=check_quotas) ocm.commit(check_quotas=check_quotas)
return new_position.position return validated_data['order'].positions.order_by('-positionid').first()
else: else:
return OrderPosition() # fake to appease DRF return OrderPosition() # fake to appease DRF
except OrderError as e: except OrderError as e:
@@ -131,7 +131,7 @@ class OrderFeeCreateForExistingOrderSerializer(OrderFeeCreateSerializer):
return data return data
def create(self, validated_data): def create(self, validated_data):
ocm: OrderChangeManager = self.context['ocm'] ocm = self.context['ocm']
try: try:
f = OrderFee( f = OrderFee(
@@ -146,7 +146,7 @@ class OrderFeeCreateForExistingOrderSerializer(OrderFeeCreateSerializer):
ocm.add_fee(f) ocm.add_fee(f)
if self.context.get('commit', True): if self.context.get('commit', True):
ocm.commit() ocm.commit()
return f return validated_data['order'].fees.order_by('-pk').first()
else: else:
return OrderFee() # fake to appease DRF return OrderFee() # fake to appease DRF
except OrderError as e: except OrderError as e:
@@ -310,7 +310,7 @@ class OrderPositionChangeSerializer(serializers.ModelSerializer):
return data return data
def update(self, instance, validated_data): def update(self, instance, validated_data):
ocm: OrderChangeManager = self.context['ocm'] ocm = self.context['ocm']
check_quotas = self.context.get('check_quotas', True) check_quotas = self.context.get('check_quotas', True)
current_seat = {'seat_guid': instance.seat.seat_guid} if instance.seat else None current_seat = {'seat_guid': instance.seat.seat_guid} if instance.seat else None
item = validated_data.get('item', instance.item) item = validated_data.get('item', instance.item)
@@ -399,7 +399,7 @@ class OrderFeeChangeSerializer(serializers.ModelSerializer):
) )
def update(self, instance, validated_data): def update(self, instance, validated_data):
ocm: OrderChangeManager = self.context['ocm'] ocm = self.context['ocm']
value = validated_data.get('value', instance.value) value = validated_data.get('value', instance.value)
try: try:

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.
@@ -49,7 +49,7 @@ from pretix.base.plugins import (
PLUGIN_LEVEL_EVENT, PLUGIN_LEVEL_EVENT_ORGANIZER_HYBRID, PLUGIN_LEVEL_EVENT, PLUGIN_LEVEL_EVENT_ORGANIZER_HYBRID,
PLUGIN_LEVEL_ORGANIZER, PLUGIN_LEVEL_ORGANIZER,
) )
from pretix.base.services.mail import mail from pretix.base.services.mail import SendMailException, mail
from pretix.base.settings import validate_organizer_settings from pretix.base.settings import validate_organizer_settings
from pretix.helpers.urls import build_absolute_uri as build_global_uri from pretix.helpers.urls import build_absolute_uri as build_global_uri
from pretix.multidomain.urlreverse import build_absolute_uri from pretix.multidomain.urlreverse import build_absolute_uri
@@ -363,21 +363,24 @@ class TeamInviteSerializer(serializers.ModelSerializer):
) )
def _send_invite(self, instance): def _send_invite(self, instance):
mail( try:
instance.email, mail(
_('pretix account invitation'), instance.email,
'pretixcontrol/email/invitation.txt', _('pretix account invitation'),
{ 'pretixcontrol/email/invitation.txt',
'user': self, {
'organizer': self.context['organizer'].name, 'user': self,
'team': instance.team.name, 'organizer': self.context['organizer'].name,
'url': build_global_uri('control:auth.invite', kwargs={ 'team': instance.team.name,
'token': instance.token 'url': build_global_uri('control:auth.invite', kwargs={
}) 'token': instance.token
}, })
event=None, },
locale=get_language_without_region() # TODO: expose? event=None,
) locale=get_language_without_region() # TODO: expose?
)
except SendMailException:
pass # Already logged
def create(self, validated_data): def create(self, validated_data):
if 'email' in validated_data: if 'email' in validated_data:
@@ -440,7 +443,6 @@ class OrganizerSettingsSerializer(SettingsSerializer):
'customer_accounts', 'customer_accounts',
'customer_accounts_native', 'customer_accounts_native',
'customer_accounts_link_by_email', 'customer_accounts_link_by_email',
'customer_accounts_require_login_for_order_access',
'invoice_regenerate_allowed', 'invoice_regenerate_allowed',
'contact_mail', 'contact_mail',
'imprint_url', 'imprint_url',
@@ -482,7 +484,6 @@ class OrganizerSettingsSerializer(SettingsSerializer):
'reusable_media_type_nfc_mf0aes', 'reusable_media_type_nfc_mf0aes',
'reusable_media_type_nfc_mf0aes_autocreate_giftcard', 'reusable_media_type_nfc_mf0aes_autocreate_giftcard',
'reusable_media_type_nfc_mf0aes_autocreate_giftcard_currency', 'reusable_media_type_nfc_mf0aes_autocreate_giftcard_currency',
'reusable_media_type_nfc_mf0aes_random_uid',
] ]
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.
@@ -92,7 +92,6 @@ event_router.register(r'taxrules', event.TaxRuleViewSet)
event_router.register(r'seats', event.SeatViewSet) event_router.register(r'seats', event.SeatViewSet)
event_router.register(r'waitinglistentries', waitinglist.WaitingListViewSet) event_router.register(r'waitinglistentries', waitinglist.WaitingListViewSet)
event_router.register(r'checkinlists', checkin.CheckinListViewSet) event_router.register(r'checkinlists', checkin.CheckinListViewSet)
event_router.register(r'checkins', checkin.CheckinViewSet)
event_router.register(r'cartpositions', cart.CartPositionViewSet) event_router.register(r'cartpositions', cart.CartPositionViewSet)
event_router.register(r'scheduled_exports', exporters.ScheduledEventExportViewSet) event_router.register(r'scheduled_exports', exporters.ScheduledEventExportViewSet)
event_router.register(r'exporters', exporters.EventExportersViewSet, basename='exporters') event_router.register(r'exporters', exporters.EventExportersViewSet, basename='exporters')
@@ -112,7 +111,6 @@ item_router = routers.DefaultRouter()
item_router.register(r'variations', item.ItemVariationViewSet) item_router.register(r'variations', item.ItemVariationViewSet)
item_router.register(r'addons', item.ItemAddOnViewSet) item_router.register(r'addons', item.ItemAddOnViewSet)
item_router.register(r'bundles', item.ItemBundleViewSet) item_router.register(r'bundles', item.ItemBundleViewSet)
item_router.register(r'program_times', item.ItemProgramTimeViewSet)
order_router = routers.DefaultRouter() order_router = routers.DefaultRouter()
order_router.register(r'payments', order.PaymentViewSet) order_router.register(r'payments', order.PaymentViewSet)

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.
@@ -56,8 +56,7 @@ from pretix.api.serializers.checkin import (
) )
from pretix.api.serializers.item import QuestionSerializer from pretix.api.serializers.item import QuestionSerializer
from pretix.api.serializers.order import ( from pretix.api.serializers.order import (
CheckinListOrderPositionSerializer, CheckinSerializer, CheckinListOrderPositionSerializer, FailedCheckinSerializer,
FailedCheckinSerializer,
) )
from pretix.api.views import RichOrderingFilter from pretix.api.views import RichOrderingFilter
from pretix.api.views.order import OrderPositionFilter from pretix.api.views.order import OrderPositionFilter
@@ -97,16 +96,6 @@ with scopes_disabled():
) )
return queryset.filter(expr) return queryset.filter(expr)
class CheckinFilter(FilterSet):
created_since = django_filters.IsoDateTimeFilter(field_name='created', lookup_expr='gte')
created_before = django_filters.IsoDateTimeFilter(field_name='created', lookup_expr='lt')
datetime_since = django_filters.IsoDateTimeFilter(field_name='datetime', lookup_expr='gte')
datetime_before = django_filters.IsoDateTimeFilter(field_name='datetime', lookup_expr='lt')
class Meta:
model = Checkin
fields = ['successful', 'error_reason', 'list', 'type', 'gate', 'device', 'auto_checked_in']
class CheckinListViewSet(viewsets.ModelViewSet): class CheckinListViewSet(viewsets.ModelViewSet):
serializer_class = CheckinListSerializer serializer_class = CheckinListSerializer
@@ -381,21 +370,15 @@ def _checkin_list_position_queryset(checkinlists, ignore_status=False, ignore_pr
qs = qs.filter(reduce(operator.or_, lists_qs)) qs = qs.filter(reduce(operator.or_, lists_qs))
prefetch_related = [
Prefetch(
lookup='checkins',
queryset=Checkin.objects.filter(list_id__in=[cl.pk for cl in checkinlists]).select_related('device')
),
Prefetch('print_logs', queryset=PrintLog.objects.select_related('device')),
'answers', 'answers__options', 'answers__question',
]
select_related = [
'item', 'variation', 'order', 'addon_to', 'order__invoice_address', 'order', 'seat'
]
if pdf_data: if pdf_data:
qs = qs.prefetch_related( qs = qs.prefetch_related(
# Don't add to list, we don't want to propagate to addons Prefetch(
lookup='checkins',
queryset=Checkin.objects.filter(list_id__in=[cl.pk for cl in checkinlists]).select_related('device')
),
Prefetch('print_logs', queryset=PrintLog.objects.select_related('device')),
'answers', 'answers__options', 'answers__question',
Prefetch('addons', OrderPosition.objects.select_related('item', 'variation')),
Prefetch('order', Order.objects.select_related('invoice_address').prefetch_related( Prefetch('order', Order.objects.select_related('invoice_address').prefetch_related(
Prefetch( Prefetch(
'event', 'event',
@@ -410,39 +393,32 @@ def _checkin_list_position_queryset(checkinlists, ignore_status=False, ignore_pr
) )
) )
)) ))
).select_related(
'item', 'variation', 'item__category', 'addon_to', 'order', 'order__invoice_address', 'seat'
) )
else:
qs = qs.prefetch_related(
Prefetch(
lookup='checkins',
queryset=Checkin.objects.filter(list_id__in=[cl.pk for cl in checkinlists]).select_related('device')
),
Prefetch('print_logs', queryset=PrintLog.objects.select_related('device')),
'answers', 'answers__options', 'answers__question',
Prefetch('addons', OrderPosition.objects.select_related('item', 'variation'))
).select_related('item', 'variation', 'order', 'addon_to', 'order__invoice_address', 'order', 'seat')
if expand and 'subevent' in expand: if expand and 'subevent' in expand:
prefetch_related += [ qs = qs.prefetch_related(
'subevent', 'subevent__event', 'subevent__subeventitem_set', 'subevent__subeventitemvariation_set', 'subevent', 'subevent__event', 'subevent__subeventitem_set', 'subevent__subeventitemvariation_set',
'subevent__seat_category_mappings', 'subevent__meta_values' 'subevent__seat_category_mappings', 'subevent__meta_values'
] )
if expand and 'item' in expand: if expand and 'item' in expand:
prefetch_related += [ qs = qs.prefetch_related('item', 'item__addons', 'item__bundles', 'item__meta_values',
'item', 'item__addons', 'item__bundles', 'item__meta_values', 'item__variations').select_related('item__tax_rule')
'item__variations',
]
select_related.append('item__tax_rule')
if expand and 'variation' in expand: if expand and 'variation' in expand:
prefetch_related += [ qs = qs.prefetch_related('variation', 'variation__meta_values')
'variation', 'variation__meta_values',
]
if expand and 'addons' in expand:
prefetch_related += [
Prefetch('addons', OrderPosition.objects.prefetch_related(*prefetch_related).select_related(*select_related)),
]
else:
prefetch_related += [
Prefetch('addons', OrderPosition.objects.select_related('item', 'variation'))
]
if pdf_data:
select_related.remove("order") # Don't need it twice on this queryset
qs = qs.prefetch_related(*prefetch_related).select_related(*select_related)
return qs return qs
@@ -979,7 +955,6 @@ class CheckinRPCSearchView(ListAPIView):
def get_serializer_context(self): def get_serializer_context(self):
ctx = super().get_serializer_context() ctx = super().get_serializer_context()
ctx['expand'] = self.request.query_params.getlist('expand') ctx['expand'] = self.request.query_params.getlist('expand')
ctx['organizer'] = self.request.organizer
ctx['pdf_data'] = False ctx['pdf_data'] = False
return ctx return ctx
@@ -1105,25 +1080,3 @@ class CheckinRPCAnnulView(views.APIView):
checkin_annulled.send(ci.position.order.event, checkin=ci) checkin_annulled.send(ci.position.order.event, checkin=ci)
return Response({"status": "ok"}, status=status.HTTP_200_OK) return Response({"status": "ok"}, status=status.HTTP_200_OK)
class CheckinViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = CheckinSerializer
queryset = Checkin.all.none()
filter_backends = (DjangoFilterBackend, RichOrderingFilter)
filterset_class = CheckinFilter
ordering = ('created', 'id')
ordering_fields = ('created', 'datetime', 'id',)
permission = 'can_view_orders'
def get_queryset(self):
qs = Checkin.all.filter().select_related(
"position",
"device",
)
return qs
def get_serializer_context(self):
ctx = super().get_serializer_context()
ctx['event'] = self.request.event
return ctx

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.
@@ -74,11 +74,6 @@ class ExportersMixin:
@action(detail=True, methods=['GET'], url_name='download', url_path='download/(?P<asyncid>[^/]+)/(?P<cfid>[^/]+)') @action(detail=True, methods=['GET'], url_name='download', url_path='download/(?P<asyncid>[^/]+)/(?P<cfid>[^/]+)')
def download(self, *args, **kwargs): def download(self, *args, **kwargs):
cf = get_object_or_404(CachedFile, id=kwargs['cfid']) cf = get_object_or_404(CachedFile, id=kwargs['cfid'])
if not cf.allowed_for_session(self.request, "exporters-api"):
return Response(
{'status': 'failed', 'message': 'Unknown file ID or export failed'},
status=status.HTTP_410_GONE
)
if cf.file: if cf.file:
resp = ChunkBasedFileResponse(cf.file.file, content_type=cf.type) resp = ChunkBasedFileResponse(cf.file.file, content_type=cf.type)
resp['Content-Disposition'] = 'attachment; filename="{}"'.format(cf.filename).encode("ascii", "ignore") resp['Content-Disposition'] = 'attachment; filename="{}"'.format(cf.filename).encode("ascii", "ignore")
@@ -114,8 +109,7 @@ class ExportersMixin:
serializer = JobRunSerializer(exporter=instance, data=self.request.data, **self.get_serializer_kwargs()) serializer = JobRunSerializer(exporter=instance, data=self.request.data, **self.get_serializer_kwargs())
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
cf = CachedFile(web_download=True) cf = CachedFile(web_download=False)
cf.bind_to_session(self.request, "exporters-api")
cf.date = now() cf.date = now()
cf.expires = now() + timedelta(hours=24) cf.expires = now() + timedelta(hours=24)
cf.save() cf.save()

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.
@@ -40,19 +40,19 @@ from django_filters.rest_framework import DjangoFilterBackend, FilterSet
from django_scopes import scopes_disabled from django_scopes import scopes_disabled
from rest_framework import viewsets from rest_framework import viewsets
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.exceptions import PermissionDenied, ValidationError from rest_framework.exceptions import PermissionDenied
from rest_framework.response import Response from rest_framework.response import Response
from pretix.api.pagination import TotalOrderingFilter from pretix.api.pagination import TotalOrderingFilter
from pretix.api.serializers.item import ( from pretix.api.serializers.item import (
ItemAddOnSerializer, ItemBundleSerializer, ItemCategorySerializer, ItemAddOnSerializer, ItemBundleSerializer, ItemCategorySerializer,
ItemProgramTimeSerializer, ItemSerializer, ItemVariationSerializer, ItemSerializer, ItemVariationSerializer, QuestionOptionSerializer,
QuestionOptionSerializer, QuestionSerializer, QuotaSerializer, QuestionSerializer, QuotaSerializer,
) )
from pretix.api.views import ConditionalListView from pretix.api.views import ConditionalListView
from pretix.base.models import ( from pretix.base.models import (
CartPosition, Item, ItemAddOn, ItemBundle, ItemCategory, ItemProgramTime, CartPosition, Item, ItemAddOn, ItemBundle, ItemCategory, ItemVariation,
ItemVariation, Question, QuestionOption, Quota, Question, QuestionOption, Quota,
) )
from pretix.base.services.quotas import QuotaAvailability from pretix.base.services.quotas import QuotaAvailability
from pretix.helpers.dicts import merge_dicts from pretix.helpers.dicts import merge_dicts
@@ -106,7 +106,7 @@ class ItemViewSet(ConditionalListView, viewsets.ModelViewSet):
'variations', 'addons', 'bundles', 'meta_values', 'meta_values__property', 'variations', 'addons', 'bundles', 'meta_values', 'meta_values__property',
'variations__meta_values', 'variations__meta_values__property', 'variations__meta_values', 'variations__meta_values__property',
'require_membership_types', 'variations__require_membership_types', 'require_membership_types', 'variations__require_membership_types',
'limit_sales_channels', 'variations__limit_sales_channels', 'program_times' 'limit_sales_channels', 'variations__limit_sales_channels',
).all() ).all()
def perform_create(self, serializer): def perform_create(self, serializer):
@@ -279,59 +279,6 @@ class ItemBundleViewSet(viewsets.ModelViewSet):
) )
class ItemProgramTimeViewSet(viewsets.ModelViewSet):
serializer_class = ItemProgramTimeSerializer
queryset = ItemProgramTime.objects.none()
filter_backends = (DjangoFilterBackend, TotalOrderingFilter,)
ordering_fields = ('id',)
ordering = ('id',)
permission = None
write_permission = 'can_change_items'
@cached_property
def item(self):
return get_object_or_404(Item, pk=self.kwargs['item'], event=self.request.event)
def get_queryset(self):
if self.request.event.has_subevents:
raise ValidationError('You cannot use program times on an event series.')
return self.item.program_times.all()
def get_serializer_context(self):
ctx = super().get_serializer_context()
ctx['event'] = self.request.event
ctx['item'] = self.item
return ctx
def perform_create(self, serializer):
item = get_object_or_404(Item, pk=self.kwargs['item'], event=self.request.event)
serializer.save(item=item)
item.log_action(
'pretix.event.item.program_times.added',
user=self.request.user,
auth=self.request.auth,
data=merge_dicts(self.request.data, {'id': serializer.instance.pk})
)
def perform_update(self, serializer):
serializer.save(event=self.request.event)
serializer.instance.item.log_action(
'pretix.event.item.program_times.changed',
user=self.request.user,
auth=self.request.auth,
data=merge_dicts(self.request.data, {'id': serializer.instance.pk})
)
def perform_destroy(self, instance):
super().perform_destroy(instance)
instance.item.log_action(
'pretix.event.item.program_times.removed',
user=self.request.user,
auth=self.request.auth,
data={'start': instance.start, 'end': instance.end}
)
class ItemAddOnViewSet(viewsets.ModelViewSet): class ItemAddOnViewSet(viewsets.ModelViewSet):
serializer_class = ItemAddOnSerializer serializer_class = ItemAddOnSerializer
queryset = ItemAddOn.objects.none() queryset = ItemAddOn.objects.none()
@@ -567,7 +514,7 @@ class QuotaViewSet(ConditionalListView, viewsets.ModelViewSet):
write_permission = 'can_change_items' write_permission = 'can_change_items'
def get_queryset(self): def get_queryset(self):
return self.request.event.quotas.select_related('subevent').prefetch_related('items', 'variations').all() return self.request.event.quotas.all()
def list(self, request, *args, **kwargs): def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset()).distinct() queryset = self.filter_queryset(self.get_queryset()).distinct()

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.
@@ -90,6 +90,7 @@ from pretix.base.services.invoices import (
generate_cancellation, generate_invoice, invoice_pdf, invoice_qualified, generate_cancellation, generate_invoice, invoice_pdf, invoice_qualified,
regenerate_invoice, transmit_invoice, regenerate_invoice, transmit_invoice,
) )
from pretix.base.services.mail import SendMailException
from pretix.base.services.orders import ( from pretix.base.services.orders import (
OrderChangeManager, OrderError, _order_placed_email, OrderChangeManager, OrderError, _order_placed_email,
_order_placed_email_attendee, approve_order, cancel_order, deny_order, _order_placed_email_attendee, approve_order, cancel_order, deny_order,
@@ -343,7 +344,6 @@ class EventOrderViewSet(OrderViewSetMixin, viewsets.ModelViewSet):
def get_serializer_context(self): def get_serializer_context(self):
ctx = super().get_serializer_context() ctx = super().get_serializer_context()
ctx['event'] = self.request.event ctx['event'] = self.request.event
ctx['auth'] = self.request.auth
ctx['pdf_data'] = self.request.query_params.get('pdf_data', 'false').lower() == 'true' ctx['pdf_data'] = self.request.query_params.get('pdf_data', 'false').lower() == 'true'
return ctx return ctx
@@ -438,6 +438,8 @@ class EventOrderViewSet(OrderViewSetMixin, viewsets.ModelViewSet):
return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST) return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST)
except PaymentException as e: except PaymentException as e:
return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST) return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST)
except SendMailException:
pass
return self.retrieve(request, [], **kwargs) return self.retrieve(request, [], **kwargs)
return Response( return Response(
@@ -631,7 +633,10 @@ class EventOrderViewSet(OrderViewSetMixin, viewsets.ModelViewSet):
order = self.get_object() order = self.get_object()
if not order.email: if not order.email:
return Response({'detail': 'There is no email address associated with this order.'}, status=status.HTTP_400_BAD_REQUEST) return Response({'detail': 'There is no email address associated with this order.'}, status=status.HTTP_400_BAD_REQUEST)
order.resend_link(user=self.request.user, auth=self.request.auth) try:
order.resend_link(user=self.request.user, auth=self.request.auth)
except SendMailException:
return Response({'detail': _('There was an error sending the mail. Please try again later.')}, status=status.HTTP_503_SERVICE_UNAVAILABLE)
return Response( return Response(
status=status.HTTP_204_NO_CONTENT status=status.HTTP_204_NO_CONTENT
@@ -738,7 +743,7 @@ class EventOrderViewSet(OrderViewSetMixin, viewsets.ModelViewSet):
user=request.user if request.user.is_authenticated else None, user=request.user if request.user.is_authenticated else None,
auth=request.auth, auth=request.auth,
) )
order_placed.send(self.request.event, order=order, bulk=False) order_placed.send(self.request.event, order=order)
if order.status == Order.STATUS_PAID: if order.status == Order.STATUS_PAID:
order_paid.send(self.request.event, order=order) order_paid.send(self.request.event, order=order)
order.log_action( order.log_action(
@@ -759,13 +764,7 @@ class EventOrderViewSet(OrderViewSetMixin, viewsets.ModelViewSet):
) and not order.invoices.last() ) and not order.invoices.last()
invoice = None invoice = None
if gen_invoice: if gen_invoice:
try: invoice = generate_invoice(order, trigger_pdf=True)
invoice = generate_invoice(order, trigger_pdf=True)
except Exception as e:
logger.exception("Could not generate invoice.")
order.log_action("pretix.event.order.invoice.failed", data={
"exception": str(e)
})
# Refresh serializer only after running signals # Refresh serializer only after running signals
prefetch_related_objects([order], self._positions_prefetch(request)) prefetch_related_objects([order], self._positions_prefetch(request))
@@ -1610,6 +1609,8 @@ class PaymentViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
) )
except Quota.QuotaExceededException: except Quota.QuotaExceededException:
pass pass
except SendMailException:
pass
serializer = OrderPaymentSerializer(r, context=serializer.context) serializer = OrderPaymentSerializer(r, context=serializer.context)
@@ -1647,6 +1648,8 @@ class PaymentViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST) return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST)
except PaymentException as e: except PaymentException as e:
return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST) return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST)
except SendMailException:
pass
return self.retrieve(request, [], **kwargs) return self.retrieve(request, [], **kwargs)
@action(detail=True, methods=['POST']) @action(detail=True, methods=['POST'])
@@ -1660,9 +1663,6 @@ class PaymentViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
else: else:
mark_refunded = request.data.get('mark_canceled', False) mark_refunded = request.data.get('mark_canceled', False)
if not isinstance(request.data.get("comment", ""), str):
return Response({'comment': 'Invalid type.'}, status=status.HTTP_400_BAD_REQUEST)
if payment.state != OrderPayment.PAYMENT_STATE_CONFIRMED: if payment.state != OrderPayment.PAYMENT_STATE_CONFIRMED:
return Response({'detail': 'Invalid state of payment.'}, status=status.HTTP_400_BAD_REQUEST) return Response({'detail': 'Invalid state of payment.'}, status=status.HTTP_400_BAD_REQUEST)
@@ -1689,7 +1689,6 @@ class PaymentViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
amount=amount, amount=amount,
provider=payment.provider, provider=payment.provider,
info='{}', info='{}',
comment=request.data.get("comment"),
) )
payment.order.log_action('pretix.event.order.refund.created', { payment.order.log_action('pretix.event.order.refund.created', {
'local_id': r.local_id, 'local_id': r.local_id,
@@ -2021,7 +2020,7 @@ class InvoiceViewSet(viewsets.ReadOnlyModelViewSet):
else: else:
order = Order.objects.select_for_update(of=OF_SELF).get(pk=inv.order_id) order = Order.objects.select_for_update(of=OF_SELF).get(pk=inv.order_id)
c = generate_cancellation(inv) c = generate_cancellation(inv)
if invoice_qualified(order): if inv.order.status != Order.STATUS_CANCELED:
inv = generate_invoice(order) inv = generate_invoice(order)
else: else:
inv = c inv = c

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.
@@ -249,17 +249,12 @@ class GiftCardViewSet(viewsets.ModelViewSet):
def perform_create(self, serializer): def perform_create(self, serializer):
value = serializer.validated_data.pop('value') value = serializer.validated_data.pop('value')
inst = serializer.save(issuer=self.request.organizer) inst = serializer.save(issuer=self.request.organizer)
inst.log_action(
action='pretix.giftcards.created',
user=self.request.user,
auth=self.request.auth,
)
inst.transactions.create(value=value, acceptor=self.request.organizer) inst.transactions.create(value=value, acceptor=self.request.organizer)
inst.log_action( inst.log_action(
action='pretix.giftcards.transaction.manual', 'pretix.giftcards.transaction.manual',
user=self.request.user, user=self.request.user,
auth=self.request.auth, auth=self.request.auth,
data=merge_dicts(self.request.data, {'id': inst.pk, 'acceptor_id': self.request.organizer.id}) data=merge_dicts(self.request.data, {'id': inst.pk})
) )
@transaction.atomic() @transaction.atomic()
@@ -274,7 +269,7 @@ class GiftCardViewSet(viewsets.ModelViewSet):
inst = serializer.save(secret=serializer.instance.secret, currency=serializer.instance.currency, inst = serializer.save(secret=serializer.instance.secret, currency=serializer.instance.currency,
testmode=serializer.instance.testmode) testmode=serializer.instance.testmode)
inst.log_action( inst.log_action(
action='pretix.giftcards.modified', 'pretix.giftcards.modified',
user=self.request.user, user=self.request.user,
auth=self.request.auth, auth=self.request.auth,
data=self.request.data, data=self.request.data,
@@ -287,10 +282,10 @@ class GiftCardViewSet(viewsets.ModelViewSet):
diff = value - old_value diff = value - old_value
inst.transactions.create(value=diff, acceptor=self.request.organizer) inst.transactions.create(value=diff, acceptor=self.request.organizer)
inst.log_action( inst.log_action(
action='pretix.giftcards.transaction.manual', 'pretix.giftcards.transaction.manual',
user=self.request.user, user=self.request.user,
auth=self.request.auth, auth=self.request.auth,
data={'value': diff, 'acceptor_id': self.request.organizer.id} data={'value': diff}
) )
return inst return inst
@@ -314,14 +309,10 @@ class GiftCardViewSet(viewsets.ModelViewSet):
}, status=status.HTTP_409_CONFLICT) }, status=status.HTTP_409_CONFLICT)
gc.transactions.create(value=value, text=text, info=info, acceptor=self.request.organizer) gc.transactions.create(value=value, text=text, info=info, acceptor=self.request.organizer)
gc.log_action( gc.log_action(
action='pretix.giftcards.transaction.manual', 'pretix.giftcards.transaction.manual',
user=self.request.user, user=self.request.user,
auth=self.request.auth, auth=self.request.auth,
data={ data={'value': value, 'text': text}
'value': value,
'text': text,
'acceptor_id': self.request.organizer.id
}
) )
return Response(GiftCardSerializer(gc, context=self.get_serializer_context()).data, status=status.HTTP_200_OK) return Response(GiftCardSerializer(gc, context=self.get_serializer_context()).data, status=status.HTTP_200_OK)
@@ -555,8 +546,7 @@ class DeviceViewSet(mixins.CreateModelMixin,
class OrganizerSettingsView(views.APIView): class OrganizerSettingsView(views.APIView):
permission = None permission = 'can_change_organizer_settings'
write_permission = 'can_change_organizer_settings'
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
s = OrganizerSettingsSerializer(instance=request.organizer.settings, organizer=request.organizer, context={ s = OrganizerSettingsSerializer(instance=request.organizer.settings, organizer=request.organizer, context={
@@ -730,7 +720,7 @@ class MembershipViewSet(viewsets.ModelViewSet):
def get_queryset(self): def get_queryset(self):
return Membership.objects.filter( return Membership.objects.filter(
customer__organizer=self.request.organizer customer__organizer=self.request.organizer
).select_related('customer') )
def get_serializer_context(self): def get_serializer_context(self):
ctx = super().get_serializer_context() ctx = super().get_serializer_context()

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.
@@ -19,7 +19,6 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see # You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>. # <https://www.gnu.org/licenses/>.
# #
from django.db import transaction from django.db import transaction
from django.db.models import F, Q from django.db.models import F, Q
from django.utils.timezone import now from django.utils.timezone import now
@@ -65,13 +64,8 @@ class VoucherViewSet(viewsets.ModelViewSet):
permission = 'can_view_vouchers' permission = 'can_view_vouchers'
write_permission = 'can_change_vouchers' write_permission = 'can_change_vouchers'
@scopes_disabled() # we have an event check here, and we can save some performance on subqueries
def get_queryset(self): def get_queryset(self):
return Voucher.annotate_budget_used( return self.request.event.vouchers.select_related('seat').all()
self.request.event.vouchers
).select_related(
'item', 'quota', 'seat', 'variation'
)
@transaction.atomic() @transaction.atomic()
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.
@@ -43,7 +43,6 @@ from pretix.base.services.tasks import ProfiledTask, TransactionAwareTask
from pretix.base.signals import periodic_task from pretix.base.signals import periodic_task
from pretix.celery_app import app from pretix.celery_app import app
from pretix.helpers import OF_SELF from pretix.helpers import OF_SELF
from pretix.helpers.celery import get_task_priority
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
_ALL_EVENTS = None _ALL_EVENTS = None
@@ -174,35 +173,6 @@ class ParametrizedEventWebhookEvent(ParametrizedWebhookEvent):
} }
class ParametrizedGiftcardWebhookEvent(ParametrizedWebhookEvent):
def build_payload(self, logentry: LogEntry):
giftcard = logentry.content_object
if not giftcard:
return None
return {
'notification_id': logentry.pk,
'issuer_id': logentry.organizer_id,
'giftcard': giftcard.pk,
'action': logentry.action_type,
}
class ParametrizedGiftcardTransactionWebhookEvent(ParametrizedWebhookEvent):
def build_payload(self, logentry: LogEntry):
giftcard = logentry.content_object
if not giftcard:
return None
return {
'notification_id': logentry.pk,
'issuer_id': logentry.organizer_id,
'acceptor_id': logentry.parsed_data.get('acceptor_id'),
'giftcard': giftcard.pk,
'action': logentry.action_type,
}
class ParametrizedVoucherWebhookEvent(ParametrizedWebhookEvent): class ParametrizedVoucherWebhookEvent(ParametrizedWebhookEvent):
def build_payload(self, logentry: LogEntry): def build_payload(self, logentry: LogEntry):
@@ -462,18 +432,6 @@ def register_default_webhook_events(sender, **kwargs):
'pretix.customer.anonymized', 'pretix.customer.anonymized',
_('Customer account anonymized'), _('Customer account anonymized'),
), ),
ParametrizedGiftcardWebhookEvent(
'pretix.giftcards.created',
_('Gift card added'),
),
ParametrizedGiftcardWebhookEvent(
'pretix.giftcards.modified',
_('Gift card modified'),
),
ParametrizedGiftcardTransactionWebhookEvent(
'pretix.giftcards.transaction.*',
_('Gift card used in transcation'),
)
) )
@@ -481,12 +439,8 @@ def register_default_webhook_events(sender, **kwargs):
def notify_webhooks(logentry_ids: list): def notify_webhooks(logentry_ids: list):
if not isinstance(logentry_ids, list): if not isinstance(logentry_ids, list):
logentry_ids = [logentry_ids] logentry_ids = [logentry_ids]
qs = LogEntry.all.select_related( qs = LogEntry.all.select_related('event', 'event__organizer', 'organizer').filter(id__in=logentry_ids)
'event', 'event__organizer', 'organizer' _org, _at, webhooks = None, None, None
).order_by(
'action_type', 'organizer_id', 'event_id',
).filter(id__in=logentry_ids)
_org, _at, _ev, webhooks = None, None, None, None
for logentry in qs: for logentry in qs:
if not logentry.organizer: if not logentry.organizer:
break # We need to know the organizer break # We need to know the organizer
@@ -496,7 +450,7 @@ def notify_webhooks(logentry_ids: list):
if not notification_type: if not notification_type:
break # Ignore, no webhooks for this event type break # Ignore, no webhooks for this event type
if _org != logentry.organizer or _at != logentry.action_type or _ev != logentry.event_id or webhooks is None: if _org != logentry.organizer or _at != logentry.action_type or webhooks is None:
_org = logentry.organizer _org = logentry.organizer
_at = logentry.action_type _at = logentry.action_type
@@ -516,10 +470,7 @@ def notify_webhooks(logentry_ids: list):
) )
for wh in webhooks: for wh in webhooks:
send_webhook.apply_async( send_webhook.apply_async(args=(logentry.id, notification_type.action_type, wh.pk))
args=(logentry.id, notification_type.action_type, wh.pk),
priority=get_task_priority("notifications", logentry.organizer_id),
)
@app.task(base=ProfiledTask, bind=True, max_retries=5, default_retry_delay=60, acks_late=True, autoretry_for=(DatabaseError,),) @app.task(base=ProfiledTask, bind=True, max_retries=5, default_retry_delay=60, acks_late=True, autoretry_for=(DatabaseError,),)

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.
@@ -112,6 +112,23 @@ def oidc_validate_and_complete_config(config):
scope="openid", scope="openid",
)) ))
for scope in config["scope"].split(" "):
if scope not in provider_config.get("scopes_supported", []):
raise ValidationError(_('You are requesting scope "{scope}" but provider only supports these: {scopes}.').format(
scope=scope,
scopes=", ".join(provider_config.get("scopes_supported", []))
))
if "claims_supported" in provider_config:
claims_supported = provider_config.get("claims_supported", [])
for k, v in config.items():
if k.endswith('_field') and v:
if v not in claims_supported: # https://openid.net/specs/openid-connect-core-1_0.html#UserInfo
raise ValidationError(_('You are requesting field "{field}" but provider only supports these: {fields}.').format(
field=v,
fields=", ".join(provider_config.get("claims_supported", []))
))
if "token_endpoint_auth_methods_supported" in provider_config: if "token_endpoint_auth_methods_supported" in provider_config:
token_endpoint_auth_methods_supported = provider_config.get("token_endpoint_auth_methods_supported", token_endpoint_auth_methods_supported = provider_config.get("token_endpoint_auth_methods_supported",
["client_secret_basic"]) ["client_secret_basic"])

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.
@@ -90,7 +90,6 @@ StaticMapping = namedtuple('StaticMapping', ('id', 'pretix_model', 'external_obj
class OutboundSyncProvider: class OutboundSyncProvider:
max_attempts = 5 max_attempts = 5
list_field_joiner = "," # set to None to keep native lists in properties
def __init__(self, event): def __init__(self, event):
self.event = event self.event = event
@@ -107,7 +106,7 @@ class OutboundSyncProvider:
return str(cls.identifier) return str(cls.identifier)
@classmethod @classmethod
def enqueue_order(cls, order, triggered_by, not_before=None, immediate=False): def enqueue_order(cls, order, triggered_by, not_before=None):
""" """
Adds an order to the sync queue. May only be called on derived classes which define an ``identifier`` attribute. Adds an order to the sync queue. May only be called on derived classes which define an ``identifier`` attribute.
@@ -120,14 +119,10 @@ class OutboundSyncProvider:
:param order: the Order that should be synced :param order: the Order that should be synced
:param triggered_by: the reason why the order should be synced, e.g. name of the signal :param triggered_by: the reason why the order should be synced, e.g. name of the signal
(currently only used internally for logging) (currently only used internally for logging)
:param immediate: whether a new sync task should run immediately for this order, instead
of waiting for the next periodic_task interval
:return: Return a tuple (queue_item, created), where created is a boolean
specifying whether a new queue item was created.
""" """
if not hasattr(cls, 'identifier'): if not hasattr(cls, 'identifier'):
raise TypeError('Call this method on a derived class that defines an "identifier" attribute.') raise TypeError('Call this method on a derived class that defines an "identifier" attribute.')
queue_item, created = OrderSyncQueue.objects.update_or_create( OrderSyncQueue.objects.update_or_create(
order=order, order=order,
sync_provider=cls.identifier, sync_provider=cls.identifier,
in_flight=False, in_flight=False,
@@ -138,10 +133,6 @@ class OutboundSyncProvider:
"need_manual_retry": None, "need_manual_retry": None,
}, },
) )
if immediate:
from pretix.base.services.datasync import sync_single
sync_single.apply_async(args=(queue_item.pk,))
return queue_item, created
@classmethod @classmethod
def get_external_link_info(cls, event, external_link_href, external_link_display_name): def get_external_link_info(cls, event, external_link_href, external_link_display_name):
@@ -261,15 +252,9 @@ class OutboundSyncProvider:
except KeyError: except KeyError:
with language(self.event.settings.locale): with language(self.event.settings.locale):
raise SyncConfigError([_( raise SyncConfigError([_(
'Field "{field_name}" does not exist. Please check your {provider_name} settings.' 'Field "{field_name}" is not valid for {available_inputs}. Please check your {provider_name} settings.'
).format(field_name=key, provider_name=self.display_name)]) ).format(key=key, available_inputs="/".join(inputs.keys()), provider_name=self.display_name)])
try: input = inputs[field.required_input]
input = inputs[field.required_input]
except KeyError:
with language(self.event.settings.locale):
raise SyncConfigError([_(
'Field "{field_name}" requires {required_input}, but only got {available_inputs}. Please check your {provider_name} settings.'
).format(field_name=key, required_input=field.required_input, available_inputs=", ".join(inputs.keys()), provider_name=self.display_name)])
val = field.getter(input) val = field.getter(input)
if isinstance(val, list): if isinstance(val, list):
if field.enum_opts and mapping_entry.get("value_map"): if field.enum_opts and mapping_entry.get("value_map"):
@@ -282,8 +267,7 @@ class OutboundSyncProvider:
'Please update value mapping for field "{field_name}" - option "{val}" not assigned' 'Please update value mapping for field "{field_name}" - option "{val}" not assigned'
).format(field_name=key, val=val)]) ).format(field_name=key, val=val)])
if self.list_field_joiner: val = ",".join(val)
val = self.list_field_joiner.join(val)
return val return val
def get_properties(self, inputs: dict, property_mappings: List[dict]): def get_properties(self, inputs: dict, property_mappings: List[dict]):
@@ -393,7 +377,7 @@ class OutboundSyncProvider:
def sync_order(self, order): def sync_order(self, order):
if not self.should_sync_order(order): if not self.should_sync_order(order):
logger.debug("Skipping order %r", order) logger.debug("Skipping order %r", order)
return {} return
logger.debug("Syncing order %r", order) logger.debug("Syncing order %r", order)
positions = list( positions = list(

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.
@@ -71,20 +71,15 @@ def assign_properties(
return out return out
def _add_to_list(out, field_name, current_value, new_item_input, list_sep): def _add_to_list(out, field_name, current_value, new_item, list_sep):
new_item = str(new_item)
if list_sep is not None: if list_sep is not None:
new_items = str(new_item_input).split(list_sep) new_item = new_item.replace(list_sep, "")
current_value = current_value.split(list_sep) if current_value else [] current_value = current_value.split(list_sep) if current_value else []
else: elif not isinstance(current_value, (list, tuple)):
new_items = [str(new_item_input)] current_value = [str(current_value)]
if not isinstance(current_value, (list, tuple)): if new_item not in current_value:
current_value = [str(current_value)] new_list = current_value + [new_item]
new_list = list(current_value)
for new_item in new_items:
if new_item not in current_value:
new_list.append(new_item)
if new_list != current_value:
if list_sep is not None: if list_sep is not None:
new_list = list_sep.join(new_list) new_list = list_sep.join(new_list)
out[field_name] = new_list out[field_name] = new_list

View File

@@ -1,8 +1,8 @@
# #
# This file is part of pretix (Community Edition). # This file is part of pretix (Community Edition).
# #
# Copyright (C) 2014-2020 Raphael Michel and contributors # Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors # Copyright (C) 2020-2021 rami.io GmbH and contributors
# #
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General # This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License. # Public License as published by the Free Software Foundation in version 3 of the License.

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