mirror of
https://github.com/pretix/pretix.git
synced 2025-12-09 00:42:28 +00:00
Compare commits
52 Commits
loadtest2
...
release/2.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a9709c29d | ||
|
|
f78f8acfa9 | ||
|
|
c2a77752c6 | ||
|
|
a0bc2688b1 | ||
|
|
a059eb0de6 | ||
|
|
dba9a56a67 | ||
|
|
15f445cb3d | ||
|
|
c9f6c71c81 | ||
|
|
2032d36ad6 | ||
|
|
ffb4cf08d1 | ||
|
|
e89aaf4059 | ||
|
|
db270b3bf2 | ||
|
|
d8b78c3a7a | ||
|
|
67c448a29e | ||
|
|
5b7906f2a1 | ||
|
|
0612d42607 | ||
|
|
83f866034a | ||
|
|
b1fa214869 | ||
|
|
aa53b5235a | ||
|
|
61a13256a0 | ||
|
|
64e2336014 | ||
|
|
3411abd1e6 | ||
|
|
2a34e54fae | ||
|
|
9863dc35d6 | ||
|
|
690883a198 | ||
|
|
d8ded08a46 | ||
|
|
4aab5daa57 | ||
|
|
e87628c902 | ||
|
|
3c7bf46268 | ||
|
|
a1dacb1897 | ||
|
|
08d5626704 | ||
|
|
c8a1481f93 | ||
|
|
e7c4121745 | ||
|
|
35ddd8dd28 | ||
|
|
e2ec6eb156 | ||
|
|
42edc4c3aa | ||
|
|
1cb2f99f3a | ||
|
|
d4146e08b1 | ||
|
|
79ae9b6501 | ||
|
|
c23f71a19c | ||
|
|
53053f19e4 | ||
|
|
a42b2d76f6 | ||
|
|
51392f73a8 | ||
|
|
465a5b01b9 | ||
|
|
74a6004613 | ||
|
|
f9fc33eba1 | ||
|
|
363dc74c31 | ||
|
|
efb598e93a | ||
|
|
bcfaf2801d | ||
|
|
98db417fe6 | ||
|
|
a03ffd949e | ||
|
|
88ef46dee9 |
@@ -125,6 +125,8 @@ Example::
|
||||
Indicates if the database backend is a MySQL/MariaDB Galera cluster and
|
||||
turns on some optimizations/special case handlers. Default: ``False``
|
||||
|
||||
.. _`config-replica`:
|
||||
|
||||
Database replica settings
|
||||
-------------------------
|
||||
|
||||
@@ -142,6 +144,8 @@ Example::
|
||||
[replica]
|
||||
host=192.168.0.2
|
||||
|
||||
.. _`config-urls`:
|
||||
|
||||
URLs
|
||||
----
|
||||
|
||||
|
||||
@@ -11,3 +11,4 @@ This documentation is for everyone who wants to install pretix on a server.
|
||||
installation/index
|
||||
config
|
||||
maintainance
|
||||
scaling
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
.. _`installation`:
|
||||
|
||||
Installation guide
|
||||
==================
|
||||
|
||||
|
||||
236
doc/admin/scaling.rst
Normal file
236
doc/admin/scaling.rst
Normal file
@@ -0,0 +1,236 @@
|
||||
.. _`scaling`:
|
||||
|
||||
Scaling guide
|
||||
=============
|
||||
|
||||
Our :ref:`installation guide <installation>` only covers "small-scale" setups, by which we mostly mean
|
||||
setups that run on a **single (virtual) machine** and do not encounter large traffic peaks.
|
||||
|
||||
We do not offer an installation guide for larger-scale setups of pretix, mostly because we believe that
|
||||
there is no one-size-fits-all solution for this and the desired setup highly depends on your use case,
|
||||
the platform you run pretix on, and your technical capabilities. We do not recommend trying set up pretix
|
||||
in a multi-server environment if you do not already have experience with managing server clusters.
|
||||
|
||||
This document is intended to give you a general idea on what issues you will encounter when you scale up
|
||||
and what you should think of.
|
||||
|
||||
.. tip::
|
||||
|
||||
If you require more help on this, we're happy to help. Our pretix Enterprise support team has built
|
||||
and helped building, scaling and load-testing pretix installations at any scale and we're looking
|
||||
forward to work with you on fine-tuning your system. If you intend to sell **more than a thousand
|
||||
tickets in a very short amount of time**, we highly recommend reaching out and at least talking this
|
||||
through. Just get in touch at sales@pretix.eu!
|
||||
|
||||
Scaling reasons
|
||||
---------------
|
||||
|
||||
There's mainly two reasons to scale up a pretix installation beyond a single server:
|
||||
|
||||
* **Availability:** Distributing pretix over multiple servers can allow you to survive failure of one or more single machines, leading to a higher uptime and reliability of your system.
|
||||
|
||||
* **Traffic and throughput:** Distributing pretix over multiple servers can allow you to process more web requests and ticket sales at the same time.
|
||||
|
||||
You are very unlikely to require scaling for other reasons, such as having too much data in your database.
|
||||
|
||||
Components
|
||||
----------
|
||||
|
||||
A pretix installation usually consists of the following components which run performance-relevant processes:
|
||||
|
||||
* ``pretix-web`` is the Django-based web application that serves all user interaction.
|
||||
|
||||
* ``pretix-worker`` is a Celery-based application that processes tasks that should be run asynchronously outside of the web application process.
|
||||
|
||||
* A **SQL database** keeps all the important data and processes the actual transactions. We recommend using PostgreSQL, but MySQL/MariaDB works as well.
|
||||
|
||||
* A **web server** that terminates TLS and HTTP connections and forwards them to ``pretix-web``. In some cases, e.g. when serving static files, the web servers might return a response directly. We recommend using ``nginx``.
|
||||
|
||||
* A **redis** server responsible for the communication between ``pretix-web`` and ``pretix-worker``, as well as for caching.
|
||||
|
||||
* A directory of **media files** such as user-uploaded files or generated files (tickets, invoices, …) that are created and used by ``pretix-web``, ``pretix-worker`` and the web server.
|
||||
|
||||
In the following, we will discuss the scaling behavior of every component individually. In general, you can run all of the components
|
||||
on the same server, but you can just as well distribute every component to its own server, or even use multiple servers for some single
|
||||
components.
|
||||
|
||||
.. warning::
|
||||
|
||||
When setting up your system, don't forget about security. In a multi-server environment,
|
||||
you need to take special care to ensure that no unauthorized access to your database
|
||||
is possible through the network and that it's not easy to wiretap your connections. We
|
||||
recommend a rigorous use of firewalls and encryption on all communications. You can
|
||||
ensure this either on an application level (such as using the TLS support in your
|
||||
database) or on a network level with a VPN solution.
|
||||
|
||||
Web server
|
||||
""""""""""
|
||||
|
||||
Your web server is at the very front of your installation. It will need to absorb all of the traffic, and it should be able to
|
||||
at least show a decent error message, even when everything else fails. Luckily, web servers are really fast these days, so this
|
||||
can be achieved without too much work.
|
||||
|
||||
We recommend reading up on tuning your web server for high concurrency. For nginx, this means thinking about the number of worker
|
||||
processes and the number of connections each worker process accepts. Double-check that TLS session caching works, because TLS
|
||||
handshakes can get really expensive.
|
||||
|
||||
During a traffic peak, your web server will be able to make us of more CPU resources, while memory usage will stay comparatively low,
|
||||
so if you invest in more hardware here, invest in more and faster CPU cores.
|
||||
|
||||
Make sure that pretix' static files (such as CSS and JavaScript assets) as well as user-uploaded media files (event logos, etc)
|
||||
are served directly by your web server and your web server caches them in-memory (nginx does it by default) and sets useful
|
||||
headers for client-side caching. As an additional performance improvement, you can turn of access logging for these types of files.
|
||||
If you want, you can even farm out serving static files to a different web server entirely and :ref:`configure pretix to reference
|
||||
them from a different URL <config-urls>`.
|
||||
|
||||
.. tip::
|
||||
|
||||
If you expect *really high traffic* for your very popular event, you might want to do some rate limiting on this layer, or,
|
||||
if you want to ensure a fair and robust first-come-first-served experience and prefer letting users wait over showing them
|
||||
errors, consider a queuing solution. We're happy to provide you with such systems, just get in touch at sales@pretix.eu.
|
||||
|
||||
pretix-web
|
||||
""""""""""
|
||||
|
||||
The ``pretix-web`` process does not carry any internal state can be easily started on as many machines as you like, and you can
|
||||
use the load balancing features of your frontend web server to redirect to all of them.
|
||||
|
||||
You can adjust the number of processes in the ``gunicorn`` command line, and we recommend choosing roughly two times the number
|
||||
of CPU cores available. Under load, the memory consumption of ``pretix-web`` will stay comparatively constant, while the CPU usage
|
||||
will increase a lot. Therefore, if you can add more or faster CPU cores, you will be able to serve more users.
|
||||
|
||||
pretix-worker
|
||||
"""""""""""""
|
||||
|
||||
The ``pretix-worker`` process performs all operations that are not directly executed in the request-response-cycle of ``pretix-web``.
|
||||
Just like ``pretix-web`` you can easily start up as many instances as you want on different machines to share the work. As long as they
|
||||
all talk to the same redis server, they will all receive tasks from ``pretix-web``, work on them and post their result back.
|
||||
You can configure the number of threads that run tasks in parallel through the ``--concurrency`` command line option of ``celery``.
|
||||
|
||||
Just like ``pretix-web``, this process is mostly heavy on CPU, disk IO and network IO, although memory peaks can occur e.g. during the
|
||||
generation of large PDF files, so we recommend having some reserves here.
|
||||
|
||||
``pretix-worker`` performs a variety of tasks which are of different importance.
|
||||
Some of them are mission-critical and need to be run quickly even during high load (such as
|
||||
creating a cart or an order), others are irrelevant and can easily run later (such as
|
||||
distributing tickets on the waiting list). You can fine-tune the capacity you assign to each
|
||||
of these tasks by running ``pretix-worker`` processes that only work on a specific **queue**.
|
||||
For example, you could have three servers dedicated only to process order creations and one
|
||||
server dedicated only to sending emails. This allows you to set priorities and also protects
|
||||
you from e.g. a slow email server lowering your ticket throughput.
|
||||
|
||||
You can do so by specifying one or more queues on the ``celery`` command line of this process, such as ``celery -A pretix.celery_app worker -Q notifications,mail``. Currently,
|
||||
the following queues exist:
|
||||
|
||||
* ``checkout`` -- This queue handles everything related to carts and orders and thereby everything required to process a sale. This includes adding and deleting items from carts as well as creating and canceling orders.
|
||||
|
||||
* ``mail`` -- This queue handles sending of outgoing emails.
|
||||
|
||||
* ``notifications`` -- This queue handles the processing of any outgoing notifications, such as email notifications to admin users (except for the actual sending) or API notifications to registered webhooks.
|
||||
|
||||
* ``background`` -- This queue handles tasks that are expected to take long or have no human waiting for their result immediately, such as refreshing caches, re-generating CSS files, assigning tickets on the waiting list or parsing bank data files.
|
||||
|
||||
* ``default`` -- This queue handles everything else with "medium" or unassigned priority, most prominently the generation of files for tickets, invoices, badges, admin exports, etc.
|
||||
|
||||
Media files
|
||||
"""""""""""
|
||||
|
||||
Both ``pretix-web``, ``pretix-worker`` and in some cases your webserver need to work with
|
||||
media files. Media files are all files generated *at runtime* by the software. This can
|
||||
include files uploaded by the event organizers, such as the event logo, files uploaded by
|
||||
ticket buyers (if you use such features) or files generated by the software, such as
|
||||
ticket files, invoice PDFs, data exports or customized CSS files.
|
||||
|
||||
Those files are by default stored to the ``media/`` sub-folder of the data directory given
|
||||
in the ``pretix.cfg`` configuration file. Inside that ``media/`` folder, you will find a
|
||||
``pub/`` folder containing the subset of files that should be publicly accessible through
|
||||
the web server. Everything else only needs to be accessible by ``pretix-web`` and
|
||||
``pretix-worker`` themselves.
|
||||
|
||||
If you distribute ``pretix-web`` or ``pretix-worker`` across more than one machine, you
|
||||
**must** make sure that they all have access to a shared storage to read and write these
|
||||
files, otherwise you **will** run into errors with the user interface.
|
||||
|
||||
The easiest solution for this is probably to store them on a NFS server that you mount
|
||||
on each of the other servers.
|
||||
|
||||
Since we use Django's file storage mechanism internally, you can in theory also use a object-storage solution like Amazon S3, Ceph, or Minio to store these files, although we currently do not expose this through pretix' configuration file and this would require you to ship your own variant of ``pretix/settings.py`` and reference it through the ``DJANGO_SETTINGS_MODULE`` environment variable.
|
||||
|
||||
At pretix.eu, we use a custom-built `object storage cluster`_.
|
||||
|
||||
SQL database
|
||||
""""""""""""
|
||||
|
||||
One of the most critical parts of the whole setup is the SQL database -- and certainly the
|
||||
hardest to scale. Tuning relational databases is an art form, and while there's lots of
|
||||
material on it on the internet, there's not a single recipe that you can apply to every case.
|
||||
|
||||
As a general rule of thumb, the more resources you can give your databases, the better.
|
||||
Most databases will happily use all CPU cores available, but only use memory up to an amount
|
||||
you configure, so make sure to set this memory usage as high as you can afford. Having more
|
||||
memory available allows your database to make more use of caching, which is usually good.
|
||||
|
||||
Scaling your database to multiple machines needs to be treated with great caution. It's a
|
||||
good to have a replica of your database for availability reasons. In case your primary
|
||||
database server fails, you can easily switch over to the replica and continue working.
|
||||
|
||||
However, using database replicas for performance gains is much more complicated. When using
|
||||
replicated database systems, you are always trading in consistency or availability to get
|
||||
additional performance and the consequences of this can be subtle and it is important
|
||||
that you have a deep understanding of the semantics of your replication mechanism.
|
||||
|
||||
.. warning::
|
||||
|
||||
Using an off-the-shelf database proxy solution that redirects read queries to your
|
||||
replicas and write queries to your primary database **will lead to very nasty bugs.**
|
||||
|
||||
As an example, if you buy a ticket, pretix first needs to calculate how many tickets
|
||||
are left to sell. If this calculation is done on a database replica that lags behind
|
||||
even for fractions of a second, the decision to allow selling the ticket will be made
|
||||
on out-of-data data and you can end up with more tickets sold than configured. Similarly,
|
||||
you could imagine situations leading to double payments etc.
|
||||
|
||||
If you do have a replica, you *can* tell pretix about it :ref:`in your configuration <config-replica>`.
|
||||
This way, pretix can offload complex read-only queries to the replica when it is safe to do so.
|
||||
As of pretix 2.7, this is mainly used for search queries in the backend and for rendering the
|
||||
product list and event lists in the frontend, but we plan on expanding this in the future.
|
||||
|
||||
Therefore, for now our clear recommendation is: Try to scale your database vertically and put
|
||||
it on the most powerful machine you have available.
|
||||
|
||||
redis
|
||||
"""""
|
||||
|
||||
While redis is a very important part that glues together some of the components, it isn't used
|
||||
heavily and can usually handle a fairly large pretix installation easily on a single modern
|
||||
CPU core.
|
||||
Having some memory available is good in case of e.g. lots of tasks queuing up during a traffic peak, but we wouldn't expect ever needing more than a gigabyte of it.
|
||||
|
||||
Feel free to set up a redis cluster for availability – but you won't need it for performance in a long time.
|
||||
|
||||
The limitations
|
||||
---------------
|
||||
|
||||
Up to a certain point, pretix scales really well. However, there are a few things that we consider
|
||||
even more important than scalability, and those are correctness and reliability. We want you to be
|
||||
able to trust that pretix will not sell more tickets than you intended or run into similar error
|
||||
cases.
|
||||
|
||||
Combined with pretix' flexibility and complexity, especially around vouchers and quotas, this creates
|
||||
some hard issues. In many cases, we need to fall back to event-global locking for some actions which
|
||||
are likely to run with high concurrency and cause harm.
|
||||
|
||||
For every event, only one of these locking actions can be run at the same time. Examples for this are
|
||||
adding products limited by a quota to a cart, adding items to a cart using a voucher or placing an order
|
||||
consisting of cart positions that don't have a valid reservation for much longer. In these cases, it is
|
||||
currently not realistically possible to exceed selling **approx. 500 orders per minute per event**, even
|
||||
if you add more hardware.
|
||||
If you have an unlimited number of tickets, we can apply fewer locking and we've reached **approx.
|
||||
1500 orders per minute per event** in benchmarks, although even more should be possible.
|
||||
|
||||
We're working to reduce the number of cases in which this is relevant and thereby improve the possible
|
||||
throughput. If you want to use pretix for an event with 10,000+ tickets that are likely to be sold out
|
||||
within minutes, please get in touch to discuss possible solutions. We'll work something out for you!
|
||||
|
||||
|
||||
.. _object storage cluster: https://behind.pretix.eu/2018/03/20/high-available-cdn/
|
||||
@@ -30,6 +30,7 @@ type string The expected ty
|
||||
* ``D`` – date
|
||||
* ``H`` – time
|
||||
* ``W`` – date and time
|
||||
* ``CC`` – country code (ISO 3666-1 alpha-2)
|
||||
required boolean If ``true``, the question needs to be filled out.
|
||||
position integer An integer, used for sorting
|
||||
items list of integers List of item IDs this question is assigned to.
|
||||
@@ -38,6 +39,8 @@ identifier string An arbitrary st
|
||||
ask_during_checkin boolean If ``true``, this question will not be asked while
|
||||
buying the ticket, but will show up when redeeming
|
||||
the ticket instead.
|
||||
hidden boolean If ``true``, the question will only be shown in the
|
||||
backend.
|
||||
options list of objects In case of question type ``C`` or ``M``, this lists the
|
||||
available objects. Only writable during creation,
|
||||
use separate endpoint to modify this later.
|
||||
@@ -68,6 +71,10 @@ dependency_value string The value ``dep
|
||||
Write methods have been added. The attribute ``identifier`` has been added to both the resource itself and the
|
||||
options resource. The ``position`` attribute has been added to the options resource.
|
||||
|
||||
.. versionchanged:: 2.7
|
||||
|
||||
The attribute ``hidden`` and the question type ``CC`` have been added.
|
||||
|
||||
Endpoints
|
||||
---------
|
||||
|
||||
@@ -110,6 +117,7 @@ Endpoints
|
||||
"position": 1,
|
||||
"identifier": "WY3TP9SL",
|
||||
"ask_during_checkin": false,
|
||||
"hidden": false,
|
||||
"dependency_question": null,
|
||||
"dependency_value": null,
|
||||
"options": [
|
||||
@@ -177,6 +185,7 @@ Endpoints
|
||||
"position": 1,
|
||||
"identifier": "WY3TP9SL",
|
||||
"ask_during_checkin": false,
|
||||
"hidden": false,
|
||||
"dependency_question": null,
|
||||
"dependency_value": null,
|
||||
"options": [
|
||||
@@ -228,6 +237,7 @@ Endpoints
|
||||
"items": [1, 2],
|
||||
"position": 1,
|
||||
"ask_during_checkin": false,
|
||||
"hidden": false,
|
||||
"dependency_question": null,
|
||||
"dependency_value": null,
|
||||
"options": [
|
||||
@@ -261,6 +271,7 @@ Endpoints
|
||||
"position": 1,
|
||||
"identifier": "WY3TP9SL",
|
||||
"ask_during_checkin": false,
|
||||
"hidden": false,
|
||||
"dependency_question": null,
|
||||
"dependency_value": null,
|
||||
"options": [
|
||||
@@ -332,6 +343,7 @@ Endpoints
|
||||
"position": 2,
|
||||
"identifier": "WY3TP9SL",
|
||||
"ask_during_checkin": false,
|
||||
"hidden": false,
|
||||
"dependency_question": null,
|
||||
"dependency_value": null,
|
||||
"options": [
|
||||
|
||||
@@ -12,7 +12,7 @@ Core
|
||||
|
||||
.. automodule:: pretix.base.signals
|
||||
:members: periodic_task, event_live_issues, event_copy_data, email_filter, register_notification_types,
|
||||
item_copy_data, register_sales_channels
|
||||
item_copy_data, register_sales_channels, register_global_settings
|
||||
|
||||
Order events
|
||||
""""""""""""
|
||||
@@ -26,11 +26,7 @@ Frontend
|
||||
--------
|
||||
|
||||
.. automodule:: pretix.presale.signals
|
||||
:members: html_head, html_footer, footer_link, front_page_top, front_page_bottom, fee_calculation_for_cart, contact_form_fields, question_form_fields, checkout_confirm_messages, checkout_confirm_page_content, checkout_all_optional, html_page_header, sass_preamble, sass_postamble
|
||||
|
||||
|
||||
.. automodule:: pretix.presale.signals
|
||||
:members: order_info, order_meta_from_request
|
||||
:members: html_head, html_footer, footer_link, front_page_top, front_page_bottom, fee_calculation_for_cart, contact_form_fields, question_form_fields, checkout_confirm_messages, checkout_confirm_page_content, checkout_all_optional, html_page_header, sass_preamble, sass_postamble, checkout_flow_steps, order_info, order_meta_from_request
|
||||
|
||||
Request flow
|
||||
""""""""""""
|
||||
|
||||
@@ -49,15 +49,19 @@ description string A more verbose description of what your
|
||||
visible boolean (optional) ``True`` by default, can hide a plugin so it cannot be normally activated.
|
||||
restricted boolean (optional) ``False`` by default, restricts a plugin such that it can only be enabled
|
||||
for an event by system administrators / superusers.
|
||||
compatibility string Specifier for compatible pretix versions.
|
||||
================== ==================== ===========================================================
|
||||
|
||||
A working example would be::
|
||||
|
||||
from django.apps import AppConfig
|
||||
try:
|
||||
from pretix.base.plugins import PluginConfig
|
||||
except ImportError:
|
||||
raise RuntimeError("Please use pretix 2.7 or above to run this plugin!")
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
class PaypalApp(AppConfig):
|
||||
class PaypalApp(PluginConfig):
|
||||
name = 'pretix_paypal'
|
||||
verbose_name = _("PayPal")
|
||||
|
||||
@@ -68,6 +72,7 @@ A working example would be::
|
||||
visible = True
|
||||
restricted = False
|
||||
description = _("This plugin allows you to receive payments via PayPal")
|
||||
compatibility = "pretix>=2.7.0"
|
||||
|
||||
|
||||
default_app_config = 'pretix_paypal.PaypalApp'
|
||||
|
||||
@@ -23,7 +23,7 @@ Organizers and events
|
||||
:members:
|
||||
|
||||
.. autoclass:: pretix.base.models.Event
|
||||
:members: get_date_from_display, get_time_from_display, get_date_to_display, get_date_range_display, presale_has_ended, presale_is_running, cache, lock, get_plugins, get_mail_backend, payment_term_last, get_payment_providers, get_invoice_renderers, active_subevents, invoice_renderer, settings
|
||||
:members: get_date_from_display, get_time_from_display, get_date_to_display, get_date_range_display, presale_has_ended, presale_is_running, cache, lock, get_plugins, get_mail_backend, payment_term_last, get_payment_providers, get_invoice_renderers, invoice_renderer, settings
|
||||
|
||||
.. autoclass:: pretix.base.models.SubEvent
|
||||
:members: get_date_from_display, get_time_from_display, get_date_to_display, get_date_range_display, presale_has_ended, presale_is_running
|
||||
|
||||
@@ -15,6 +15,7 @@ boolean
|
||||
booleans
|
||||
cancelled
|
||||
casted
|
||||
Ceph
|
||||
checkbox
|
||||
checksum
|
||||
config
|
||||
@@ -53,6 +54,7 @@ linters
|
||||
memcached
|
||||
metadata
|
||||
middleware
|
||||
Minio
|
||||
mixin
|
||||
mixins
|
||||
multi
|
||||
@@ -94,6 +96,7 @@ renderer
|
||||
renderers
|
||||
reportlab
|
||||
SaaS
|
||||
scalability
|
||||
screenshot
|
||||
scss
|
||||
searchable
|
||||
@@ -124,6 +127,7 @@ unconfigured
|
||||
unix
|
||||
unprefixed
|
||||
untrusted
|
||||
uptime
|
||||
username
|
||||
url
|
||||
versa
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = "2.6.0"
|
||||
__version__ = "2.7.2"
|
||||
|
||||
@@ -207,7 +207,8 @@ class QuestionSerializer(I18nAwareModelSerializer):
|
||||
class Meta:
|
||||
model = Question
|
||||
fields = ('id', 'question', 'type', 'required', 'items', 'options', 'position',
|
||||
'ask_during_checkin', 'identifier', 'dependency_question', 'dependency_value')
|
||||
'ask_during_checkin', 'identifier', 'dependency_question', 'dependency_value',
|
||||
'hidden')
|
||||
|
||||
def validate_identifier(self, value):
|
||||
Question._clean_identifier(self.context['event'], value, self.instance)
|
||||
|
||||
@@ -30,13 +30,13 @@ class DekodiNREIExporter(BaseExporter):
|
||||
for l in invoice.lines.all():
|
||||
positions.append({
|
||||
'ADes': l.description.replace("<br />", "\n"),
|
||||
'ANetA': round(float(l.net_value), 2),
|
||||
'ANetA': round(float((-1 if invoice.is_cancellation else 1) * l.net_value), 2),
|
||||
'ANo': self.event.slug,
|
||||
'AQ': -1 if invoice.is_cancellation else 1,
|
||||
'AVatP': round(float(l.tax_rate), 2),
|
||||
'DIDt': (l.subevent or invoice.order.event).date_from.isoformat().replace('Z', '+00:00'),
|
||||
'PosGrossA': round(float((-1 if invoice.is_cancellation else 1) * l.gross_value), 2),
|
||||
'PosNetA': round(float((-1 if invoice.is_cancellation else 1) * l.net_value), 2),
|
||||
'PosGrossA': round(float(l.gross_value), 2),
|
||||
'PosNetA': round(float(l.net_value), 2),
|
||||
})
|
||||
gross_total += l.gross_value
|
||||
net_total += l.net_value
|
||||
@@ -50,7 +50,7 @@ class DekodiNREIExporter(BaseExporter):
|
||||
if p.provider == 'paypal':
|
||||
paypal_email = p.info_data.get('payer', {}).get('payer_info', {}).get('email')
|
||||
try:
|
||||
ppid = p.info_data['transactions'][0]['related_resources']['sale']['id']
|
||||
ppid = p.info_data['transactions'][0]['related_resources'][0]['sale']['id']
|
||||
except:
|
||||
ppid = p.info_data.get('id')
|
||||
payments.append({
|
||||
|
||||
@@ -12,6 +12,7 @@ from django.core.exceptions import ValidationError
|
||||
from django.utils.html import escape
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django_countries.fields import CountryField
|
||||
|
||||
from pretix.base.forms.widgets import (
|
||||
BusinessBooleanRadio, DatePickerWidget, SplitDateTimePickerWidget,
|
||||
@@ -213,6 +214,14 @@ class BaseQuestionsForm(forms.Form):
|
||||
widget=forms.Textarea,
|
||||
initial=initial.answer if initial else None,
|
||||
)
|
||||
elif q.type == Question.TYPE_COUNTRYCODE:
|
||||
field = CountryField().formfield(
|
||||
label=label, required=required,
|
||||
help_text=help_text,
|
||||
widget=forms.Select,
|
||||
empty_label='',
|
||||
initial=initial.answer if initial else None,
|
||||
)
|
||||
elif q.type == Question.TYPE_CHOICE:
|
||||
field = forms.ModelChoiceField(
|
||||
queryset=q.options,
|
||||
|
||||
51
src/pretix/base/management/commands/makemigrations.py
Normal file
51
src/pretix/base/management/commands/makemigrations.py
Normal file
@@ -0,0 +1,51 @@
|
||||
"""
|
||||
Django, for theoretically very valid reasons, creates migrations for *every single thing*
|
||||
we change on a model. Even the `help_text`! This makes sense, as we don't know if any
|
||||
database backend unknown to us might actually use this information for its database schema.
|
||||
|
||||
However, pretix only supports PostgreSQL, MySQL, MariaDB and SQLite and we can be pretty
|
||||
certain that some changes to models will never require a change to the database. In this case,
|
||||
not creating a migration for certain changes will save us some performance while applying them
|
||||
*and* allow for a cleaner git history. Win-win!
|
||||
|
||||
Only caveat is that we need to do some dirty monkeypatching to achieve it...
|
||||
"""
|
||||
from django.core.management.commands.makemigrations import Command as Parent
|
||||
from django.db import models
|
||||
from django.db.migrations.operations import models as modelops
|
||||
from django_countries.fields import CountryField
|
||||
|
||||
modelops.AlterModelOptions.ALTER_OPTION_KEYS.remove("verbose_name")
|
||||
modelops.AlterModelOptions.ALTER_OPTION_KEYS.remove("verbose_name_plural")
|
||||
modelops.AlterModelOptions.ALTER_OPTION_KEYS.remove("ordering")
|
||||
modelops.AlterModelOptions.ALTER_OPTION_KEYS.remove("get_latest_by")
|
||||
modelops.AlterModelOptions.ALTER_OPTION_KEYS.remove("default_manager_name")
|
||||
modelops.AlterModelOptions.ALTER_OPTION_KEYS.remove("permissions")
|
||||
modelops.AlterModelOptions.ALTER_OPTION_KEYS.remove("default_permissions")
|
||||
IGNORED_ATTRS = [
|
||||
# (field type, attribute name, blacklist of field sub-types)
|
||||
(models.Field, 'verbose_name', []),
|
||||
(models.Field, 'help_text', []),
|
||||
(models.Field, 'validators', []),
|
||||
(models.Field, 'editable', [models.DateField, models.DateTimeField, models.DateField, models.BinaryField]),
|
||||
(models.Field, 'blank', [models.DateField, models.DateTimeField, models.AutoField, models.NullBooleanField,
|
||||
models.TimeField]),
|
||||
(models.CharField, 'choices', [CountryField])
|
||||
]
|
||||
|
||||
original_deconstruct = models.Field.deconstruct
|
||||
|
||||
|
||||
def new_deconstruct(self):
|
||||
name, path, args, kwargs = original_deconstruct(self)
|
||||
for ftype, attr, blacklist in IGNORED_ATTRS:
|
||||
if isinstance(self, ftype) and not any(isinstance(self, ft) for ft in blacklist):
|
||||
kwargs.pop(attr, None)
|
||||
return name, path, args, kwargs
|
||||
|
||||
|
||||
models.Field.deconstruct = new_deconstruct
|
||||
|
||||
|
||||
class Command(Parent):
|
||||
pass
|
||||
28
src/pretix/base/management/commands/migrate.py
Normal file
28
src/pretix/base/management/commands/migrate.py
Normal file
@@ -0,0 +1,28 @@
|
||||
"""
|
||||
Django tries to be helpful by suggesting to run "makemigrations" in red font on every "migrate"
|
||||
run when there are things we have no migrations for. Usually, this is intended, and running
|
||||
"makemigrations" can really screw up the environment of a user, so we want to prevent novice
|
||||
users from doing that by going really dirty and fitlering it from the output.
|
||||
"""
|
||||
import sys
|
||||
|
||||
from django.core.management.base import OutputWrapper
|
||||
from django.core.management.commands.migrate import Command as Parent
|
||||
|
||||
|
||||
class OutputFilter(OutputWrapper):
|
||||
blacklist = (
|
||||
"Your models have changes that are not yet reflected",
|
||||
"Run 'manage.py makemigrations' to make new "
|
||||
)
|
||||
|
||||
def write(self, msg, style_func=None, ending=None):
|
||||
if any(b in msg for b in self.blacklist):
|
||||
return
|
||||
super().write(msg, style_func, ending)
|
||||
|
||||
|
||||
class Command(Parent):
|
||||
def __init__(self, stdout=None, stderr=None, no_color=False, force_color=False):
|
||||
super().__init__(stdout, stderr, no_color, force_color)
|
||||
self.stdout = OutputFilter(stdout or sys.stdout)
|
||||
22
src/pretix/base/migrations/0119_auto_20190509_0654.py
Normal file
22
src/pretix/base/migrations/0119_auto_20190509_0654.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# Generated by Django 2.2 on 2019-05-09 06:54
|
||||
|
||||
import django.db.models.deletion
|
||||
import jsonfallback.fields
|
||||
from django.db import migrations, models
|
||||
|
||||
import pretix.base.models.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('pretixbase', '0118_auto_20190423_0839'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='question',
|
||||
name='hidden',
|
||||
field=models.BooleanField(default=False, help_text='This question will only show up in the backend.', verbose_name='Hidden question'),
|
||||
),
|
||||
]
|
||||
77
src/pretix/base/migrations/0120_auto_20190509_0736.py
Normal file
77
src/pretix/base/migrations/0120_auto_20190509_0736.py
Normal file
@@ -0,0 +1,77 @@
|
||||
# Generated by Django 2.2 on 2019-05-09 07:36
|
||||
|
||||
import django.db.models.deletion
|
||||
import jsonfallback.fields
|
||||
from django.db import migrations, models
|
||||
|
||||
import pretix.base.models.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('pretixbase', '0119_auto_20190509_0654'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='cartposition',
|
||||
name='attendee_name_parts',
|
||||
field=jsonfallback.fields.FallbackJSONField(default=dict),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='cartposition',
|
||||
name='subevent',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='pretixbase.SubEvent'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='cartposition',
|
||||
name='voucher',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='pretixbase.Voucher'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='event',
|
||||
name='is_public',
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='invoiceaddress',
|
||||
name='name_parts',
|
||||
field=jsonfallback.fields.FallbackJSONField(default=dict),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='item',
|
||||
name='sales_channels',
|
||||
field=pretix.base.models.fields.MultiStringField(default=['web']),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='order',
|
||||
name='sales_channel',
|
||||
field=models.CharField(default='web', max_length=190),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='orderposition',
|
||||
name='attendee_name_parts',
|
||||
field=jsonfallback.fields.FallbackJSONField(default=dict),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='orderposition',
|
||||
name='subevent',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='pretixbase.SubEvent'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='orderposition',
|
||||
name='voucher',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='pretixbase.Voucher'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='staffsessionauditlog',
|
||||
name='method',
|
||||
field=models.CharField(max_length=255),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='user',
|
||||
name='email',
|
||||
field=models.EmailField(db_index=True, max_length=190, null=True, unique=True),
|
||||
),
|
||||
]
|
||||
@@ -164,7 +164,7 @@ class EventMixin:
|
||||
def annotated(cls, qs, channel='web'):
|
||||
from pretix.base.models import Item, ItemVariation, Quota
|
||||
|
||||
sq_active_item = Item.objects.filter_available(channel=channel).filter(
|
||||
sq_active_item = Item.objects.using(settings.DATABASE_REPLICA).filter_available(channel=channel).filter(
|
||||
Q(variations__isnull=True)
|
||||
& Q(quotas__pk=OuterRef('pk'))
|
||||
).order_by().values_list('quotas__pk').annotate(
|
||||
@@ -186,7 +186,7 @@ class EventMixin:
|
||||
Prefetch(
|
||||
'quotas',
|
||||
to_attr='active_quotas',
|
||||
queryset=Quota.objects.annotate(
|
||||
queryset=Quota.objects.using(settings.DATABASE_REPLICA).annotate(
|
||||
active_items=Subquery(sq_active_item, output_field=models.TextField()),
|
||||
active_variations=Subquery(sq_active_variation, output_field=models.TextField()),
|
||||
).exclude(
|
||||
|
||||
@@ -16,6 +16,7 @@ from django.utils.crypto import get_random_string
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.timezone import is_naive, make_aware, now
|
||||
from django.utils.translation import pgettext_lazy, ugettext_lazy as _
|
||||
from django_countries.fields import Country
|
||||
from i18nfield.fields import I18nCharField, I18nTextField
|
||||
|
||||
from pretix.base.models import fields
|
||||
@@ -332,7 +333,9 @@ class Item(LoggedModel):
|
||||
require_bundling = models.BooleanField(
|
||||
verbose_name=_('Only sell this product as part of a bundle'),
|
||||
default=False,
|
||||
help_text=_('If this option is set, the product will only be sold as part of bundle products.')
|
||||
help_text=_('If this option is set, the product will only be sold as part of bundle products. Do '
|
||||
'<strong>not</strong> check this option if you want to use this product as an add-on product, '
|
||||
'but only for fixed bundles!')
|
||||
)
|
||||
allow_cancel = models.BooleanField(
|
||||
verbose_name=_('Allow product to be canceled'),
|
||||
@@ -893,6 +896,8 @@ class Question(LoggedModel):
|
||||
:param items: A set of ``Items`` objects that this question should be applied to
|
||||
:param ask_during_checkin: Whether to ask this question during check-in instead of during check-out.
|
||||
:type ask_during_checkin: bool
|
||||
:param hidden: Whether to only show the question in the backend
|
||||
:type hidden: bool
|
||||
:param identifier: An arbitrary, internal identifier
|
||||
:type identifier: str
|
||||
:param dependency_question: This question will only show up if the referenced question is set to `dependency_value`.
|
||||
@@ -910,6 +915,7 @@ class Question(LoggedModel):
|
||||
TYPE_DATE = "D"
|
||||
TYPE_TIME = "H"
|
||||
TYPE_DATETIME = "W"
|
||||
TYPE_COUNTRYCODE = "CC"
|
||||
TYPE_CHOICES = (
|
||||
(TYPE_NUMBER, _("Number")),
|
||||
(TYPE_STRING, _("Text (one line)")),
|
||||
@@ -921,6 +927,7 @@ class Question(LoggedModel):
|
||||
(TYPE_DATE, _("Date")),
|
||||
(TYPE_TIME, _("Time")),
|
||||
(TYPE_DATETIME, _("Date and time")),
|
||||
(TYPE_COUNTRYCODE, _("Country code (ISO 3166-1 alpha-2)")),
|
||||
)
|
||||
|
||||
event = models.ForeignKey(
|
||||
@@ -968,6 +975,11 @@ class Question(LoggedModel):
|
||||
'pretixdesk 0.2 or newer.'),
|
||||
default=False
|
||||
)
|
||||
hidden = models.BooleanField(
|
||||
verbose_name=_('Hidden question'),
|
||||
help_text=_('This question will only show up in the backend.'),
|
||||
default=False
|
||||
)
|
||||
dependency_question = models.ForeignKey(
|
||||
'Question', null=True, blank=True, on_delete=models.SET_NULL, related_name='dependent_questions'
|
||||
)
|
||||
@@ -1071,6 +1083,12 @@ class Question(LoggedModel):
|
||||
return dt
|
||||
except:
|
||||
raise ValidationError(_('Invalid datetime input.'))
|
||||
elif self.type == Question.TYPE_COUNTRYCODE and answer:
|
||||
c = Country(answer.upper())
|
||||
if c.name:
|
||||
return answer
|
||||
else:
|
||||
raise ValidationError(_('Unknown country code.'))
|
||||
|
||||
return answer
|
||||
|
||||
@@ -1290,7 +1308,8 @@ class Quota(LoggedModel):
|
||||
'cached_availability_state', 'cached_availability_number', 'cached_availability_time',
|
||||
'cached_availability_paid_orders'
|
||||
],
|
||||
clear_cache=False
|
||||
clear_cache=False,
|
||||
using='default'
|
||||
)
|
||||
|
||||
if _cache is not None:
|
||||
|
||||
@@ -24,7 +24,7 @@ from django.utils.formats import date_format
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.timezone import make_aware, now
|
||||
from django.utils.translation import pgettext_lazy, ugettext_lazy as _
|
||||
from django_countries.fields import CountryField
|
||||
from django_countries.fields import Country, CountryField
|
||||
from i18nfield.strings import LazyI18nString
|
||||
from jsonfallback.fields import FallbackJSONField
|
||||
|
||||
@@ -32,6 +32,7 @@ from pretix.base.decimal import round_decimal
|
||||
from pretix.base.i18n import language
|
||||
from pretix.base.models import User
|
||||
from pretix.base.reldate import RelativeDateWrapper
|
||||
from pretix.base.services.locking import NoLockManager
|
||||
from pretix.base.settings import PERSON_NAME_SCHEMES
|
||||
|
||||
from .base import LockModel, LoggedModel
|
||||
@@ -859,6 +860,8 @@ class QuestionAnswer(models.Model):
|
||||
return date_format(d, "TIME_FORMAT")
|
||||
except ValueError:
|
||||
return self.answer
|
||||
elif self.question.type == Question.TYPE_COUNTRYCODE and self.answer:
|
||||
return Country(self.answer).name or self.answer
|
||||
else:
|
||||
return self.answer
|
||||
|
||||
@@ -975,7 +978,8 @@ class AbstractPosition(models.Model):
|
||||
if hasattr(self.item, 'questions_to_ask'):
|
||||
questions = list(copy.copy(q) for q in self.item.questions_to_ask)
|
||||
else:
|
||||
questions = list(copy.copy(q) for q in self.item.questions.filter(ask_during_checkin=False))
|
||||
questions = list(copy.copy(q) for q in self.item.questions.filter(ask_during_checkin=False,
|
||||
hidden=False))
|
||||
else:
|
||||
questions = list(copy.copy(q) for q in self.item.questions.all())
|
||||
|
||||
@@ -1222,13 +1226,13 @@ class OrderPayment(models.Model):
|
||||
if (self.order.status == Order.STATUS_PENDING and self.order.expires > now() + timedelta(hours=12)) or not lock:
|
||||
# Performance optimization. In this case, there's really no reason to lock everything and an atomic
|
||||
# database transaction is more than enough.
|
||||
with transaction.atomic():
|
||||
self._mark_paid(force, count_waitinglist, user, auth, overpaid=payment_sum - refund_sum > self.order.total,
|
||||
ignore_date=ignore_date)
|
||||
lockfn = NoLockManager
|
||||
else:
|
||||
with self.order.event.lock():
|
||||
self._mark_paid(force, count_waitinglist, user, auth, overpaid=payment_sum - refund_sum > self.order.total,
|
||||
ignore_date=ignore_date)
|
||||
lockfn = self.order.event.lock
|
||||
|
||||
with lockfn():
|
||||
self._mark_paid(force, count_waitinglist, user, auth, overpaid=payment_sum - refund_sum > self.order.total,
|
||||
ignore_date=ignore_date)
|
||||
|
||||
invoice = None
|
||||
if invoice_qualified(self.order):
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import sys
|
||||
from enum import Enum
|
||||
from typing import List
|
||||
|
||||
from django.apps import apps
|
||||
from django.apps import AppConfig, apps
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
||||
|
||||
class PluginType(Enum):
|
||||
@@ -39,3 +41,22 @@ def get_all_plugins(event=None) -> List[type]:
|
||||
plugins,
|
||||
key=lambda m: (0 if m.module.startswith('pretix.') else 1, str(m.name).lower().replace('pretix ', ''))
|
||||
)
|
||||
|
||||
|
||||
class PluginConfig(AppConfig):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if not hasattr(self, 'PretixPluginMeta'):
|
||||
raise ImproperlyConfigured("A pretix plugin config should have a PretixPluginMeta inner class.")
|
||||
|
||||
if hasattr(self.PretixPluginMeta, 'compatibility'):
|
||||
import pkg_resources
|
||||
try:
|
||||
pkg_resources.require(self.PretixPluginMeta.compatibility)
|
||||
except pkg_resources.VersionConflict as e:
|
||||
print("Incompatible plugins found!")
|
||||
print("Plugin {} requires you to have {}, but you installed {}.".format(
|
||||
self.name, e.req, e.dist
|
||||
))
|
||||
sys.exit(1)
|
||||
|
||||
@@ -5,7 +5,7 @@ from typing import List, Optional
|
||||
|
||||
from celery.exceptions import MaxRetriesExceededError
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import transaction
|
||||
from django.db import DatabaseError, transaction
|
||||
from django.db.models import Q
|
||||
from django.dispatch import receiver
|
||||
from django.utils.timezone import make_aware, now
|
||||
@@ -21,7 +21,7 @@ from pretix.base.models.orders import OrderFee
|
||||
from pretix.base.models.tax import TAXED_ZERO, TaxedPrice, TaxRule
|
||||
from pretix.base.reldate import RelativeDateWrapper
|
||||
from pretix.base.services.checkin import _save_answers
|
||||
from pretix.base.services.locking import LockTimeoutException
|
||||
from pretix.base.services.locking import LockTimeoutException, NoLockManager
|
||||
from pretix.base.services.pricing import get_price
|
||||
from pretix.base.services.tasks import ProfiledTask
|
||||
from pretix.base.settings import PERSON_NAME_SCHEMES
|
||||
@@ -634,7 +634,7 @@ class CartManager:
|
||||
Q(voucher=voucher) & Q(event=self.event) &
|
||||
Q(expires__gte=self.now_dt)
|
||||
).exclude(pk__in=[
|
||||
op.position.voucher_id for op in self._operations if isinstance(op, self.ExtendOperation)
|
||||
op.position.id for op in self._operations if isinstance(op, self.ExtendOperation)
|
||||
])
|
||||
cart_count = redeemed_in_carts.count()
|
||||
v_avail = voucher.max_usages - voucher.redeemed - cart_count
|
||||
@@ -791,7 +791,11 @@ class CartManager:
|
||||
if available_count == 1:
|
||||
op.position.expires = self._expiry
|
||||
op.position.price = op.price.gross
|
||||
op.position.save()
|
||||
try:
|
||||
op.position.save(force_update=True)
|
||||
except DatabaseError:
|
||||
# Best effort... The position might have been deleted in the meantime!
|
||||
pass
|
||||
elif available_count == 0:
|
||||
op.position.addons.all().delete()
|
||||
op.position.delete()
|
||||
@@ -806,17 +810,33 @@ class CartManager:
|
||||
CartPosition.objects.bulk_create([p for p in new_cart_positions if not getattr(p, '_answers', None) and not p.pk])
|
||||
return err
|
||||
|
||||
def _require_locking(self):
|
||||
if self._voucher_use_diff:
|
||||
# If any vouchers are used, we lock to make sure we don't redeem them to often
|
||||
return True
|
||||
|
||||
if self._quota_diff and any(q.size is not None for q in self._quota_diff):
|
||||
# If any quotas are affected that are not unlimited, we lock
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def commit(self):
|
||||
self._check_presale_dates()
|
||||
self._check_max_cart_size()
|
||||
self._calculate_expiry()
|
||||
|
||||
with self.event.lock() as now_dt:
|
||||
err = self._delete_out_of_timeframe()
|
||||
err = self.extend_expired_positions() or err
|
||||
|
||||
lockfn = NoLockManager
|
||||
if self._require_locking():
|
||||
lockfn = self.event.lock
|
||||
|
||||
with lockfn() as now_dt:
|
||||
with transaction.atomic():
|
||||
self.now_dt = now_dt
|
||||
self._extend_expiry_of_valid_existing_positions()
|
||||
err = self._delete_out_of_timeframe()
|
||||
err = self.extend_expired_positions() or err
|
||||
err = self._perform_operations() or err
|
||||
if err:
|
||||
raise CartError(err)
|
||||
|
||||
@@ -13,6 +13,18 @@ logger = logging.getLogger('pretix.base.locking')
|
||||
LOCK_TIMEOUT = 120
|
||||
|
||||
|
||||
class NoLockManager:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def __enter__(self):
|
||||
return now()
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
if exc_type is not None:
|
||||
return False
|
||||
|
||||
|
||||
class LockManager:
|
||||
def __init__(self, event):
|
||||
self.event = event
|
||||
|
||||
@@ -39,7 +39,7 @@ from pretix.base.services import tickets
|
||||
from pretix.base.services.invoices import (
|
||||
generate_cancellation, generate_invoice, invoice_qualified,
|
||||
)
|
||||
from pretix.base.services.locking import LockTimeoutException
|
||||
from pretix.base.services.locking import LockTimeoutException, NoLockManager
|
||||
from pretix.base.services.mail import SendMailException
|
||||
from pretix.base.services.pricing import get_price
|
||||
from pretix.base.services.tasks import ProfiledTask
|
||||
@@ -531,7 +531,6 @@ def _check_positions(event: Event, now_dt: datetime, positions: List[CartPositio
|
||||
continue
|
||||
|
||||
if price.gross != cp.price and not (cp.item.free_price and cp.price > price.gross):
|
||||
positions[i] = cp
|
||||
cp.price = price.gross
|
||||
cp.includes_tax = bool(price.rate)
|
||||
cp.save()
|
||||
@@ -555,7 +554,6 @@ def _check_positions(event: Event, now_dt: datetime, positions: List[CartPositio
|
||||
break
|
||||
|
||||
if quota_ok:
|
||||
positions[i] = cp
|
||||
cp.expires = now_dt + timedelta(
|
||||
minutes=event.settings.get('reservation_time', as_type=int))
|
||||
cp.save()
|
||||
@@ -665,9 +663,18 @@ def _perform_order(event: str, payment_provider: str, position_ids: List[str],
|
||||
except InvoiceAddress.DoesNotExist:
|
||||
pass
|
||||
|
||||
with event.lock() as now_dt:
|
||||
positions = list(CartPosition.objects.filter(
|
||||
id__in=position_ids).select_related('item', 'variation', 'subevent', 'addon_to').prefetch_related('addons'))
|
||||
positions = CartPosition.objects.filter(id__in=position_ids, event=event)
|
||||
|
||||
lockfn = NoLockManager
|
||||
locked = False
|
||||
if positions.filter(Q(voucher__isnull=False) | Q(expires__lt=now() + timedelta(minutes=2))).exists():
|
||||
# Performance optimization: If no voucher is used and no cart position is dangerously close to its expiry date,
|
||||
# creating this order shouldn't be prone to any race conditions and we don't need to lock the event.
|
||||
locked = True
|
||||
lockfn = event.lock
|
||||
|
||||
with lockfn() as now_dt:
|
||||
positions = list(positions.select_related('item', 'variation', 'subevent', 'addon_to').prefetch_related('addons'))
|
||||
if len(positions) == 0:
|
||||
raise OrderError(error_messages['empty'])
|
||||
if len(position_ids) != len(positions):
|
||||
@@ -679,7 +686,7 @@ def _perform_order(event: str, payment_provider: str, position_ids: List[str],
|
||||
free_order_flow = payment and payment_provider == 'free' and order.total == Decimal('0.00') and not order.require_approval
|
||||
if free_order_flow:
|
||||
try:
|
||||
payment.confirm(send_mail=False, lock=False)
|
||||
payment.confirm(send_mail=False, lock=not locked)
|
||||
except Quota.QuotaExceededException:
|
||||
pass
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ def money_filter(value: Decimal, arg='', hide_currency=False):
|
||||
raise TypeError("Invalid data type passed to money filter: %r" % type(value))
|
||||
if not arg:
|
||||
raise ValueError("No currency passed.")
|
||||
arg = arg.upper()
|
||||
|
||||
places = settings.CURRENCY_PLACES.get(arg, 2)
|
||||
rounded = value.quantize(Decimal('1') / 10 ** places, ROUND_HALF_UP)
|
||||
|
||||
@@ -279,7 +279,7 @@ class EventSettingsForm(SettingsForm):
|
||||
)
|
||||
display_net_prices = forms.BooleanField(
|
||||
label=_("Show net prices instead of gross prices in the product list (not recommended!)"),
|
||||
help_text=_("Independent of your choice, the cart will show gross prices as this the price that needs to be "
|
||||
help_text=_("Independent of your choice, the cart will show gross prices as this is the price that needs to be "
|
||||
"paid"),
|
||||
required=False
|
||||
)
|
||||
|
||||
@@ -82,6 +82,7 @@ class QuestionForm(I18nModelForm):
|
||||
'type',
|
||||
'required',
|
||||
'ask_during_checkin',
|
||||
'hidden',
|
||||
'identifier',
|
||||
'items',
|
||||
'dependency_question',
|
||||
|
||||
@@ -2,16 +2,6 @@ from django.dispatch import Signal
|
||||
|
||||
from pretix.base.signals import DeprecatedSignal, EventPluginSignal
|
||||
|
||||
restriction_formset = EventPluginSignal(
|
||||
providing_args=["item"]
|
||||
)
|
||||
"""
|
||||
This signal is sent out to build configuration forms for all restriction formsets
|
||||
(see plugin API documentation for details).
|
||||
|
||||
As with all plugin signals, the ``sender`` keyword argument will contain the event.
|
||||
"""
|
||||
|
||||
html_page_start = Signal(
|
||||
providing_args=[]
|
||||
)
|
||||
|
||||
@@ -38,7 +38,10 @@
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="3">
|
||||
{% trans "There are no payment providers available. Please go to the plugin settings and activate one or more payment plugins." %}
|
||||
{% url "control:event.settings.plugins" event=request.event.slug organizer=request.organizer.slug as plugin_settings_url %}
|
||||
{% blocktrans trimmed with plugin_settings_href='href="'|add:plugin_settings_url|add:'"'|safe %}
|
||||
There are no payment providers available. Please go to the <a {{ plugin_settings_href }}>plugin settings</a> and activate one or more payment plugins.
|
||||
{% endblocktrans %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
@@ -40,7 +40,10 @@
|
||||
</div>
|
||||
{% empty %}
|
||||
<div class="alert alert-warning">
|
||||
{% trans "There are no ticket outputs available. Please go to the plugin settings and activate one or more ticket output plugins." %}</em>
|
||||
{% url "control:event.settings.plugins" event=request.event.slug organizer=request.organizer.slug as plugin_settings_url %}
|
||||
{% blocktrans trimmed with plugin_settings_href='href="'|add:plugin_settings_url|add:'"'|safe %}
|
||||
There are no ticket outputs available. Please go to the <a {{ plugin_settings_href }}>plugin settings</a> and activate one or more ticket output plugins.
|
||||
{% endblocktrans %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</fieldset>
|
||||
|
||||
@@ -109,6 +109,7 @@
|
||||
{% bootstrap_field form.help_text layout="control" %}
|
||||
{% bootstrap_field form.identifier layout="control" %}
|
||||
{% bootstrap_field form.ask_during_checkin layout="control" %}
|
||||
{% bootstrap_field form.hidden layout="control" %}
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-md-3 control-label" for="id_dependency_question">
|
||||
|
||||
@@ -15,6 +15,7 @@ from django.views.generic import ListView
|
||||
from django.views.generic.base import TemplateView
|
||||
from django.views.generic.detail import DetailView, SingleObjectMixin
|
||||
from django.views.generic.edit import DeleteView
|
||||
from django_countries.fields import Country
|
||||
|
||||
from pretix.base.forms import I18nFormSet
|
||||
from pretix.base.models import (
|
||||
@@ -444,6 +445,10 @@ class QuestionView(EventPermissionRequiredMixin, QuestionMixin, ChartContainingV
|
||||
a['alink'] = a['answer']
|
||||
a['answer'] = ugettext('Yes') if a['answer'] == 'True' else ugettext('No')
|
||||
a['answer_bool'] = a['answer'] == 'True'
|
||||
elif self.object.type == Question.TYPE_COUNTRYCODE:
|
||||
for a in qs:
|
||||
a['alink'] = a['answer']
|
||||
a['answer'] = Country(a['answer']).name or a['answer']
|
||||
|
||||
return list(qs)
|
||||
|
||||
|
||||
@@ -143,7 +143,7 @@ class VoucherDelete(EventPermissionRequiredMixin, DeleteView):
|
||||
messages.error(request, _('A voucher can not be deleted if it already has been redeemed.'))
|
||||
else:
|
||||
self.object.log_action('pretix.voucher.deleted', user=self.request.user)
|
||||
CartPosition.objects.filter(addon_to__voucher=False).delete()
|
||||
CartPosition.objects.filter(addon_to__voucher=self.object).delete()
|
||||
self.object.cartposition_set.all().delete()
|
||||
self.object.delete()
|
||||
messages.success(request, _('The selected voucher has been deleted.'))
|
||||
|
||||
@@ -76,3 +76,21 @@ class GroupConcat(Aggregate):
|
||||
function='string_agg',
|
||||
template="%(function)s(%(field)s::text, '%(separator)s')",
|
||||
)
|
||||
|
||||
|
||||
class ReplicaRouter:
|
||||
|
||||
def db_for_read(self, model, **hints):
|
||||
return 'default'
|
||||
|
||||
def db_for_write(self, model, **hints):
|
||||
return 'default'
|
||||
|
||||
def allow_relation(self, obj1, obj2, **hints):
|
||||
db_list = ('default', 'replica')
|
||||
if obj1._state.db in db_list and obj2._state.db in db_list:
|
||||
return True
|
||||
return None
|
||||
|
||||
def allow_migrate(self, db, app_label, model_name=None, **hintrs):
|
||||
return True
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-05-01 12:02+0000\n"
|
||||
"POT-Creation-Date: 2019-05-09 12:45+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: Automatically generated\n"
|
||||
"Language-Team: none\n"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-05-01 12:02+0000\n"
|
||||
"POT-Creation-Date: 2019-05-09 12:45+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: Automatically generated\n"
|
||||
"Language-Team: none\n"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-05-01 12:02+0000\n"
|
||||
"POT-Creation-Date: 2019-05-09 12:45+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: Automatically generated\n"
|
||||
"Language-Team: none\n"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-05-01 12:02+0000\n"
|
||||
"POT-Creation-Date: 2019-05-09 12:45+0000\n"
|
||||
"PO-Revision-Date: 2018-04-24 14:22+0000\n"
|
||||
"Last-Translator: Pernille Thorsen <perth@aarhus.dk>\n"
|
||||
"Language-Team: Danish <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,8 +7,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-05-01 12:02+0000\n"
|
||||
"PO-Revision-Date: 2019-04-23 08:55+0000\n"
|
||||
"POT-Creation-Date: 2019-05-09 12:45+0000\n"
|
||||
"PO-Revision-Date: 2019-05-01 12:13+0000\n"
|
||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||
"Language-Team: German <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
"de/>\n"
|
||||
@@ -222,10 +222,8 @@ msgid "Click to close"
|
||||
msgstr "Klicken zum Schließen"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/orderchange.js:24
|
||||
#, fuzzy
|
||||
#| msgid "Contacting Stripe …"
|
||||
msgid "Calculating default price…"
|
||||
msgstr "Kontaktiere Stripe …"
|
||||
msgstr "Berechne Standardpreis…"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:42
|
||||
msgid "Others"
|
||||
@@ -267,7 +265,7 @@ msgstr[1] ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:201
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr ""
|
||||
msgstr "Bitte tragen Sie eine Menge für eines der Produkte ein."
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:15
|
||||
msgctxt "widget"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,8 +7,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-05-01 12:02+0000\n"
|
||||
"PO-Revision-Date: 2019-04-23 08:55+0000\n"
|
||||
"POT-Creation-Date: 2019-05-09 12:45+0000\n"
|
||||
"PO-Revision-Date: 2019-05-01 12:12+0000\n"
|
||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||
"Language-Team: German (informal) <https://translate.pretix.eu/projects/"
|
||||
"pretix/pretix-js/de_Informal/>\n"
|
||||
@@ -221,10 +221,8 @@ msgid "Click to close"
|
||||
msgstr "Klicken zum Schließen"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/orderchange.js:24
|
||||
#, fuzzy
|
||||
#| msgid "Contacting Stripe …"
|
||||
msgid "Calculating default price…"
|
||||
msgstr "Kontaktiere Stripe …"
|
||||
msgstr "Berechne Standardpreis…"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:42
|
||||
msgid "Others"
|
||||
@@ -266,7 +264,7 @@ msgstr[1] ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:201
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr ""
|
||||
msgstr "Bitte trage eine Menge für eines der Produkte ein."
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:15
|
||||
msgctxt "widget"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-05-01 12:02+0000\n"
|
||||
"POT-Creation-Date: 2019-05-09 12:45+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-05-01 12:02+0000\n"
|
||||
"POT-Creation-Date: 2019-05-09 12:45+0000\n"
|
||||
"PO-Revision-Date: 2019-04-24 22:00+0000\n"
|
||||
"Last-Translator: ThanosTeste <testebasisth@unisystems.eu>\n"
|
||||
"Language-Team: Greek <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-05-01 12:02+0000\n"
|
||||
"POT-Creation-Date: 2019-05-09 12:45+0000\n"
|
||||
"PO-Revision-Date: 2019-03-31 08:00+0000\n"
|
||||
"Last-Translator: oocf <oswaldocerna@gmail.com>\n"
|
||||
"Language-Team: Spanish <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: French\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-05-01 12:02+0000\n"
|
||||
"POT-Creation-Date: 2019-05-09 12:45+0000\n"
|
||||
"PO-Revision-Date: 2018-10-28 10:23+0000\n"
|
||||
"Last-Translator: Arnaud Vergnet <keplyx@gmail.com>\n"
|
||||
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-05-01 12:02+0000\n"
|
||||
"POT-Creation-Date: 2019-05-09 12:45+0000\n"
|
||||
"PO-Revision-Date: 2019-01-02 08:20+0000\n"
|
||||
"Last-Translator: amefad <fame@libero.it>\n"
|
||||
"Language-Team: Italian <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
|
||||
15593
src/pretix/locale/nb_NO/LC_MESSAGES/django.po
Normal file
15593
src/pretix/locale/nb_NO/LC_MESSAGES/django.po
Normal file
File diff suppressed because it is too large
Load Diff
479
src/pretix/locale/nb_NO/LC_MESSAGES/djangojs.po
Normal file
479
src/pretix/locale/nb_NO/LC_MESSAGES/djangojs.po
Normal file
@@ -0,0 +1,479 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-05-09 12:45+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
|
||||
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:68
|
||||
msgid "Marked as paid"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:76
|
||||
msgid "Comment:"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:15
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:39
|
||||
msgid "Placed orders"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:15
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:39
|
||||
msgid "Paid orders"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:27
|
||||
msgid "Total revenue"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/stripe/static/pretixplugins/stripe/pretix-stripe.js:12
|
||||
msgid "Contacting Stripe …"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/stripe/static/pretixplugins/stripe/pretix-stripe.js:56
|
||||
msgid "Total"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:39
|
||||
#: pretix/static/pretixbase/js/asynctask.js:95
|
||||
msgid ""
|
||||
"Your request has been queued on the server and will now be processed. "
|
||||
"Depending on the size of your event, this might take up to a few minutes."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:45
|
||||
#: pretix/static/pretixbase/js/asynctask.js:101
|
||||
msgid ""
|
||||
"Your request arrived on the server but we still wait for it to be processed. "
|
||||
"If this takes longer than two minutes, please contact us or go back in your "
|
||||
"browser and try again."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:66
|
||||
#: pretix/static/pretixbase/js/asynctask.js:124
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:23
|
||||
msgid "An error of type {code} occurred."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:69
|
||||
msgid ""
|
||||
"We currently cannot reach the server, but we keep trying. Last error code: "
|
||||
"{code}"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:115
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:20
|
||||
msgid "The request took to long. Please try again."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:127
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:25
|
||||
msgid ""
|
||||
"We currently cannot reach the server. Please try again. Error code: {code}"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:148
|
||||
msgid "We are processing your request …"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:156
|
||||
msgid ""
|
||||
"We are currently sending your request to the server. If this takes longer "
|
||||
"than one minute, please check your internet connection and then reload this "
|
||||
"page and try again."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:193
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:20
|
||||
msgid "Close message"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/clipboard.js:23
|
||||
msgid "Copied!"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/clipboard.js:29
|
||||
msgid "Press Ctrl-C to copy!"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:43
|
||||
msgid "Lead Scan QR"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:45
|
||||
msgid "Check-in QR"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:249
|
||||
msgid "The PDF background file could not be loaded for the following reason:"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:418
|
||||
msgid "Group of objects"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:424
|
||||
msgid "Text object"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:426
|
||||
msgid "Barcode area"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:428
|
||||
msgid "Powered by pretix"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:430
|
||||
msgid "Object"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:434
|
||||
msgid "Ticket design"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:687
|
||||
msgid "Saving failed."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:735
|
||||
msgid "Do you really want to leave the editor without saving your changes?"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:749
|
||||
msgid "Error while uploading your PDF file, please try again."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:18
|
||||
msgid "An error has occurred."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:52
|
||||
msgid "Generating messages …"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:55
|
||||
msgid "Unknown error."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:217
|
||||
msgid "Your color has great contrast and is very easy to read!"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:221
|
||||
msgid "Your color has decent contrast and is probably good-enough to read!"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:225
|
||||
msgid ""
|
||||
"Your color has bad contrast for text on white background, please choose a "
|
||||
"darker shade."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:305
|
||||
msgid "All"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:306
|
||||
msgid "None"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:595
|
||||
msgid "Use a different name internally"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:652
|
||||
msgid "Click to close"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/orderchange.js:24
|
||||
msgid "Calculating default price…"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:42
|
||||
msgid "Others"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:71
|
||||
msgid "Count"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:120
|
||||
msgid "Yes"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:121
|
||||
msgid "No"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/subevent.js:108
|
||||
msgid "(one more date)"
|
||||
msgid_plural "({num} more dates)"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:39
|
||||
msgid "The items in your cart are no longer reserved for you."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:41
|
||||
msgid "Cart expired"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:46
|
||||
msgid "The items in your cart are reserved for you for one minute."
|
||||
msgid_plural "The items in your cart are reserved for you for {num} minutes."
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:201
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:15
|
||||
msgctxt "widget"
|
||||
msgid "Sold out"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:16
|
||||
msgctxt "widget"
|
||||
msgid "Buy"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:17
|
||||
msgctxt "widget"
|
||||
msgid "Reserved"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:18
|
||||
msgctxt "widget"
|
||||
msgid "FREE"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:19
|
||||
msgctxt "widget"
|
||||
msgid "from %(currency)s %(price)s"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:20
|
||||
msgctxt "widget"
|
||||
msgid "incl. %(rate)s% %(taxname)s"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:21
|
||||
msgctxt "widget"
|
||||
msgid "plus %(rate)s% %(taxname)s"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:22
|
||||
msgctxt "widget"
|
||||
msgid "incl. taxes"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:23
|
||||
msgctxt "widget"
|
||||
msgid "plus taxes"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:24
|
||||
#, javascript-format
|
||||
msgctxt "widget"
|
||||
msgid "currently available: %s"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:25
|
||||
msgctxt "widget"
|
||||
msgid "Only available with a voucher"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:26
|
||||
#, javascript-format
|
||||
msgctxt "widget"
|
||||
msgid "minimum amount to order: %s"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:27
|
||||
msgctxt "widget"
|
||||
msgid "Close ticket shop"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:28
|
||||
msgctxt "widget"
|
||||
msgid "The ticket shop could not be loaded."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:29
|
||||
msgctxt "widget"
|
||||
msgid "The cart could not be created. Please try again later"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:30
|
||||
msgctxt "widget"
|
||||
msgid "Waiting list"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:31
|
||||
msgctxt "widget"
|
||||
msgid ""
|
||||
"You currently have an active cart for this event. If you select more "
|
||||
"products, they will be added to your existing cart."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:33
|
||||
msgctxt "widget"
|
||||
msgid "Resume checkout"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:34
|
||||
msgctxt "widget"
|
||||
msgid ""
|
||||
"<a href=\"https://pretix.eu\" target=\"_blank\" rel=\"noopener\">event "
|
||||
"ticketing powered by pretix</a>"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:36
|
||||
msgctxt "widget"
|
||||
msgid "Redeem a voucher"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:37
|
||||
msgctxt "widget"
|
||||
msgid "Redeem"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:38
|
||||
msgctxt "widget"
|
||||
msgid "Voucher code"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:39
|
||||
msgctxt "widget"
|
||||
msgid "Close"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:40
|
||||
msgctxt "widget"
|
||||
msgid "Continue"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:41
|
||||
msgctxt "widget"
|
||||
msgid "See variations"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:42
|
||||
msgctxt "widget"
|
||||
msgid "Choose a different event"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:43
|
||||
msgctxt "widget"
|
||||
msgid "Choose a different date"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:44
|
||||
msgctxt "widget"
|
||||
msgid "Back"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:45
|
||||
msgctxt "widget"
|
||||
msgid "Next month"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:46
|
||||
msgctxt "widget"
|
||||
msgid "Previous month"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:48
|
||||
msgid "Mo"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:49
|
||||
msgid "Tu"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:50
|
||||
msgid "We"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:51
|
||||
msgid "Th"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:52
|
||||
msgid "Fr"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:53
|
||||
msgid "Sa"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:54
|
||||
msgid "Su"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:57
|
||||
msgid "January"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:58
|
||||
msgid "February"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:59
|
||||
msgid "March"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:60
|
||||
msgid "April"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:61
|
||||
msgid "May"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:62
|
||||
msgid "June"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:63
|
||||
msgid "July"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:64
|
||||
msgid "August"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:65
|
||||
msgid "September"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:66
|
||||
msgid "October"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:67
|
||||
msgid "November"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:68
|
||||
msgid "December"
|
||||
msgstr ""
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: 1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-05-01 12:02+0000\n"
|
||||
"POT-Creation-Date: 2019-05-09 12:45+0000\n"
|
||||
"PO-Revision-Date: 2019-04-27 21:00+0000\n"
|
||||
"Last-Translator: Maarten van den Berg <maartenberg1@gmail.com>\n"
|
||||
"Language-Team: Dutch <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-05-01 12:02+0000\n"
|
||||
"POT-Creation-Date: 2019-05-09 12:45+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: Automatically generated\n"
|
||||
"Language-Team: none\n"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-05-01 12:02+0000\n"
|
||||
"POT-Creation-Date: 2019-05-09 12:45+0000\n"
|
||||
"PO-Revision-Date: 2019-04-27 21:00+0000\n"
|
||||
"Last-Translator: Maarten van den Berg <maartenberg1@gmail.com>\n"
|
||||
"Language-Team: Dutch (informal) <https://translate.pretix.eu/projects/pretix/"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-05-01 12:02+0000\n"
|
||||
"POT-Creation-Date: 2019-05-09 12:45+0000\n"
|
||||
"PO-Revision-Date: 2019-03-15 11:19+0000\n"
|
||||
"Last-Translator: Serge Bazanski <q3k@hackerspace.pl>\n"
|
||||
"Language-Team: Polish <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-05-01 12:02+0000\n"
|
||||
"POT-Creation-Date: 2019-05-09 12:45+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: Automatically generated\n"
|
||||
"Language-Team: none\n"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-05-01 12:02+0000\n"
|
||||
"POT-Creation-Date: 2019-05-09 12:45+0000\n"
|
||||
"PO-Revision-Date: 2019-03-19 09:00+0000\n"
|
||||
"Last-Translator: Vitor Reis <vitor.reis7@gmail.com>\n"
|
||||
"Language-Team: Portuguese (Brazil) <https://translate.pretix.eu/projects/"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-05-01 12:02+0000\n"
|
||||
"POT-Creation-Date: 2019-05-09 12:45+0000\n"
|
||||
"PO-Revision-Date: 2019-01-02 08:21+0000\n"
|
||||
"Last-Translator: Alexey Zh <write2aracon@gmail.com>\n"
|
||||
"Language-Team: Russian <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
|
||||
15600
src/pretix/locale/sl/LC_MESSAGES/django.po
Normal file
15600
src/pretix/locale/sl/LC_MESSAGES/django.po
Normal file
File diff suppressed because it is too large
Load Diff
483
src/pretix/locale/sl/LC_MESSAGES/djangojs.po
Normal file
483
src/pretix/locale/sl/LC_MESSAGES/djangojs.po
Normal file
@@ -0,0 +1,483 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-05-09 12:45+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: Automatically generated\n"
|
||||
"Language-Team: none\n"
|
||||
"Language: sl\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n"
|
||||
"%100==4 ? 2 : 3;\n"
|
||||
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:68
|
||||
msgid "Marked as paid"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:76
|
||||
msgid "Comment:"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:15
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:39
|
||||
msgid "Placed orders"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:15
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:39
|
||||
msgid "Paid orders"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:27
|
||||
msgid "Total revenue"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/stripe/static/pretixplugins/stripe/pretix-stripe.js:12
|
||||
msgid "Contacting Stripe …"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/stripe/static/pretixplugins/stripe/pretix-stripe.js:56
|
||||
msgid "Total"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:39
|
||||
#: pretix/static/pretixbase/js/asynctask.js:95
|
||||
msgid ""
|
||||
"Your request has been queued on the server and will now be processed. "
|
||||
"Depending on the size of your event, this might take up to a few minutes."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:45
|
||||
#: pretix/static/pretixbase/js/asynctask.js:101
|
||||
msgid ""
|
||||
"Your request arrived on the server but we still wait for it to be processed. "
|
||||
"If this takes longer than two minutes, please contact us or go back in your "
|
||||
"browser and try again."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:66
|
||||
#: pretix/static/pretixbase/js/asynctask.js:124
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:23
|
||||
msgid "An error of type {code} occurred."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:69
|
||||
msgid ""
|
||||
"We currently cannot reach the server, but we keep trying. Last error code: "
|
||||
"{code}"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:115
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:20
|
||||
msgid "The request took to long. Please try again."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:127
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:25
|
||||
msgid ""
|
||||
"We currently cannot reach the server. Please try again. Error code: {code}"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:148
|
||||
msgid "We are processing your request …"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:156
|
||||
msgid ""
|
||||
"We are currently sending your request to the server. If this takes longer "
|
||||
"than one minute, please check your internet connection and then reload this "
|
||||
"page and try again."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:193
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:20
|
||||
msgid "Close message"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/clipboard.js:23
|
||||
msgid "Copied!"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/clipboard.js:29
|
||||
msgid "Press Ctrl-C to copy!"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:43
|
||||
msgid "Lead Scan QR"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:45
|
||||
msgid "Check-in QR"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:249
|
||||
msgid "The PDF background file could not be loaded for the following reason:"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:418
|
||||
msgid "Group of objects"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:424
|
||||
msgid "Text object"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:426
|
||||
msgid "Barcode area"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:428
|
||||
msgid "Powered by pretix"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:430
|
||||
msgid "Object"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:434
|
||||
msgid "Ticket design"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:687
|
||||
msgid "Saving failed."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:735
|
||||
msgid "Do you really want to leave the editor without saving your changes?"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:749
|
||||
msgid "Error while uploading your PDF file, please try again."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:18
|
||||
msgid "An error has occurred."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:52
|
||||
msgid "Generating messages …"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:55
|
||||
msgid "Unknown error."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:217
|
||||
msgid "Your color has great contrast and is very easy to read!"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:221
|
||||
msgid "Your color has decent contrast and is probably good-enough to read!"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:225
|
||||
msgid ""
|
||||
"Your color has bad contrast for text on white background, please choose a "
|
||||
"darker shade."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:305
|
||||
msgid "All"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:306
|
||||
msgid "None"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:595
|
||||
msgid "Use a different name internally"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:652
|
||||
msgid "Click to close"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/orderchange.js:24
|
||||
msgid "Calculating default price…"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:42
|
||||
msgid "Others"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:71
|
||||
msgid "Count"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:120
|
||||
msgid "Yes"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:121
|
||||
msgid "No"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/subevent.js:108
|
||||
msgid "(one more date)"
|
||||
msgid_plural "({num} more dates)"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
msgstr[2] ""
|
||||
msgstr[3] ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:39
|
||||
msgid "The items in your cart are no longer reserved for you."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:41
|
||||
msgid "Cart expired"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:46
|
||||
msgid "The items in your cart are reserved for you for one minute."
|
||||
msgid_plural "The items in your cart are reserved for you for {num} minutes."
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
msgstr[2] ""
|
||||
msgstr[3] ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:201
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:15
|
||||
msgctxt "widget"
|
||||
msgid "Sold out"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:16
|
||||
msgctxt "widget"
|
||||
msgid "Buy"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:17
|
||||
msgctxt "widget"
|
||||
msgid "Reserved"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:18
|
||||
msgctxt "widget"
|
||||
msgid "FREE"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:19
|
||||
msgctxt "widget"
|
||||
msgid "from %(currency)s %(price)s"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:20
|
||||
msgctxt "widget"
|
||||
msgid "incl. %(rate)s% %(taxname)s"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:21
|
||||
msgctxt "widget"
|
||||
msgid "plus %(rate)s% %(taxname)s"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:22
|
||||
msgctxt "widget"
|
||||
msgid "incl. taxes"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:23
|
||||
msgctxt "widget"
|
||||
msgid "plus taxes"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:24
|
||||
#, javascript-format
|
||||
msgctxt "widget"
|
||||
msgid "currently available: %s"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:25
|
||||
msgctxt "widget"
|
||||
msgid "Only available with a voucher"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:26
|
||||
#, javascript-format
|
||||
msgctxt "widget"
|
||||
msgid "minimum amount to order: %s"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:27
|
||||
msgctxt "widget"
|
||||
msgid "Close ticket shop"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:28
|
||||
msgctxt "widget"
|
||||
msgid "The ticket shop could not be loaded."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:29
|
||||
msgctxt "widget"
|
||||
msgid "The cart could not be created. Please try again later"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:30
|
||||
msgctxt "widget"
|
||||
msgid "Waiting list"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:31
|
||||
msgctxt "widget"
|
||||
msgid ""
|
||||
"You currently have an active cart for this event. If you select more "
|
||||
"products, they will be added to your existing cart."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:33
|
||||
msgctxt "widget"
|
||||
msgid "Resume checkout"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:34
|
||||
msgctxt "widget"
|
||||
msgid ""
|
||||
"<a href=\"https://pretix.eu\" target=\"_blank\" rel=\"noopener\">event "
|
||||
"ticketing powered by pretix</a>"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:36
|
||||
msgctxt "widget"
|
||||
msgid "Redeem a voucher"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:37
|
||||
msgctxt "widget"
|
||||
msgid "Redeem"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:38
|
||||
msgctxt "widget"
|
||||
msgid "Voucher code"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:39
|
||||
msgctxt "widget"
|
||||
msgid "Close"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:40
|
||||
msgctxt "widget"
|
||||
msgid "Continue"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:41
|
||||
msgctxt "widget"
|
||||
msgid "See variations"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:42
|
||||
msgctxt "widget"
|
||||
msgid "Choose a different event"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:43
|
||||
msgctxt "widget"
|
||||
msgid "Choose a different date"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:44
|
||||
msgctxt "widget"
|
||||
msgid "Back"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:45
|
||||
msgctxt "widget"
|
||||
msgid "Next month"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:46
|
||||
msgctxt "widget"
|
||||
msgid "Previous month"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:48
|
||||
msgid "Mo"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:49
|
||||
msgid "Tu"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:50
|
||||
msgid "We"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:51
|
||||
msgid "Th"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:52
|
||||
msgid "Fr"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:53
|
||||
msgid "Sa"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:54
|
||||
msgid "Su"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:57
|
||||
msgid "January"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:58
|
||||
msgid "February"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:59
|
||||
msgid "March"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:60
|
||||
msgid "April"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:61
|
||||
msgid "May"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:62
|
||||
msgid "June"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:63
|
||||
msgid "July"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:64
|
||||
msgid "August"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:65
|
||||
msgid "September"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:66
|
||||
msgid "October"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:67
|
||||
msgid "November"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:68
|
||||
msgid "December"
|
||||
msgstr ""
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-05-01 12:02+0000\n"
|
||||
"POT-Creation-Date: 2019-05-09 12:45+0000\n"
|
||||
"PO-Revision-Date: 2019-04-27 21:00+0000\n"
|
||||
"Last-Translator: Tobias Sundgren <syrgas@gmail.com>\n"
|
||||
"Language-Team: Swedish <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-05-01 12:02+0000\n"
|
||||
"POT-Creation-Date: 2019-05-09 12:45+0000\n"
|
||||
"PO-Revision-Date: 2018-09-03 06:36+0000\n"
|
||||
"Last-Translator: Yunus Fırat Pişkin <firat.piskin@idvlabs.com>\n"
|
||||
"Language-Team: Turkish <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-05-01 12:02+0000\n"
|
||||
"POT-Creation-Date: 2019-05-09 12:45+0000\n"
|
||||
"PO-Revision-Date: 2019-03-28 14:00+0000\n"
|
||||
"Last-Translator: yichengsd <sunzl@jxepub.com>\n"
|
||||
"Language-Team: Chinese (Simplified) <https://translate.pretix.eu/projects/"
|
||||
|
||||
@@ -196,7 +196,7 @@ class PDFCheckinList(ReportlabExportMixin, CheckInListMixin, BaseExporter):
|
||||
|
||||
story = [
|
||||
Paragraph(
|
||||
'{} – {}'.format(cl.name, (cl.subevent or self.event).get_date_from_display()),
|
||||
cl.name,
|
||||
headlinestyle
|
||||
),
|
||||
Spacer(1, 5 * mm)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import hashlib
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.files.base import ContentFile
|
||||
from django.core.files.base import ContentFile, File
|
||||
from django.core.files.storage import default_storage
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
@@ -35,4 +35,7 @@ class Command(BaseCommand):
|
||||
gs.settings.set('widget_file_{}'.format(lc), 'file://' + newname)
|
||||
gs.settings.set('widget_checksum_{}'.format(lc), checksum)
|
||||
if fname:
|
||||
default_storage.delete(fname)
|
||||
if isinstance(fname, File):
|
||||
default_storage.delete(fname.name)
|
||||
else:
|
||||
default_storage.delete(fname)
|
||||
|
||||
@@ -82,7 +82,8 @@ As with all plugin signals, the ``sender`` keyword argument will contain the eve
|
||||
|
||||
checkout_flow_steps = EventPluginSignal()
|
||||
"""
|
||||
This signal is sent out to retrieve pages for the checkout flow
|
||||
This signal is sent out to retrieve pages for the checkout flow. Receivers are expected to return
|
||||
a subclass of ``pretix.presale.checkoutflow.BaseCheckoutFlowStep``.
|
||||
|
||||
As with all plugin signals, the ``sender`` keyword argument will contain the event.
|
||||
"""
|
||||
|
||||
@@ -271,10 +271,11 @@
|
||||
</div>
|
||||
<div class="col-md-2 col-xs-6 price">
|
||||
{% if var.original_price %}
|
||||
<del>{{ var.original_price|money:event.currency }}</del>
|
||||
<ins>
|
||||
{% elif item.original_price %}
|
||||
<del>{{ item.original_price|money:event.currency }}</del>
|
||||
{% if event.settings.display_net_prices %}
|
||||
<del>{{ var.original_price.net|money:event.currency }}</del>
|
||||
{% else %}
|
||||
<del>{{ var.original_price.gross|money:event.currency }}</del>
|
||||
{% endif %}
|
||||
<ins>
|
||||
{% endif %}
|
||||
{% if item.free_price %}
|
||||
@@ -386,7 +387,11 @@
|
||||
</div>
|
||||
<div class="col-md-2 col-xs-6 price">
|
||||
{% if item.original_price %}
|
||||
<del>{{ item.original_price|money:event.currency }}</del>
|
||||
{% if event.settings.display_net_prices %}
|
||||
<del>{{ item.original_price.net|money:event.currency }}</del>
|
||||
{% else %}
|
||||
<del>{{ item.original_price.gross|money:event.currency }}</del>
|
||||
{% endif %}
|
||||
<ins>
|
||||
{% endif %}
|
||||
{% if item.free_price %}
|
||||
|
||||
@@ -94,10 +94,11 @@
|
||||
</div>
|
||||
<div class="col-md-2 col-xs-6 price">
|
||||
{% if var.original_price %}
|
||||
<del>{{ var.original_price|money:event.currency }}</del>
|
||||
<ins>
|
||||
{% elif item.original_price %}
|
||||
<del>{{ item.original_price|money:event.currency }}</del>
|
||||
{% if event.settings.display_net_prices %}
|
||||
<del>{{ var.original_price.net|money:event.currency }}</del>
|
||||
{% else %}
|
||||
<del>{{ var.original_price.gross|money:event.currency }}</del>
|
||||
{% endif %}
|
||||
<ins>
|
||||
{% endif %}
|
||||
{% if item.free_price %}
|
||||
@@ -200,7 +201,11 @@
|
||||
</div>
|
||||
<div class="col-md-2 col-xs-6 price">
|
||||
{% if item.original_price %}
|
||||
<del>{{ item.original_price|money:event.currency }}</del>
|
||||
{% if event.settings.display_net_prices %}
|
||||
<del>{{ item.original_price.net|money:event.currency }}</del>
|
||||
{% else %}
|
||||
<del>{{ item.original_price.gross|money:event.currency }}</del>
|
||||
{% endif %}
|
||||
<ins>
|
||||
{% endif %}
|
||||
{% if item.free_price %}
|
||||
|
||||
@@ -21,6 +21,10 @@ def _detect_event(request, require_live=True, require_plugin=None):
|
||||
if hasattr(request, '_event_detected'):
|
||||
return
|
||||
|
||||
db = 'default'
|
||||
if request.method == 'GET':
|
||||
db = settings.DATABASE_REPLICA
|
||||
|
||||
url = resolve(request.path_info)
|
||||
try:
|
||||
if hasattr(request, 'organizer_domain'):
|
||||
@@ -31,24 +35,24 @@ def _detect_event(request, require_live=True, require_plugin=None):
|
||||
path = "/" + request.get_full_path().split("/", 2)[-1]
|
||||
return redirect(path)
|
||||
|
||||
request.event = request.organizer.events\
|
||||
.get(
|
||||
slug=url.kwargs['event'],
|
||||
organizer=request.organizer,
|
||||
)
|
||||
request.event = request.organizer.events.using(db).get(
|
||||
slug=url.kwargs['event'],
|
||||
organizer=request.organizer,
|
||||
)
|
||||
request.organizer = request.organizer
|
||||
else:
|
||||
# We are on our main domain
|
||||
if 'event' in url.kwargs and 'organizer' in url.kwargs:
|
||||
request.event = Event.objects\
|
||||
.select_related('organizer')\
|
||||
.using(db)\
|
||||
.get(
|
||||
slug=url.kwargs['event'],
|
||||
organizer__slug=url.kwargs['organizer']
|
||||
)
|
||||
request.organizer = request.event.organizer
|
||||
elif 'organizer' in url.kwargs:
|
||||
request.organizer = Organizer.objects.get(
|
||||
request.organizer = Organizer.objects.using(db).get(
|
||||
slug=url.kwargs['organizer']
|
||||
)
|
||||
else:
|
||||
|
||||
@@ -50,32 +50,34 @@ def item_group_by_category(items):
|
||||
|
||||
|
||||
def get_grouped_items(event, subevent=None, voucher=None, channel='web'):
|
||||
items = event.items.filter_available(channel=channel, voucher=voucher).select_related(
|
||||
items = event.items.using(settings.DATABASE_REPLICA).filter_available(channel=channel, voucher=voucher).select_related(
|
||||
'category', 'tax_rule', # for re-grouping
|
||||
).prefetch_related(
|
||||
Prefetch('quotas',
|
||||
to_attr='_subevent_quotas',
|
||||
queryset=event.quotas.filter(subevent=subevent)),
|
||||
queryset=event.quotas.using(settings.DATABASE_REPLICA).filter(subevent=subevent)),
|
||||
Prefetch('bundles',
|
||||
queryset=ItemBundle.objects.prefetch_related(
|
||||
queryset=ItemBundle.objects.using(settings.DATABASE_REPLICA).prefetch_related(
|
||||
Prefetch('bundled_item',
|
||||
queryset=event.items.select_related('tax_rule').prefetch_related(
|
||||
queryset=event.items.using(settings.DATABASE_REPLICA).select_related('tax_rule').prefetch_related(
|
||||
Prefetch('quotas',
|
||||
to_attr='_subevent_quotas',
|
||||
queryset=event.quotas.filter(subevent=subevent)),
|
||||
queryset=event.quotas.using(settings.DATABASE_REPLICA).filter(subevent=subevent)),
|
||||
)),
|
||||
Prefetch('bundled_variation',
|
||||
queryset=ItemVariation.objects.select_related('item', 'item__tax_rule').filter(item__event=event).prefetch_related(
|
||||
queryset=ItemVariation.objects.using(
|
||||
settings.DATABASE_REPLICA
|
||||
).select_related('item', 'item__tax_rule').filter(item__event=event).prefetch_related(
|
||||
Prefetch('quotas',
|
||||
to_attr='_subevent_quotas',
|
||||
queryset=event.quotas.filter(subevent=subevent)),
|
||||
queryset=event.quotas.using(settings.DATABASE_REPLICA).filter(subevent=subevent)),
|
||||
)),
|
||||
)),
|
||||
Prefetch('variations', to_attr='available_variations',
|
||||
queryset=ItemVariation.objects.filter(active=True, quotas__isnull=False).prefetch_related(
|
||||
queryset=ItemVariation.objects.using(settings.DATABASE_REPLICA).filter(active=True, quotas__isnull=False).prefetch_related(
|
||||
Prefetch('quotas',
|
||||
to_attr='_subevent_quotas',
|
||||
queryset=event.quotas.filter(subevent=subevent))
|
||||
queryset=event.quotas.using(settings.DATABASE_REPLICA).filter(subevent=subevent))
|
||||
).distinct()),
|
||||
).annotate(
|
||||
quotac=Count('quotas'),
|
||||
@@ -134,7 +136,13 @@ def get_grouped_items(event, subevent=None, voucher=None, channel='web'):
|
||||
item.display_price = item.tax(price, currency=event.currency, include_bundled=True)
|
||||
|
||||
if price != original_price:
|
||||
item.original_price = original_price
|
||||
item.original_price = item.tax(original_price, currency=event.currency, include_bundled=True)
|
||||
else:
|
||||
item.original_price = (
|
||||
item.tax(item.original_price, currency=event.currency, include_bundled=True,
|
||||
base_price_is='net' if event.settings.display_net_prices else 'gross') # backwards-compat
|
||||
if item.original_price else None
|
||||
)
|
||||
|
||||
display_add_to_cart = display_add_to_cart or item.order_max > 0
|
||||
else:
|
||||
@@ -163,10 +171,22 @@ def get_grouped_items(event, subevent=None, voucher=None, channel='web'):
|
||||
var.display_price = var.tax(price, currency=event.currency, include_bundled=True)
|
||||
|
||||
if price != original_price:
|
||||
var.original_price = original_price
|
||||
var.original_price = var.tax(original_price, currency=event.currency, include_bundled=True)
|
||||
else:
|
||||
var.original_price = (
|
||||
var.tax(var.original_price or item.original_price, currency=event.currency,
|
||||
include_bundled=True,
|
||||
base_price_is='net' if event.settings.display_net_prices else 'gross') # backwards-compat
|
||||
) if var.original_price or item.original_price else None
|
||||
|
||||
display_add_to_cart = display_add_to_cart or var.order_max > 0
|
||||
|
||||
item.original_price = (
|
||||
item.tax(item.original_price, currency=event.currency, include_bundled=True,
|
||||
base_price_is='net' if event.settings.display_net_prices else 'gross') # backwards-compat
|
||||
if item.original_price else None
|
||||
)
|
||||
|
||||
item.available_variations = [
|
||||
v for v in item.available_variations if v._subevent_quotas and (
|
||||
not voucher or not voucher.quota_id or v in restrict_vars
|
||||
@@ -229,7 +249,7 @@ class EventIndex(EventViewMixin, EventListMixin, CartMixin, TemplateView):
|
||||
|
||||
if request.event.has_subevents:
|
||||
if 'subevent' in kwargs:
|
||||
self.subevent = request.event.subevents.filter(pk=kwargs['subevent'], active=True).first()
|
||||
self.subevent = request.event.subevents.using(settings.DATABASE_REPLICA).filter(pk=kwargs['subevent'], active=True).first()
|
||||
if not self.subevent:
|
||||
raise Http404()
|
||||
return super().get(request, *args, **kwargs)
|
||||
@@ -287,7 +307,7 @@ class EventIndex(EventViewMixin, EventListMixin, CartMixin, TemplateView):
|
||||
|
||||
ebd = defaultdict(list)
|
||||
add_subevents_for_days(
|
||||
filter_qs_by_attr(self.request.event.subevents_annotated(self.request.sales_channel), self.request),
|
||||
filter_qs_by_attr(self.request.event.subevents_annotated(self.request.sales_channel).using(settings.DATABASE_REPLICA), self.request),
|
||||
before, after, ebd, set(), self.request.event,
|
||||
kwargs.get('cart_namespace')
|
||||
)
|
||||
@@ -297,7 +317,7 @@ class EventIndex(EventViewMixin, EventListMixin, CartMixin, TemplateView):
|
||||
context['years'] = range(now().year - 2, now().year + 3)
|
||||
else:
|
||||
context['subevent_list'] = self.request.event.subevents_sorted(
|
||||
filter_qs_by_attr(self.request.event.subevents_annotated(self.request.sales_channel), self.request)
|
||||
filter_qs_by_attr(self.request.event.subevents_annotated(self.request.sales_channel).using(settings.DATABASE_REPLICA), self.request)
|
||||
)
|
||||
|
||||
context['show_cart'] = (
|
||||
|
||||
@@ -93,7 +93,7 @@ class EventListMixin:
|
||||
|
||||
def _get_event_queryset(self):
|
||||
query = Q(is_public=True) & Q(live=True)
|
||||
qs = self.request.organizer.events.filter(query)
|
||||
qs = self.request.organizer.events.using(settings.DATABASE_REPLICA).filter(query)
|
||||
qs = qs.annotate(
|
||||
min_from=Min('subevents__date_from'),
|
||||
min_to=Min('subevents__date_to'),
|
||||
@@ -126,7 +126,7 @@ class EventListMixin:
|
||||
|
||||
def _set_month_to_next_subevent(self):
|
||||
tz = pytz.timezone(self.request.event.settings.timezone)
|
||||
next_sev = self.request.event.subevents.filter(
|
||||
next_sev = self.request.event.subevents.using(settings.DATABASE_REPLICA).filter(
|
||||
active=True,
|
||||
is_public=True,
|
||||
date_from__gte=now()
|
||||
@@ -141,14 +141,14 @@ class EventListMixin:
|
||||
self.month = now().month
|
||||
|
||||
def _set_month_to_next_event(self):
|
||||
next_ev = filter_qs_by_attr(Event.objects.filter(
|
||||
next_ev = filter_qs_by_attr(Event.objects.using(settings.DATABASE_REPLICA).filter(
|
||||
organizer=self.request.organizer,
|
||||
live=True,
|
||||
is_public=True,
|
||||
date_from__gte=now(),
|
||||
has_subevents=False
|
||||
), self.request).order_by('date_from').first()
|
||||
next_sev = filter_qs_by_attr(SubEvent.objects.filter(
|
||||
next_sev = filter_qs_by_attr(SubEvent.objects.using(settings.DATABASE_REPLICA).filter(
|
||||
event__organizer=self.request.organizer,
|
||||
event__is_public=True,
|
||||
event__live=True,
|
||||
@@ -353,14 +353,14 @@ class CalendarView(OrganizerViewMixin, EventListMixin, TemplateView):
|
||||
def _events_by_day(self, before, after):
|
||||
ebd = defaultdict(list)
|
||||
timezones = set()
|
||||
add_events_for_days(self.request, Event.annotated(self.request.organizer.events, 'web'), before, after, ebd, timezones)
|
||||
add_events_for_days(self.request, Event.annotated(self.request.organizer.events, 'web').using(settings.DATABASE_REPLICA), before, after, ebd, timezones)
|
||||
add_subevents_for_days(filter_qs_by_attr(SubEvent.annotated(SubEvent.objects.filter(
|
||||
event__organizer=self.request.organizer,
|
||||
event__is_public=True,
|
||||
event__live=True,
|
||||
).prefetch_related(
|
||||
'event___settings_objects', 'event__organizer___settings_objects'
|
||||
)), self.request), before, after, ebd, timezones)
|
||||
)), self.request).using(settings.DATABASE_REPLICA), before, after, ebd, timezones)
|
||||
self._multiple_timezones = len(timezones) > 1
|
||||
return ebd
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ class QuestionsViewMixin(BaseQuestionsViewMixin):
|
||||
def _positions_for_questions(self):
|
||||
qqs = self.request.event.questions.all()
|
||||
if self.only_user_visible:
|
||||
qqs = qqs.filter(ask_during_checkin=False)
|
||||
qqs = qqs.filter(ask_during_checkin=False, hidden=False)
|
||||
cart = get_cart(self.request).select_related(
|
||||
'addon_to'
|
||||
).prefetch_related(
|
||||
|
||||
@@ -200,7 +200,12 @@ class WidgetAPIProductList(EventListMixin, View):
|
||||
item.cached_availability[0],
|
||||
item.cached_availability[1] if self.request.event.settings.show_quota_left else None
|
||||
] if not item.has_variations else None,
|
||||
'original_price': item.original_price,
|
||||
'original_price': (
|
||||
(item.original_price.net
|
||||
if self.request.event.settings.display_net_prices
|
||||
else item.original_price.gross)
|
||||
if item.original_price else None
|
||||
),
|
||||
'variations': [
|
||||
{
|
||||
'id': var.id,
|
||||
@@ -208,7 +213,19 @@ class WidgetAPIProductList(EventListMixin, View):
|
||||
'order_max': var.order_max,
|
||||
'description': str(rich_text(var.description, safelinks=False)) if var.description else None,
|
||||
'price': price_dict(item, var.display_price),
|
||||
'original_price': getattr(var, 'original_price') or item.original_price,
|
||||
'original_price': (
|
||||
(
|
||||
var.original_price.net
|
||||
if self.request.event.settings.display_net_prices
|
||||
else var.original_price.gross
|
||||
) if var.original_price else None
|
||||
) or (
|
||||
(
|
||||
item.original_price.net
|
||||
if self.request.event.settings.display_net_prices
|
||||
else item.original_price.gross
|
||||
) if item.original_price else None
|
||||
),
|
||||
'avail': [
|
||||
var.cached_availability[0],
|
||||
var.cached_availability[1] if self.request.event.settings.show_quota_left else None
|
||||
@@ -434,6 +451,7 @@ class WidgetAPIProductList(EventListMixin, View):
|
||||
'display_net_prices': request.event.settings.display_net_prices,
|
||||
'show_variations_expanded': request.event.settings.show_variations_expanded,
|
||||
'waiting_list_enabled': request.event.settings.waiting_list_enabled,
|
||||
'voucher_explanation_text': str(request.event.settings.voucher_explanation_text),
|
||||
'error': None,
|
||||
'cart_exists': False
|
||||
}
|
||||
|
||||
@@ -109,6 +109,7 @@ if config.has_section('replica'):
|
||||
'COLLATION': 'utf8mb4_unicode_ci',
|
||||
} if 'mysql' in db_backend else {}
|
||||
}
|
||||
DATABASE_ROUTERS = ['pretix.helpers.database.ReplicaRouter']
|
||||
|
||||
STATIC_URL = config.get('urls', 'static', fallback='/static/')
|
||||
|
||||
|
||||
@@ -66,6 +66,7 @@ pre[lang=ck], input[lang=ck], textarea[lang=ck], div[lang=ck] { background-image
|
||||
pre[lang=cl], input[lang=cl], textarea[lang=cl], div[lang=cl] { background-image: url(static('pretixbase/img/flags/cl.png')); }
|
||||
pre[lang=cm], input[lang=cm], textarea[lang=cm], div[lang=cm] { background-image: url(static('pretixbase/img/flags/cm.png')); }
|
||||
pre[lang=cn], input[lang=cn], textarea[lang=cn], div[lang=cn] { background-image: url(static('pretixbase/img/flags/cn.png')); }
|
||||
pre[lang=zh-hans], input[lang=zh-hans], textarea[lang=zh-hans], div[lang=zh-hans] { background-image: url(static('pretixbase/img/flags/cn.png')); }
|
||||
pre[lang=co], input[lang=co], textarea[lang=co], div[lang=co] { background-image: url(static('pretixbase/img/flags/co.png')); }
|
||||
pre[lang=cr], input[lang=cr], textarea[lang=cr], div[lang=cr] { background-image: url(static('pretixbase/img/flags/cr.png')); }
|
||||
pre[lang=cs], input[lang=cs], textarea[lang=cs], div[lang=cs] { background-image: url(static('pretixbase/img/flags/cs.png')); }
|
||||
|
||||
@@ -234,7 +234,7 @@ Vue.component('pricebox', {
|
||||
+ '<div v-if="free_price">'
|
||||
+ '{{ $root.currency }} '
|
||||
+ '<input type="number" class="pretix-widget-pricebox-price-input" placeholder="0" '
|
||||
+ ' :min="display_price" :value="display_price" :name="field_name"'
|
||||
+ ' :min="display_price_nonlocalized" :value="display_price_nonlocalized" :name="field_name"'
|
||||
+ ' step="any">'
|
||||
+ '</div>'
|
||||
+ '<small class="pretix-widget-pricebox-tax" v-if="price.rate != \'0.00\' && price.gross != \'0.00\'">'
|
||||
@@ -255,6 +255,13 @@ Vue.component('pricebox', {
|
||||
return floatformat(parseFloat(this.price.gross), 2);
|
||||
}
|
||||
},
|
||||
display_price_nonlocalized: function () {
|
||||
if (this.$root.display_net_prices) {
|
||||
return parseFloat(this.price.net).toFixed(2);
|
||||
} else {
|
||||
return parseFloat(this.price.gross).toFixed(2);
|
||||
}
|
||||
},
|
||||
original_line: function () {
|
||||
return this.$root.currency + " " + floatformat(parseFloat(this.original_price), 2);
|
||||
},
|
||||
@@ -680,6 +687,7 @@ Vue.component('pretix-widget-event-form', {
|
||||
+ ' v-if="$root.vouchers_exist && !$root.disable_vouchers && !$root.voucher_code">'
|
||||
+ '<div class="pretix-widget-voucher">'
|
||||
+ '<h3 class="pretix-widget-voucher-headline">'+ strings['redeem_voucher'] +'</h3>'
|
||||
+ '<div v-if="$root.voucher_explanation_text" class="pretix-widget-voucher-text">{{ $root.voucher_explanation_text }}</div>'
|
||||
+ '<div class="pretix-widget-voucher-input-wrap">'
|
||||
+ '<input class="pretix-widget-voucher-input" type="text" v-model="$parent.voucher" name="voucher" placeholder="'+strings.voucher_code+'">'
|
||||
+ '</div>'
|
||||
@@ -1045,6 +1053,7 @@ var shared_root_methods = {
|
||||
root.categories = data.items_by_category;
|
||||
root.currency = data.currency;
|
||||
root.display_net_prices = data.display_net_prices;
|
||||
root.voucher_explanation_text = data.voucher_explanation_text;
|
||||
root.error = data.error;
|
||||
root.display_add_to_cart = data.display_add_to_cart;
|
||||
root.waiting_list_enabled = data.waiting_list_enabled;
|
||||
@@ -1200,6 +1209,7 @@ var create_widget = function (element) {
|
||||
name: null,
|
||||
voucher_code: voucher,
|
||||
display_net_prices: false,
|
||||
voucher_explanation_text: null,
|
||||
show_variations_expanded: false,
|
||||
skip_ssl: skip_ssl,
|
||||
style: style,
|
||||
|
||||
@@ -273,6 +273,10 @@
|
||||
.pretix-widget-voucher-headline {
|
||||
margin: 10px 0 0 0;
|
||||
}
|
||||
.pretix-widget-voucher-text {
|
||||
margin: 10px 0;
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
.pretix-widget-voucher-input-wrap {
|
||||
padding: 0 15px;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
Django==2.2.*
|
||||
djangorestframework==3.9.*
|
||||
python-dateutil==2.8.*
|
||||
requests==2.21.0
|
||||
pytz
|
||||
django-bootstrap3==11.0.*
|
||||
django-formset-js-improved==0.5.0.2
|
||||
@@ -53,7 +54,6 @@ pyuca # for better sorting of country names in django-countries
|
||||
defusedcsv>=1.0.1
|
||||
vat_moss==0.11.0
|
||||
django-localflavor
|
||||
idna==2.6 # required by cureent requests
|
||||
urllib3==1.24.2 # required by cureent requests
|
||||
urllib3==1.24.*
|
||||
django-redis==4.10.*
|
||||
redis==3.2.*
|
||||
|
||||
@@ -90,6 +90,7 @@ setup(
|
||||
'Django==2.2.*',
|
||||
'djangorestframework==3.9.*',
|
||||
'python-dateutil==2.8.*',
|
||||
'requests==2.21.*',
|
||||
'pytz',
|
||||
'django-bootstrap3==11.0.*',
|
||||
'django-formset-js-improved==0.5.0.2',
|
||||
@@ -140,8 +141,7 @@ setup(
|
||||
'openpyxl',
|
||||
'django-oauth-toolkit==1.2.*',
|
||||
'oauthlib==2.1.*',
|
||||
'idna==2.6', # required by current requests
|
||||
'urllib3==1.24.2', # required by current requests
|
||||
'urllib3==1.24.*', # required by current requests
|
||||
],
|
||||
extras_require={
|
||||
'dev': [
|
||||
|
||||
@@ -1591,6 +1591,7 @@ TEST_QUESTION_RES = {
|
||||
"required": False,
|
||||
"items": [],
|
||||
"ask_during_checkin": False,
|
||||
"hidden": False,
|
||||
"identifier": "ABC",
|
||||
"position": 0,
|
||||
"dependency_question": None,
|
||||
|
||||
@@ -2206,6 +2206,21 @@ class CheckoutBundleTest(BaseCheckoutTestCase, TestCase):
|
||||
assert a.tax_rate == Decimal('7.00')
|
||||
assert a.tax_value == Decimal('0.10')
|
||||
|
||||
def test_simple_bundle_with_voucher(self):
|
||||
v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event, price_mode='none',
|
||||
valid_until=now() + timedelta(days=2))
|
||||
self.cp1.voucher = v
|
||||
self.cp1.save()
|
||||
oid = _perform_order(self.event.pk, 'manual', [self.cp1.pk, self.bundled1.pk], 'admin@example.org', 'en', None, {}, 'web')
|
||||
o = Order.objects.get(pk=oid)
|
||||
cp = o.positions.get(addon_to__isnull=True)
|
||||
assert cp.item == self.ticket
|
||||
assert cp.price == 23 - 1.5
|
||||
assert cp.addons.count() == 1
|
||||
a = cp.addons.get()
|
||||
assert a.item == self.trans
|
||||
assert a.price == 1.5
|
||||
|
||||
def test_expired_keep_price(self):
|
||||
self.cp1.expires = now() - timedelta(minutes=10)
|
||||
self.cp1.save()
|
||||
|
||||
@@ -197,7 +197,8 @@ class WidgetCartTest(CartTestMixin, TestCase):
|
||||
],
|
||||
"itemnum": 2,
|
||||
"display_add_to_cart": True,
|
||||
"cart_exists": False
|
||||
"cart_exists": False,
|
||||
"voucher_explanation_text": "",
|
||||
}
|
||||
|
||||
def test_product_list_view_with_voucher(self):
|
||||
@@ -240,6 +241,7 @@ class WidgetCartTest(CartTestMixin, TestCase):
|
||||
}
|
||||
],
|
||||
"itemnum": 1,
|
||||
"voucher_explanation_text": "",
|
||||
"display_add_to_cart": True,
|
||||
"cart_exists": False
|
||||
}
|
||||
@@ -303,6 +305,7 @@ class WidgetCartTest(CartTestMixin, TestCase):
|
||||
],
|
||||
"itemnum": 1,
|
||||
"display_add_to_cart": True,
|
||||
"voucher_explanation_text": "",
|
||||
"cart_exists": False
|
||||
}
|
||||
|
||||
@@ -322,6 +325,7 @@ class WidgetCartTest(CartTestMixin, TestCase):
|
||||
"items_by_category": [],
|
||||
"display_add_to_cart": False,
|
||||
"cart_exists": False,
|
||||
"voucher_explanation_text": "",
|
||||
"itemnum": 0,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user