forked from CGM_Public/pretix_original
Compare commits
95 Commits
v3.1.0
...
release/3.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b704d21e88 | ||
|
|
4bfe0e3784 | ||
|
|
d4d046ca60 | ||
|
|
fb3fc05522 | ||
|
|
fdcd750487 | ||
|
|
92754136a6 | ||
|
|
3b4d39ec27 | ||
|
|
88bb3b483c | ||
|
|
247370839b | ||
|
|
b0510f47b3 | ||
|
|
4fa086bbc5 | ||
|
|
cb03e7c843 | ||
|
|
d012d0804a | ||
|
|
3731d5f431 | ||
|
|
9f04d53564 | ||
|
|
748a389acb | ||
|
|
05a1df244b | ||
|
|
9f7d5156cc | ||
|
|
143fe6c1a6 | ||
|
|
1e3ccc4449 | ||
|
|
d09000b716 | ||
|
|
fc4572767e | ||
|
|
eeedd9a9c2 | ||
|
|
1d0c148170 | ||
|
|
cb37e7435d | ||
|
|
749ddbf21c | ||
|
|
9f6634025f | ||
|
|
ad039ae08e | ||
|
|
eeaaca574d | ||
|
|
bef15ec442 | ||
|
|
447a8b0a8c | ||
|
|
05b4d954d9 | ||
|
|
515d8c4899 | ||
|
|
82497cfb89 | ||
|
|
4ade9d39cd | ||
|
|
4360d5652b | ||
|
|
9fca3188b2 | ||
|
|
0ed48fac7f | ||
|
|
27a32173e6 | ||
|
|
81b6188777 | ||
|
|
9e85d3c94c | ||
|
|
4e58ba7594 | ||
|
|
248493dbf2 | ||
|
|
f1bd240096 | ||
|
|
0b673dc68c | ||
|
|
b2a7fe13da | ||
|
|
d28f735fca | ||
|
|
7a3b450bc6 | ||
|
|
f1ec129c0a | ||
|
|
ce6e46dfd2 | ||
|
|
f296f262e6 | ||
|
|
7f8d290ae1 | ||
|
|
ca0c0f4ae3 | ||
|
|
ac1e69f2d8 | ||
|
|
6338cc69ae | ||
|
|
39eaf3ad6a | ||
|
|
76e75bef65 | ||
|
|
a39822aedc | ||
|
|
73d5a2cec0 | ||
|
|
9f668e5fd6 | ||
|
|
1b92a891d7 | ||
|
|
827925e3c9 | ||
|
|
c0be574974 | ||
|
|
738413e8fd | ||
|
|
2c6125adeb | ||
|
|
3f7807d242 | ||
|
|
59b7f0a03d | ||
|
|
b7dea16db3 | ||
|
|
3a81706aeb | ||
|
|
ad3369b059 | ||
|
|
ab0709558d | ||
|
|
e2e64ac01d | ||
|
|
cf14dcf889 | ||
|
|
a884a25d2b | ||
|
|
8493778028 | ||
|
|
36a276c360 | ||
|
|
795c423d73 | ||
|
|
3ce9ec79f0 | ||
|
|
debc5e255b | ||
|
|
145aa0d7fd | ||
|
|
3997bdd098 | ||
|
|
a51a905512 | ||
|
|
d3ac9e8880 | ||
|
|
07402c9ea0 | ||
|
|
5f81bf55ca | ||
|
|
0120a5a930 | ||
|
|
ba555f956e | ||
|
|
5e3876ddde | ||
|
|
f5719687aa | ||
|
|
18449efcc7 | ||
|
|
3bb20d943a | ||
|
|
586e544fce | ||
|
|
3a4fc69db1 | ||
|
|
8b5d49d82f | ||
|
|
7665faa39f |
@@ -57,6 +57,8 @@ COPY deployment/docker/nginx.conf /etc/nginx/nginx.conf
|
||||
COPY deployment/docker/production_settings.py /pretix/src/production_settings.py
|
||||
COPY src /pretix/src
|
||||
|
||||
RUN cd /pretix/src && pip3 install .
|
||||
|
||||
RUN chmod +x /usr/local/bin/pretix && \
|
||||
rm /etc/nginx/sites-enabled/default && \
|
||||
cd /pretix/src && \
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
.. spelling:: checkin
|
||||
|
||||
Check-in lists
|
||||
==============
|
||||
|
||||
@@ -27,6 +29,7 @@ subevent integer ID of the date
|
||||
position_count integer Number of tickets that match this list (read-only).
|
||||
checkin_count integer Number of check-ins performed on this list (read-only).
|
||||
include_pending boolean If ``true``, the check-in list also contains tickets from orders in pending state.
|
||||
auto_checkin_sales_channels list of strings All items on the check-in list will be automatically marked as checked-in when purchased through any of the listed sales channels.
|
||||
===================================== ========================== =======================================================
|
||||
|
||||
.. versionchanged:: 1.10
|
||||
@@ -41,6 +44,10 @@ include_pending boolean If ``true``, th
|
||||
|
||||
The ``include_pending`` field has been added.
|
||||
|
||||
.. versionchanged:: 3.2
|
||||
|
||||
The ``auto_checkin_sales_channels`` field has been added.
|
||||
|
||||
Endpoints
|
||||
---------
|
||||
|
||||
@@ -81,7 +88,10 @@ Endpoints
|
||||
"all_products": true,
|
||||
"limit_products": [],
|
||||
"include_pending": false,
|
||||
"subevent": null
|
||||
"subevent": null,
|
||||
"auto_checkin_sales_channels": [
|
||||
"pretixpos"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -122,7 +132,10 @@ Endpoints
|
||||
"all_products": true,
|
||||
"limit_products": [],
|
||||
"include_pending": false,
|
||||
"subevent": null
|
||||
"subevent": null,
|
||||
"auto_checkin_sales_channels": [
|
||||
"pretixpos"
|
||||
]
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to fetch
|
||||
@@ -215,7 +228,10 @@ Endpoints
|
||||
"name": "VIP entry",
|
||||
"all_products": false,
|
||||
"limit_products": [1, 2],
|
||||
"subevent": null
|
||||
"subevent": null,
|
||||
"auto_checkin_sales_channels": [
|
||||
"pretixpos"
|
||||
]
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
@@ -234,7 +250,10 @@ Endpoints
|
||||
"all_products": false,
|
||||
"limit_products": [1, 2],
|
||||
"include_pending": false,
|
||||
"subevent": null
|
||||
"subevent": null,
|
||||
"auto_checkin_sales_channels": [
|
||||
"pretixpos"
|
||||
]
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer of the event/item to create a list for
|
||||
@@ -283,7 +302,10 @@ Endpoints
|
||||
"all_products": false,
|
||||
"limit_products": [1, 2],
|
||||
"include_pending": false,
|
||||
"subevent": null
|
||||
"subevent": null,
|
||||
"auto_checkin_sales_channels": [
|
||||
"pretixpos"
|
||||
]
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to modify
|
||||
@@ -342,6 +364,11 @@ Order position endpoints
|
||||
``ignore_status`` filter. The ``attendee_name`` field is now "smart" (see below) and the redemption endpoint
|
||||
returns ``400`` instead of ``404`` on tickets which are known but not paid.
|
||||
|
||||
.. versionchanged:: 3.2
|
||||
|
||||
The ``checkins`` dict now also contains a ``auto_checked_in`` value to indicate if the check-in has been performed
|
||||
automatically by the system.
|
||||
|
||||
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/checkinlists/(list)/positions/
|
||||
|
||||
Returns a list of all order positions within a given event. The result is the same as
|
||||
@@ -400,7 +427,8 @@ Order position endpoints
|
||||
"checkins": [
|
||||
{
|
||||
"list": 1,
|
||||
"datetime": "2017-12-25T12:45:23Z"
|
||||
"datetime": "2017-12-25T12:45:23Z",
|
||||
"auto_checked_in": true
|
||||
}
|
||||
],
|
||||
"answers": [
|
||||
@@ -510,7 +538,8 @@ Order position endpoints
|
||||
"checkins": [
|
||||
{
|
||||
"list": 1,
|
||||
"datetime": "2017-12-25T12:45:23Z"
|
||||
"datetime": "2017-12-25T12:45:23Z",
|
||||
"auto_checked_in": true
|
||||
}
|
||||
],
|
||||
"answers": [
|
||||
|
||||
@@ -175,7 +175,8 @@ subevent integer ID of the date
|
||||
pseudonymization_id string A random ID, e.g. for use in lead scanning apps
|
||||
checkins list of objects List of check-ins with this ticket
|
||||
├ list integer Internal ID of the check-in list
|
||||
└ datetime datetime Time of check-in
|
||||
├ datetime datetime Time of check-in
|
||||
└ auto_checked_in boolean Indicates if this check-in been performed automatically by the system
|
||||
downloads list of objects List of ticket download options
|
||||
├ output string Ticket output provider (e.g. ``pdf``, ``passbook``)
|
||||
└ url string Download URL
|
||||
@@ -214,6 +215,10 @@ pdf_data object Data object req
|
||||
|
||||
The attribute ``seat`` has been added.
|
||||
|
||||
.. versionchanged:: 3.2
|
||||
|
||||
The value ``auto_checked_in`` has been added to the ``checkins``-attribute.
|
||||
|
||||
.. _order-payment-resource:
|
||||
|
||||
Order payment resource
|
||||
@@ -365,7 +370,8 @@ List of all orders
|
||||
"checkins": [
|
||||
{
|
||||
"list": 44,
|
||||
"datetime": "2017-12-25T12:45:23Z"
|
||||
"datetime": "2017-12-25T12:45:23Z",
|
||||
"auto_checked_in": false
|
||||
}
|
||||
],
|
||||
"answers": [
|
||||
@@ -512,7 +518,8 @@ Fetching individual orders
|
||||
"checkins": [
|
||||
{
|
||||
"list": 44,
|
||||
"datetime": "2017-12-25T12:45:23Z"
|
||||
"datetime": "2017-12-25T12:45:23Z",
|
||||
"auto_checked_in": false
|
||||
}
|
||||
],
|
||||
"answers": [
|
||||
@@ -1286,6 +1293,11 @@ List of all order positions
|
||||
The order positions endpoint has been extended by the filter queries ``voucher``, ``voucher__code`` and
|
||||
``pseudonymization_id``.
|
||||
|
||||
.. versionchanged:: 3.2
|
||||
|
||||
The value ``auto_checked_in`` has been added to the ``checkins``-attribute.
|
||||
|
||||
|
||||
.. note:: Individually canceled order positions are currently not visible via the API at all.
|
||||
|
||||
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/orderpositions/
|
||||
@@ -1337,7 +1349,8 @@ List of all order positions
|
||||
"checkins": [
|
||||
{
|
||||
"list": 44,
|
||||
"datetime": "2017-12-25T12:45:23Z"
|
||||
"datetime": "2017-12-25T12:45:23Z",
|
||||
"auto_checked_in": false
|
||||
}
|
||||
],
|
||||
"answers": [
|
||||
@@ -1438,7 +1451,8 @@ Fetching individual positions
|
||||
"checkins": [
|
||||
{
|
||||
"list": 44,
|
||||
"datetime": "2017-12-25T12:45:23Z"
|
||||
"datetime": "2017-12-25T12:45:23Z",
|
||||
"auto_checked_in": false
|
||||
}
|
||||
],
|
||||
"answers": [
|
||||
|
||||
@@ -12,6 +12,7 @@ Contents:
|
||||
payment
|
||||
payment_2.0
|
||||
email
|
||||
placeholder
|
||||
invoice
|
||||
shredder
|
||||
customview
|
||||
|
||||
79
doc/development/api/placeholder.rst
Normal file
79
doc/development/api/placeholder.rst
Normal file
@@ -0,0 +1,79 @@
|
||||
.. highlight:: python
|
||||
:linenothreshold: 5
|
||||
|
||||
Writing an HTML e-mail placeholder plugin
|
||||
=========================================
|
||||
|
||||
An email placeholder is a dynamic value that pretix users can use in their email templates.
|
||||
|
||||
Please read :ref:`Creating a plugin <pluginsetup>` first, if you haven't already.
|
||||
|
||||
Placeholder registration
|
||||
------------------------
|
||||
|
||||
The placeholder API does not make a lot of usage from signals, however, it
|
||||
does use a signal to get a list of all available email placeholders. Your plugin
|
||||
should listen for this signal and return an instance of a subclass of ``pretix.base.email.BaseMailTextPlaceholder``::
|
||||
|
||||
from django.dispatch import receiver
|
||||
|
||||
from pretix.base.signals import register_mail_placeholders
|
||||
|
||||
|
||||
@receiver(register_mail_placeholders, dispatch_uid="placeholder_custom")
|
||||
def register_mail_renderers(sender, **kwargs):
|
||||
from .email import MyPlaceholderClass
|
||||
return MyPlaceholder()
|
||||
|
||||
|
||||
Context mechanism
|
||||
-----------------
|
||||
|
||||
Emails are sent in different "contexts" within pretix. For example, many emails are sent in the
|
||||
the context of an order, but some are not, such as the notification of a waiting list voucher.
|
||||
|
||||
Not all placeholders make sense in every email, and placeholders usually depend some parameters
|
||||
themselves, such as the ``Order`` object. Therefore, placeholders are expected to explicitly declare
|
||||
what values they depend on and they will only be available in an email if all those dependencies are
|
||||
met. Currently, placeholders can depend on the following context parameters:
|
||||
|
||||
* ``event``
|
||||
* ``order``
|
||||
* ``position``
|
||||
* ``waiting_list_entry``
|
||||
* ``invoice_address``
|
||||
* ``payment``
|
||||
|
||||
There are a few more that are only to be used internally but not by plugins.
|
||||
|
||||
The placeholder class
|
||||
---------------------
|
||||
|
||||
.. class:: pretix.base.email.BaseMailTextPlaceholder
|
||||
|
||||
.. autoattribute:: identifier
|
||||
|
||||
This is an abstract attribute, you **must** override this!
|
||||
|
||||
.. autoattribute:: required_context
|
||||
|
||||
This is an abstract attribute, you **must** override this!
|
||||
|
||||
.. automethod:: render
|
||||
|
||||
This is an abstract method, you **must** implement this!
|
||||
|
||||
.. automethod:: render_sample
|
||||
|
||||
This is an abstract method, you **must** implement this!
|
||||
|
||||
Helper class for simple placeholders
|
||||
------------------------------------
|
||||
|
||||
pretix ships with a helper class that makes it easy to provide placeholders based on simple
|
||||
functions::
|
||||
|
||||
placeholder = SimpleFunctionalMailTextPlaceholder(
|
||||
'code', ['order'], lambda order: order.code, sample='F8VVL'
|
||||
)
|
||||
|
||||
@@ -35,9 +35,9 @@ The shredder class
|
||||
|
||||
.. class:: pretix.base.shredder.BaseDataShredder
|
||||
|
||||
The central object of each invoice renderer is the subclass of ``BaseInvoiceRenderer``.
|
||||
The central object of each data shredder is the subclass of ``BaseDataShredder``.
|
||||
|
||||
.. py:attribute:: BaseInvoiceRenderer.event
|
||||
.. py:attribute:: BaseDataShredder.event
|
||||
|
||||
The default constructor sets this property to the event we are currently
|
||||
working for.
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = "3.1.0"
|
||||
__version__ = "3.2.0"
|
||||
|
||||
@@ -3,6 +3,7 @@ from rest_framework import serializers
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
from pretix.api.serializers.i18n import I18nAwareModelSerializer
|
||||
from pretix.base.channels import get_all_sales_channels
|
||||
from pretix.base.models import CheckinList
|
||||
|
||||
|
||||
@@ -13,7 +14,7 @@ class CheckinListSerializer(I18nAwareModelSerializer):
|
||||
class Meta:
|
||||
model = CheckinList
|
||||
fields = ('id', 'name', 'all_products', 'limit_products', 'subevent', 'checkin_count', 'position_count',
|
||||
'include_pending')
|
||||
'include_pending', 'auto_checkin_sales_channels')
|
||||
|
||||
def validate(self, data):
|
||||
data = super().validate(data)
|
||||
@@ -35,4 +36,8 @@ class CheckinListSerializer(I18nAwareModelSerializer):
|
||||
if full_data.get('subevent'):
|
||||
raise ValidationError(_('The subevent does not belong to this event.'))
|
||||
|
||||
for channel in full_data.get('auto_checkin_sales_channels') or []:
|
||||
if channel not in get_all_sales_channels():
|
||||
raise ValidationError(_('Unknown sales channel.'))
|
||||
|
||||
return data
|
||||
|
||||
@@ -114,7 +114,7 @@ class AnswerSerializer(I18nAwareModelSerializer):
|
||||
class CheckinSerializer(I18nAwareModelSerializer):
|
||||
class Meta:
|
||||
model = Checkin
|
||||
fields = ('datetime', 'list')
|
||||
fields = ('datetime', 'list', 'auto_checked_in')
|
||||
|
||||
|
||||
class OrderDownloadsField(serializers.Field):
|
||||
|
||||
@@ -44,7 +44,6 @@ class CheckinListViewSet(viewsets.ModelViewSet):
|
||||
qs = self.request.event.checkin_lists.prefetch_related(
|
||||
'limit_products',
|
||||
)
|
||||
qs = CheckinList.annotate_with_numbers(qs, self.request.event)
|
||||
return qs
|
||||
|
||||
def perform_create(self, serializer):
|
||||
|
||||
@@ -13,7 +13,7 @@ class PretixBaseConfig(AppConfig):
|
||||
from . import invoice # NOQA
|
||||
from . import notifications # NOQA
|
||||
from . import email # NOQA
|
||||
from .services import auth, export, mail, tickets, cart, orders, invoices, cleanup, update_check, quotas, notifications # NOQA
|
||||
from .services import auth, checkin, export, mail, tickets, cart, orders, invoices, cleanup, update_check, quotas, notifications # NOQA
|
||||
|
||||
try:
|
||||
from .celery_app import app as celery_app # NOQA
|
||||
|
||||
80
src/pretix/base/banlist.py
Normal file
80
src/pretix/base/banlist.py
Normal file
@@ -0,0 +1,80 @@
|
||||
import re
|
||||
|
||||
# banlist based on http://www.bannedwordlist.com/lists/swearWords.txt
|
||||
banlist = [
|
||||
"anal",
|
||||
"anus",
|
||||
"arse",
|
||||
"ass",
|
||||
"balls",
|
||||
"bastard",
|
||||
"bitch",
|
||||
"biatch",
|
||||
"bloody",
|
||||
"blowjob",
|
||||
"bollock",
|
||||
"bollok",
|
||||
"boner",
|
||||
"boob",
|
||||
"bugger",
|
||||
"bum",
|
||||
"butt",
|
||||
"clitoris",
|
||||
"cock",
|
||||
"coon",
|
||||
"crap",
|
||||
"cunt",
|
||||
"damn",
|
||||
"dick",
|
||||
"dildo",
|
||||
"dyke",
|
||||
"fag",
|
||||
"feck",
|
||||
"fellate",
|
||||
"fellatio",
|
||||
"felching",
|
||||
"fuck",
|
||||
"fudgepacker",
|
||||
"flange",
|
||||
"goddamn",
|
||||
"hell",
|
||||
"homo",
|
||||
"jerk",
|
||||
"jizz",
|
||||
"knobend",
|
||||
"labia",
|
||||
"lmao",
|
||||
"lmfao",
|
||||
"muff",
|
||||
"nigger",
|
||||
"nigga",
|
||||
"omg",
|
||||
"penis",
|
||||
"piss",
|
||||
"poop",
|
||||
"prick",
|
||||
"pube",
|
||||
"pussy",
|
||||
"queer",
|
||||
"scrotum",
|
||||
"sex",
|
||||
"shit",
|
||||
"sh1t",
|
||||
"slut",
|
||||
"smegma",
|
||||
"spunk",
|
||||
"tit",
|
||||
"tosser",
|
||||
"turd",
|
||||
"twat",
|
||||
"vagina",
|
||||
"wank",
|
||||
"whore",
|
||||
"wtf"
|
||||
]
|
||||
|
||||
blacklist_regex = re.compile('(' + '|'.join(banlist) + ')')
|
||||
|
||||
|
||||
def banned(string):
|
||||
return bool(blacklist_regex.search(string.lower()))
|
||||
13
src/pretix/base/context.py
Normal file
13
src/pretix/base/context.py
Normal file
@@ -0,0 +1,13 @@
|
||||
import sys
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
def contextprocessor(request):
|
||||
ctx = {}
|
||||
if settings.DEBUG and 'runserver' not in sys.argv:
|
||||
ctx['debug_warning'] = True
|
||||
elif 'runserver' in sys.argv:
|
||||
ctx['development_warning'] = True
|
||||
|
||||
return ctx
|
||||
@@ -1,15 +1,23 @@
|
||||
import inspect
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
from decimal import Decimal
|
||||
from smtplib import SMTPResponseException
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.mail.backends.smtp import EmailBackend
|
||||
from django.dispatch import receiver
|
||||
from django.template.loader import get_template
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from inlinestyler.utils import inline_css
|
||||
|
||||
from pretix.base.models import Event, Order, OrderPosition
|
||||
from pretix.base.signals import register_html_mail_renderers
|
||||
from pretix.base.i18n import LazyCurrencyNumber, LazyDate, LazyNumber
|
||||
from pretix.base.models import Event
|
||||
from pretix.base.settings import PERSON_NAME_SCHEMES
|
||||
from pretix.base.signals import (
|
||||
register_html_mail_renderers, register_mail_placeholders,
|
||||
)
|
||||
from pretix.base.templatetags.rich_text import markdown_compile_email
|
||||
|
||||
logger = logging.getLogger('pretix.base.email')
|
||||
@@ -44,8 +52,8 @@ class BaseHTMLMailRenderer:
|
||||
def __str__(self):
|
||||
return self.identifier
|
||||
|
||||
def render(self, plain_body: str, plain_signature: str, subject: str, order: Order=None,
|
||||
position: OrderPosition=None) -> str:
|
||||
def render(self, plain_body: str, plain_signature: str, subject: str, order=None,
|
||||
position=None) -> str:
|
||||
"""
|
||||
This method should generate the HTML part of the email.
|
||||
|
||||
@@ -97,7 +105,7 @@ class TemplateBasedMailRenderer(BaseHTMLMailRenderer):
|
||||
def template_name(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def render(self, plain_body: str, plain_signature: str, subject: str, order: Order, position: OrderPosition) -> str:
|
||||
def render(self, plain_body: str, plain_signature: str, subject: str, order, position) -> str:
|
||||
body_md = markdown_compile_email(plain_body)
|
||||
htmlctx = {
|
||||
'site': settings.PRETIX_INSTANCE_NAME,
|
||||
@@ -136,3 +144,285 @@ class ClassicMailRenderer(TemplateBasedMailRenderer):
|
||||
@receiver(register_html_mail_renderers, dispatch_uid="pretixbase_email_renderers")
|
||||
def base_renderers(sender, **kwargs):
|
||||
return [ClassicMailRenderer]
|
||||
|
||||
|
||||
class BaseMailTextPlaceholder:
|
||||
"""
|
||||
This is the base class for for all email text placeholders.
|
||||
"""
|
||||
|
||||
@property
|
||||
def required_context(self):
|
||||
"""
|
||||
This property should return a list of all attribute names that need to be
|
||||
contained in the base context so that this placeholder is available. By default,
|
||||
it returns a list containing the string "event".
|
||||
"""
|
||||
return ["event"]
|
||||
|
||||
@property
|
||||
def identifier(self):
|
||||
"""
|
||||
This should return the identifier of this placeholder in the email.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def render(self, context):
|
||||
"""
|
||||
This method is called to generate the actual text that is being
|
||||
used in the email. You will be passed a context dictionary with the
|
||||
base context attributes specified in ``required_context``. You are
|
||||
expected to return a plain-text string.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def render_sample(self, event):
|
||||
"""
|
||||
This method is called to generate a text to be used in email previews.
|
||||
This may only depend on the event.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class SimpleFunctionalMailTextPlaceholder(BaseMailTextPlaceholder):
|
||||
def __init__(self, identifier, args, func, sample):
|
||||
self._identifier = identifier
|
||||
self._args = args
|
||||
self._func = func
|
||||
self._sample = sample
|
||||
|
||||
@property
|
||||
def identifier(self):
|
||||
return self._identifier
|
||||
|
||||
@property
|
||||
def required_context(self):
|
||||
return self._args
|
||||
|
||||
def render(self, context):
|
||||
return self._func(**{k: context[k] for k in self._args})
|
||||
|
||||
def render_sample(self, event):
|
||||
if callable(self._sample):
|
||||
return self._sample(event)
|
||||
else:
|
||||
return self._sample
|
||||
|
||||
|
||||
def get_available_placeholders(event, base_parameters):
|
||||
if 'order' in base_parameters:
|
||||
base_parameters.append('invoice_address')
|
||||
params = {}
|
||||
for r, val in register_mail_placeholders.send(sender=event):
|
||||
if not isinstance(val, (list, tuple)):
|
||||
val = [val]
|
||||
for v in val:
|
||||
if all(rp in base_parameters for rp in v.required_context):
|
||||
params[v.identifier] = v
|
||||
return params
|
||||
|
||||
|
||||
def get_email_context(**kwargs):
|
||||
from pretix.base.models import InvoiceAddress
|
||||
|
||||
event = kwargs['event']
|
||||
if 'order' in kwargs:
|
||||
try:
|
||||
kwargs['invoice_address'] = kwargs['order'].invoice_address
|
||||
except InvoiceAddress.DoesNotExist:
|
||||
kwargs['invoice_address'] = InvoiceAddress()
|
||||
ctx = {}
|
||||
for r, val in register_mail_placeholders.send(sender=event):
|
||||
if not isinstance(val, (list, tuple)):
|
||||
val = [val]
|
||||
for v in val:
|
||||
if all(rp in kwargs for rp in v.required_context):
|
||||
ctx[v.identifier] = v.render(kwargs)
|
||||
return ctx
|
||||
|
||||
|
||||
def _placeholder_payment(order, payment):
|
||||
if not payment:
|
||||
return None
|
||||
if 'payment' in inspect.signature(payment.payment_provider.order_pending_mail_render).parameters:
|
||||
return str(payment.payment_provider.order_pending_mail_render(order, payment))
|
||||
else:
|
||||
return str(payment.payment_provider.order_pending_mail_render(order))
|
||||
|
||||
|
||||
@receiver(register_mail_placeholders, dispatch_uid="pretixbase_register_mail_placeholders")
|
||||
def base_placeholders(sender, **kwargs):
|
||||
from pretix.base.models import InvoiceAddress
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
|
||||
ph = [
|
||||
SimpleFunctionalMailTextPlaceholder(
|
||||
'event', ['event'], lambda event: event.name, lambda event: event.name
|
||||
),
|
||||
SimpleFunctionalMailTextPlaceholder(
|
||||
'code', ['order'], lambda order: order.code, 'F8VVL'
|
||||
),
|
||||
SimpleFunctionalMailTextPlaceholder(
|
||||
'total', ['order'], lambda order: LazyNumber(order.total), lambda event: LazyNumber(Decimal('42.23'))
|
||||
),
|
||||
SimpleFunctionalMailTextPlaceholder(
|
||||
'currency', ['event'], lambda event: event.currency, lambda event: event.currency
|
||||
),
|
||||
SimpleFunctionalMailTextPlaceholder(
|
||||
'total_with_currency', ['event', 'order'], lambda event, order: LazyCurrencyNumber(order.total,
|
||||
event.currency),
|
||||
lambda event: LazyCurrencyNumber(Decimal('42.23'), event.currency)
|
||||
),
|
||||
SimpleFunctionalMailTextPlaceholder(
|
||||
'expire_date', ['event', 'order'], lambda event, order: LazyDate(order.expires.astimezone(event.timezone)),
|
||||
lambda event: LazyDate(now() + timedelta(days=15))
|
||||
# TODO: This used to be "date" in some placeholders, add a migration!
|
||||
),
|
||||
SimpleFunctionalMailTextPlaceholder(
|
||||
'url', ['order', 'event'], lambda order, event: build_absolute_uri(
|
||||
event,
|
||||
'presale:event.order.open', kwargs={
|
||||
'order': order.code,
|
||||
'secret': order.secret,
|
||||
'hash': order.email_confirm_hash()
|
||||
}
|
||||
), lambda event: build_absolute_uri(
|
||||
event,
|
||||
'presale:event.order.open', kwargs={
|
||||
'order': 'F8VVL',
|
||||
'secret': '6zzjnumtsx136ddy',
|
||||
'hash': '98kusd8ofsj8dnkd'
|
||||
}
|
||||
),
|
||||
),
|
||||
SimpleFunctionalMailTextPlaceholder(
|
||||
'url', ['event', 'position'], lambda event, position: build_absolute_uri(
|
||||
event,
|
||||
'presale:event.order.position',
|
||||
kwargs={
|
||||
'order': position.order.code,
|
||||
'secret': position.web_secret,
|
||||
'position': position.positionid
|
||||
}
|
||||
),
|
||||
lambda event: build_absolute_uri(
|
||||
event,
|
||||
'presale:event.order.position', kwargs={
|
||||
'order': 'F8VVL',
|
||||
'secret': '6zzjnumtsx136ddy',
|
||||
'position': '123'
|
||||
}
|
||||
),
|
||||
),
|
||||
SimpleFunctionalMailTextPlaceholder(
|
||||
'url', ['waiting_list_entry', 'event'],
|
||||
lambda waiting_list_entry, event: build_absolute_uri(
|
||||
event, 'presale:event.redeem'
|
||||
) + '?voucher=' + waiting_list_entry.voucher.code,
|
||||
lambda event: build_absolute_uri(
|
||||
event,
|
||||
'presale:event.redeem',
|
||||
) + '?voucher=68CYU2H6ZTP3WLK5',
|
||||
),
|
||||
SimpleFunctionalMailTextPlaceholder(
|
||||
'invoice_name', ['invoice_address'], lambda invoice_address: invoice_address.name or '',
|
||||
_('John Doe')
|
||||
),
|
||||
SimpleFunctionalMailTextPlaceholder(
|
||||
'invoice_company', ['invoice_address'], lambda invoice_address: invoice_address.company or '',
|
||||
_('Sample Corporation')
|
||||
),
|
||||
SimpleFunctionalMailTextPlaceholder(
|
||||
'orders', ['event', 'orders'], lambda event, orders: '\n' + '\n\n'.join(
|
||||
'* {} - {}'.format(
|
||||
order.full_code,
|
||||
build_absolute_uri(event, 'presale:event.order', kwargs={
|
||||
'event': event.slug,
|
||||
'organizer': event.organizer.slug,
|
||||
'order': order.code,
|
||||
'secret': order.secret
|
||||
}),
|
||||
)
|
||||
for order in orders
|
||||
), lambda event: '\n' + '\n\n'.join(
|
||||
'* {} - {}'.format(
|
||||
'{}-{}'.format(event.slug.upper(), order['code']),
|
||||
build_absolute_uri(event, 'presale:event.order', kwargs={
|
||||
'event': event.slug,
|
||||
'organizer': event.organizer.slug,
|
||||
'order': order['code'],
|
||||
'secret': order['secret']
|
||||
}),
|
||||
)
|
||||
for order in [
|
||||
{'code': 'F8VVL', 'secret': '6zzjnumtsx136ddy'},
|
||||
{'code': 'HIDHK', 'secret': '98kusd8ofsj8dnkd'},
|
||||
{'code': 'OPKSB', 'secret': '09pjdksflosk3njd'}
|
||||
]
|
||||
),
|
||||
),
|
||||
SimpleFunctionalMailTextPlaceholder(
|
||||
'hours', ['event', 'waiting_list_entry'], lambda event, waiting_list_entry:
|
||||
event.settings.waiting_list_hours,
|
||||
lambda event: event.settings.waiting_list_hours
|
||||
),
|
||||
SimpleFunctionalMailTextPlaceholder(
|
||||
'product', ['waiting_list_entry'], lambda waiting_list_entry: waiting_list_entry.item.name,
|
||||
_('Sample Admission Ticket')
|
||||
),
|
||||
SimpleFunctionalMailTextPlaceholder(
|
||||
'code', ['waiting_list_entry'], lambda waiting_list_entry: waiting_list_entry.voucher.code,
|
||||
'68CYU2H6ZTP3WLK5'
|
||||
),
|
||||
SimpleFunctionalMailTextPlaceholder(
|
||||
'comment', ['comment'], lambda comment: comment,
|
||||
_('An individual text with a reason can be inserted here.'),
|
||||
),
|
||||
SimpleFunctionalMailTextPlaceholder(
|
||||
'payment_info', ['order', 'payment'], _placeholder_payment,
|
||||
_('The amount has been charged to your card.'),
|
||||
),
|
||||
SimpleFunctionalMailTextPlaceholder(
|
||||
'payment_info', ['payment_info'], lambda payment_info: payment_info,
|
||||
_('Please transfer money to this bank account: 9999-9999-9999-9999'),
|
||||
),
|
||||
SimpleFunctionalMailTextPlaceholder(
|
||||
'attendee_name', ['position'], lambda position: position.attendee_name,
|
||||
_('John Doe'),
|
||||
),
|
||||
SimpleFunctionalMailTextPlaceholder(
|
||||
'name', ['position_or_address'],
|
||||
lambda position_or_address: (
|
||||
position_or_address.name
|
||||
if isinstance(position_or_address, InvoiceAddress)
|
||||
else position_or_address.attendee_name
|
||||
),
|
||||
_('John Doe'),
|
||||
),
|
||||
]
|
||||
|
||||
name_scheme = PERSON_NAME_SCHEMES[sender.settings.name_scheme]
|
||||
for f, l, w in name_scheme['fields']:
|
||||
if f == 'full_name':
|
||||
continue
|
||||
ph.append(SimpleFunctionalMailTextPlaceholder(
|
||||
'attendee_name_%s' % f, ['position'], lambda position, f=f: position.attendee_name_parts.get(f, ''),
|
||||
name_scheme['sample'][f]
|
||||
))
|
||||
ph.append(SimpleFunctionalMailTextPlaceholder(
|
||||
'name_%s' % f, ['position_or_address'],
|
||||
lambda position_or_address, f=f: (
|
||||
position_or_address.name_parts.get(f, '')
|
||||
if isinstance(position_or_address, InvoiceAddress)
|
||||
else position_or_address.attendee_name_parts.get(f, '')
|
||||
),
|
||||
name_scheme['sample'][f]
|
||||
))
|
||||
|
||||
for k, v in sender.meta_data.items():
|
||||
ph.append(SimpleFunctionalMailTextPlaceholder(
|
||||
'meta_%s' % k, ['event'], lambda event, k=k: event.meta_data[k],
|
||||
v
|
||||
))
|
||||
|
||||
return ph
|
||||
|
||||
@@ -111,7 +111,7 @@ class ListExporter(BaseExporter):
|
||||
raise NotImplementedError() # noqa
|
||||
|
||||
def get_filename(self):
|
||||
return 'export.csv'
|
||||
return 'export'
|
||||
|
||||
def _render_csv(self, form_data, output_file=None, **kwargs):
|
||||
if output_file:
|
||||
|
||||
@@ -129,8 +129,11 @@ class DekodiNREIExporter(BaseExporter):
|
||||
'DIDt': invoice.order.datetime.isoformat().replace('Z', '+00:00'),
|
||||
'DT': '30' if invoice.is_cancellation else '10',
|
||||
'EM': invoice.order.email,
|
||||
'FamN': invoice.invoice_to_name.rsplit(' ', 1)[-1],
|
||||
'FN': invoice.invoice_to_name.rsplit(' ', 1)[0] if ' ' in invoice.invoice_to_name else '',
|
||||
'FamN': invoice.invoice_to_name.rsplit(' ', 1)[-1] if invoice.invoice_to_name else '',
|
||||
'FN': (
|
||||
invoice.invoice_to_name.rsplit(' ', 1)[0]
|
||||
if invoice.invoice_to_name and ' ' in invoice.invoice_to_name else ''
|
||||
),
|
||||
'IDt': invoice.date.isoformat() + 'T08:00:00+01:00',
|
||||
'INo': invoice.full_invoice_no,
|
||||
'IsNet': invoice.reverse_charge,
|
||||
|
||||
@@ -43,6 +43,14 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class NamePartsWidget(forms.MultiWidget):
|
||||
widget = forms.TextInput
|
||||
autofill_map = {
|
||||
'given_name': 'given-name',
|
||||
'family_name': 'family-name',
|
||||
'middle_name': 'additional-name',
|
||||
'title': 'honorific-prefix',
|
||||
'full_name': 'name',
|
||||
'calling_name': 'nickname',
|
||||
}
|
||||
|
||||
def __init__(self, scheme: dict, field: forms.Field, attrs=None, titles: list=None):
|
||||
widgets = []
|
||||
@@ -89,6 +97,7 @@ class NamePartsWidget(forms.MultiWidget):
|
||||
title=self.scheme['fields'][i][1],
|
||||
placeholder=self.scheme['fields'][i][1],
|
||||
)
|
||||
final_attrs['autocomplete'] = (self.attrs.get('autocomplete', '') + ' ' + self.autofill_map.get(self.scheme['fields'][i][0], 'off')).strip()
|
||||
final_attrs['data-size'] = self.scheme['fields'][i][2]
|
||||
output.append(widget.render(name + '_%s' % i, widget_value, final_attrs, renderer=renderer))
|
||||
return mark_safe(self.format_output(output))
|
||||
@@ -194,7 +203,12 @@ class BaseQuestionsForm(forms.Form):
|
||||
self.fields['attendee_email'] = forms.EmailField(
|
||||
required=event.settings.attendee_emails_required,
|
||||
label=_('Attendee email'),
|
||||
initial=(cartpos.attendee_email if cartpos else orderpos.attendee_email)
|
||||
initial=(cartpos.attendee_email if cartpos else orderpos.attendee_email),
|
||||
widget=forms.EmailInput(
|
||||
attrs={
|
||||
'autocomplete': 'email'
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
for q in questions:
|
||||
@@ -324,6 +338,10 @@ class BaseQuestionsForm(forms.Form):
|
||||
self.fields[key] = value
|
||||
value.initial = data.get('question_form_data', {}).get(key)
|
||||
|
||||
for k, v in self.fields.items():
|
||||
if v.widget.attrs.get('autocomplete') or k == 'attendee_name_parts':
|
||||
v.widget.attrs['autocomplete'] = 'section-{} '.format(self.prefix) + v.widget.attrs.get('autocomplete', '')
|
||||
|
||||
def clean(self):
|
||||
d = super().clean()
|
||||
|
||||
@@ -366,9 +384,25 @@ class BaseInvoiceAddressForm(forms.ModelForm):
|
||||
'vat_id', 'internal_reference', 'beneficiary')
|
||||
widgets = {
|
||||
'is_business': BusinessBooleanRadio,
|
||||
'street': forms.Textarea(attrs={'rows': 2, 'placeholder': _('Street and Number')}),
|
||||
'street': forms.Textarea(attrs={
|
||||
'rows': 2,
|
||||
'placeholder': _('Street and Number'),
|
||||
'autocomplete': 'street-address'
|
||||
}),
|
||||
'beneficiary': forms.Textarea(attrs={'rows': 3}),
|
||||
'company': forms.TextInput(attrs={'data-display-dependency': '#id_is_business_1'}),
|
||||
'country': forms.Select(attrs={
|
||||
'autocomplete': 'country',
|
||||
}),
|
||||
'zipcode': forms.TextInput(attrs={
|
||||
'autocomplete': 'postal-code',
|
||||
}),
|
||||
'city': forms.TextInput(attrs={
|
||||
'autocomplete': 'address-level2',
|
||||
}),
|
||||
'company': forms.TextInput(attrs={
|
||||
'data-display-dependency': '#id_is_business_1',
|
||||
'autocomplete': 'organization',
|
||||
}),
|
||||
'vat_id': forms.TextInput(attrs={'data-display-dependency': '#id_is_business_1'}),
|
||||
'internal_reference': forms.TextInput,
|
||||
}
|
||||
@@ -426,7 +460,10 @@ class BaseInvoiceAddressForm(forms.ModelForm):
|
||||
self.fields['state'] = forms.ChoiceField(
|
||||
label=pgettext_lazy('address', 'State'),
|
||||
required=False,
|
||||
choices=c
|
||||
choices=c,
|
||||
widget=forms.Select(attrs={
|
||||
'autocomplete': 'address-level1',
|
||||
}),
|
||||
)
|
||||
self.fields['state'].widget.is_required = True
|
||||
|
||||
@@ -463,6 +500,10 @@ class BaseInvoiceAddressForm(forms.ModelForm):
|
||||
if not event.settings.invoice_address_beneficiary:
|
||||
del self.fields['beneficiary']
|
||||
|
||||
for k, v in self.fields.items():
|
||||
if v.widget.attrs.get('autocomplete') or k == 'name_parts':
|
||||
v.widget.attrs['autocomplete'] = 'section-invoice billing ' + v.widget.attrs.get('autocomplete', '')
|
||||
|
||||
def clean(self):
|
||||
data = self.cleaned_data
|
||||
if not data.get('is_business'):
|
||||
@@ -522,9 +563,8 @@ class BaseInvoiceAddressForm(forms.ModelForm):
|
||||
|
||||
|
||||
class BaseInvoiceNameForm(BaseInvoiceAddressForm):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
for f in list(self.fields.keys()):
|
||||
if f != 'name':
|
||||
if f != 'name_parts':
|
||||
del self.fields[f]
|
||||
|
||||
@@ -26,7 +26,7 @@ class PlaceholderValidator(BaseValidator):
|
||||
if value.count('{') != value.count('}'):
|
||||
raise ValidationError(
|
||||
_('Invalid placeholder syntax: You used a different number of "{" than of "}".'),
|
||||
code='invalid',
|
||||
code='invalid_placeholder_syntax',
|
||||
)
|
||||
|
||||
data_placeholders = list(re.findall(r'({[^}]*})', value, re.X))
|
||||
@@ -37,7 +37,7 @@ class PlaceholderValidator(BaseValidator):
|
||||
if invalid_placeholders:
|
||||
raise ValidationError(
|
||||
_('Invalid placeholder(s): %(value)s'),
|
||||
code='invalid',
|
||||
code='invalid_placeholders',
|
||||
params={'value': ", ".join(invalid_placeholders,)})
|
||||
|
||||
def clean(self, x):
|
||||
|
||||
27
src/pretix/base/migrations/0135_auto_20191007_0803.py
Normal file
27
src/pretix/base/migrations/0135_auto_20191007_0803.py
Normal file
@@ -0,0 +1,27 @@
|
||||
# Generated by Django 2.2.4 on 2019-10-07 08:03
|
||||
from django.core.cache import cache
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def mail_migrator(app, schema_editor):
|
||||
Event_SettingsStore = app.get_model('pretixbase', 'Event_SettingsStore')
|
||||
|
||||
for ss in Event_SettingsStore.objects.filter(
|
||||
key__in=['mail_text_order_approved', 'mail_text_order_placed', 'mail_text_order_placed_require_approval']
|
||||
):
|
||||
chgd = ss.value.replace("{date}", "{expire_date}")
|
||||
if chgd != ss.value:
|
||||
ss.value = chgd
|
||||
ss.save()
|
||||
cache.delete('hierarkey_{}_{}'.format('event', ss.object_id))
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('pretixbase', '0134_auto_20190909_1042'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(mail_migrator, migrations.RunPython.noop)
|
||||
]
|
||||
25
src/pretix/base/migrations/0136_auto_20190918_1742.py
Normal file
25
src/pretix/base/migrations/0136_auto_20190918_1742.py
Normal file
@@ -0,0 +1,25 @@
|
||||
# Generated by Django 2.2 on 2019-09-18 17:42
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
import pretix.base.models.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('pretixbase', '0135_auto_20191007_0803'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='checkin',
|
||||
name='auto_checked_in',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='checkinlist',
|
||||
name='auto_checkin_sales_channels',
|
||||
field=pretix.base.models.fields.MultiStringField(default=[]),
|
||||
)
|
||||
]
|
||||
@@ -1,11 +1,11 @@
|
||||
from django.db import models
|
||||
from django.db.models import Case, Count, F, OuterRef, Q, Subquery, When
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.db.models import Exists, OuterRef
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import pgettext_lazy, ugettext_lazy as _
|
||||
from django_scopes import ScopedManager
|
||||
|
||||
from pretix.base.models import LoggedModel
|
||||
from pretix.base.models.fields import MultiStringField
|
||||
|
||||
|
||||
class CheckinList(LoggedModel):
|
||||
@@ -18,142 +18,69 @@ class CheckinList(LoggedModel):
|
||||
include_pending = models.BooleanField(verbose_name=pgettext_lazy('checkin', 'Include pending orders'),
|
||||
default=False,
|
||||
help_text=_('With this option, people will be able to check in even if the '
|
||||
'order have not been paid. This only works with pretixdesk '
|
||||
'0.3.0 or newer or pretixdroid 1.9 or newer.'))
|
||||
'order have not been paid.'))
|
||||
|
||||
auto_checkin_sales_channels = MultiStringField(
|
||||
default=[],
|
||||
blank=True,
|
||||
verbose_name=_('Sales channels to automatically check in'),
|
||||
help_text=_('All items on this check-in list will be automatically marked as checked-in when purchased through '
|
||||
'any of the selected sales channels. This option can be useful when tickets sold at the box office '
|
||||
'are not checked again before entry and should be considered validated directly upon purchase.')
|
||||
)
|
||||
|
||||
objects = ScopedManager(organizer='event__organizer')
|
||||
|
||||
class Meta:
|
||||
ordering = ('subevent__date_from', 'name')
|
||||
|
||||
@property
|
||||
def positions(self):
|
||||
from . import OrderPosition, Order
|
||||
|
||||
qs = OrderPosition.objects.filter(
|
||||
order__event=self.event,
|
||||
order__status__in=[Order.STATUS_PAID, Order.STATUS_PENDING] if self.include_pending else [Order.STATUS_PAID],
|
||||
subevent=self.subevent
|
||||
)
|
||||
if not self.all_products:
|
||||
qs = qs.filter(item__in=self.limit_products.values_list('id', flat=True))
|
||||
return qs
|
||||
|
||||
@property
|
||||
def checkin_count(self):
|
||||
return self.event.cache.get_or_set(
|
||||
'checkin_list_{}_checkin_count'.format(self.pk),
|
||||
lambda: self.positions.annotate(
|
||||
checkedin=Exists(Checkin.objects.filter(list_id=self.pk, position=OuterRef('pk')))
|
||||
).filter(
|
||||
checkedin=True
|
||||
).count(),
|
||||
60
|
||||
)
|
||||
|
||||
@property
|
||||
def percent(self):
|
||||
pc = self.position_count
|
||||
return round(self.checkin_count * 100 / pc) if pc else 0
|
||||
|
||||
@property
|
||||
def position_count(self):
|
||||
return self.event.cache.get_or_set(
|
||||
'checkin_list_{}_position_count'.format(self.pk),
|
||||
lambda: self.positions.count(),
|
||||
60
|
||||
)
|
||||
|
||||
def touch(self):
|
||||
self.event.cache.delete('checkin_list_{}_position_count'.format(self.pk))
|
||||
self.event.cache.delete('checkin_list_{}_checkin_count'.format(self.pk))
|
||||
|
||||
@staticmethod
|
||||
def annotate_with_numbers(qs, event):
|
||||
"""
|
||||
Modifies a queryset of checkin lists by annotating it with the number of order positions and
|
||||
checkins associated with it.
|
||||
"""
|
||||
# Import here to prevent circular import
|
||||
from . import Order, OrderPosition, Item
|
||||
|
||||
# This is the mother of all subqueries. Sorry. I try to explain it, at least?
|
||||
# First, we prepare a subquery that for every check-in that belongs to a paid-order
|
||||
# position and to the list in question. Then, we check that it also belongs to the
|
||||
# correct subevent (just to be sure) and aggregate over lists (so, over everything,
|
||||
# since we filtered by lists).
|
||||
cqs_paid = Checkin.objects.filter(
|
||||
position__order__event=event,
|
||||
position__order__status=Order.STATUS_PAID,
|
||||
list=OuterRef('pk')
|
||||
).filter(
|
||||
# This assumes that in an event with subevents, *all* positions have subevents
|
||||
# and *all* checkin lists have a subevent assigned
|
||||
Q(position__subevent=OuterRef('subevent'))
|
||||
| (Q(position__subevent__isnull=True))
|
||||
).order_by().values('list').annotate(
|
||||
c=Count('*')
|
||||
).values('c')
|
||||
cqs_paid_and_pending = Checkin.objects.filter(
|
||||
position__order__event=event,
|
||||
position__order__status__in=[Order.STATUS_PAID, Order.STATUS_PENDING],
|
||||
list=OuterRef('pk')
|
||||
).filter(
|
||||
# This assumes that in an event with subevents, *all* positions have subevents
|
||||
# and *all* checkin lists have a subevent assigned
|
||||
Q(position__subevent=OuterRef('subevent'))
|
||||
| (Q(position__subevent__isnull=True))
|
||||
).order_by().values('list').annotate(
|
||||
c=Count('*')
|
||||
).values('c')
|
||||
|
||||
# Now for the hard part: getting all order positions that contribute to this list. This
|
||||
# requires us to use TWO subqueries. The first one, pqs_all, will only be used for check-in
|
||||
# lists that contain all the products of the event. This is the simpler one, it basically
|
||||
# looks like the check-in counter above.
|
||||
pqs_all_paid = OrderPosition.objects.filter(
|
||||
order__event=event,
|
||||
order__status=Order.STATUS_PAID,
|
||||
).filter(
|
||||
# This assumes that in an event with subevents, *all* positions have subevents
|
||||
# and *all* checkin lists have a subevent assigned
|
||||
Q(subevent=OuterRef('subevent'))
|
||||
| (Q(subevent__isnull=True))
|
||||
).order_by().values('order__event').annotate(
|
||||
c=Count('*')
|
||||
).values('c')
|
||||
pqs_all_paid_and_pending = OrderPosition.objects.filter(
|
||||
order__event=event,
|
||||
order__status__in=[Order.STATUS_PAID, Order.STATUS_PENDING]
|
||||
).filter(
|
||||
# This assumes that in an event with subevents, *all* positions have subevents
|
||||
# and *all* checkin lists have a subevent assigned
|
||||
Q(subevent=OuterRef('subevent'))
|
||||
| (Q(subevent__isnull=True))
|
||||
).order_by().values('order__event').annotate(
|
||||
c=Count('*')
|
||||
).values('c')
|
||||
|
||||
# Now we need a subquery for the case of checkin lists that are limited to certain
|
||||
# products. We cannot use OuterRef("limit_products") since that would do a cross-product
|
||||
# with the products table and we'd get duplicate rows in the output with different annotations
|
||||
# on them, which isn't useful at all. Therefore, we need to add a second layer of subqueries
|
||||
# to retrieve all of those items and then check if the item_id is IN this subquery result.
|
||||
pqs_limited_paid = OrderPosition.objects.filter(
|
||||
order__event=event,
|
||||
order__status=Order.STATUS_PAID,
|
||||
item_id__in=Subquery(Item.objects.filter(checkinlist__pk=OuterRef(OuterRef('pk'))).values('pk'))
|
||||
).filter(
|
||||
# This assumes that in an event with subevents, *all* positions have subevents
|
||||
# and *all* checkin lists have a subevent assigned
|
||||
Q(subevent=OuterRef('subevent'))
|
||||
| (Q(subevent__isnull=True))
|
||||
).order_by().values('order__event').annotate(
|
||||
c=Count('*')
|
||||
).values('c')
|
||||
pqs_limited_paid_and_pending = OrderPosition.objects.filter(
|
||||
order__event=event,
|
||||
order__status__in=[Order.STATUS_PAID, Order.STATUS_PENDING],
|
||||
item_id__in=Subquery(Item.objects.filter(checkinlist__pk=OuterRef(OuterRef('pk'))).values('pk'))
|
||||
).filter(
|
||||
# This assumes that in an event with subevents, *all* positions have subevents
|
||||
# and *all* checkin lists have a subevent assigned
|
||||
Q(subevent=OuterRef('subevent'))
|
||||
| (Q(subevent__isnull=True))
|
||||
).order_by().values('order__event').annotate(
|
||||
c=Count('*')
|
||||
).values('c')
|
||||
|
||||
# Finally, we put all of this together. We force empty subquery aggregates to 0 by using Coalesce()
|
||||
# and decide which subquery to use for this row. In the end, we compute an integer percentage in case
|
||||
# we want to display a progress bar.
|
||||
return qs.annotate(
|
||||
checkin_count=Coalesce(
|
||||
Case(
|
||||
When(include_pending=True, then=Subquery(cqs_paid_and_pending, output_field=models.IntegerField())),
|
||||
default=Subquery(cqs_paid, output_field=models.IntegerField()),
|
||||
output_field=models.IntegerField()
|
||||
),
|
||||
0
|
||||
),
|
||||
position_count=Coalesce(
|
||||
Case(
|
||||
When(all_products=True, include_pending=False,
|
||||
then=Subquery(pqs_all_paid, output_field=models.IntegerField())),
|
||||
When(all_products=True, include_pending=True,
|
||||
then=Subquery(pqs_all_paid_and_pending, output_field=models.IntegerField())),
|
||||
When(all_products=False, include_pending=False,
|
||||
then=Subquery(pqs_limited_paid, output_field=models.IntegerField())),
|
||||
default=Subquery(pqs_limited_paid_and_pending, output_field=models.IntegerField()),
|
||||
output_field=models.IntegerField()
|
||||
),
|
||||
0
|
||||
)
|
||||
).annotate(
|
||||
percent=Case(
|
||||
When(position_count__gt=0, then=F('checkin_count') * 100 / F('position_count')),
|
||||
default=0,
|
||||
output_field=models.IntegerField()
|
||||
)
|
||||
)
|
||||
# This is only kept for backwards-compatibility reasons. This method used to precompute .position_count
|
||||
# and .checkin_count through a huge subquery chain, but was dropped for performance reasons.
|
||||
return qs
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
@@ -169,6 +96,7 @@ class Checkin(models.Model):
|
||||
list = models.ForeignKey(
|
||||
'pretixbase.CheckinList', related_name='checkins', on_delete=models.PROTECT,
|
||||
)
|
||||
auto_checked_in = models.BooleanField(default=False)
|
||||
|
||||
objects = ScopedManager(organizer='position__order__event__organizer')
|
||||
|
||||
@@ -182,8 +110,11 @@ class Checkin(models.Model):
|
||||
|
||||
def save(self, **kwargs):
|
||||
self.position.order.touch()
|
||||
self.list.event.cache.delete('checkin_count')
|
||||
self.list.touch()
|
||||
super().save(**kwargs)
|
||||
|
||||
def delete(self, **kwargs):
|
||||
self.position.order.touch()
|
||||
super().delete(**kwargs)
|
||||
self.list.touch()
|
||||
|
||||
@@ -1025,8 +1025,6 @@ class Question(LoggedModel):
|
||||
)
|
||||
ask_during_checkin = models.BooleanField(
|
||||
verbose_name=_('Ask during check-in instead of in the ticket buying process'),
|
||||
help_text=_('This will only work if you handle your check-in with pretixdroid 1.8 or newer or '
|
||||
'pretixdesk 0.2 or newer.'),
|
||||
default=False
|
||||
)
|
||||
hidden = models.BooleanField(
|
||||
|
||||
@@ -31,7 +31,9 @@ from django_scopes import ScopedManager, scopes_disabled
|
||||
from i18nfield.strings import LazyI18nString
|
||||
from jsonfallback.fields import FallbackJSONField
|
||||
|
||||
from pretix.base.banlist import banned
|
||||
from pretix.base.decimal import round_decimal
|
||||
from pretix.base.email import get_email_context
|
||||
from pretix.base.i18n import language
|
||||
from pretix.base.models import User
|
||||
from pretix.base.reldate import RelativeDateWrapper
|
||||
@@ -535,16 +537,30 @@ class Order(LockModel, LoggedModel):
|
||||
# handwriting (2/Z, 4/A, 5/S, 6/G). This allows for better detection e.g. in incoming wire transfers that
|
||||
# might include OCR'd handwritten text
|
||||
charset = list('ABCDEFGHJKLMNPQRSTUVWXYZ3789')
|
||||
iteration = 0
|
||||
length = settings.ENTROPY['order_code']
|
||||
while True:
|
||||
code = get_random_string(length=settings.ENTROPY['order_code'], allowed_chars=charset)
|
||||
code = get_random_string(length=length, allowed_chars=charset)
|
||||
iteration += 1
|
||||
|
||||
if banned(code):
|
||||
continue
|
||||
|
||||
if self.testmode:
|
||||
# Subtle way to recognize test orders while debugging: They all contain a 0 at the second place,
|
||||
# even though zeros are not used outside test mode.
|
||||
code = code[0] + "0" + code[2:]
|
||||
|
||||
if not Order.objects.filter(event__organizer=self.event.organizer, code=code).exists():
|
||||
self.code = code
|
||||
return
|
||||
|
||||
if iteration > 20:
|
||||
# Safeguard: If we don't find an unused and non-blacklisted code within 20 iterations, we increase
|
||||
# the length.
|
||||
length += 1
|
||||
iteration = 0
|
||||
|
||||
@property
|
||||
def can_modify_answers(self) -> bool:
|
||||
"""
|
||||
@@ -757,26 +773,9 @@ class Order(LockModel, LoggedModel):
|
||||
)
|
||||
|
||||
def resend_link(self, user=None, auth=None):
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
|
||||
with language(self.locale):
|
||||
try:
|
||||
invoice_name = self.invoice_address.name
|
||||
invoice_company = self.invoice_address.company
|
||||
except InvoiceAddress.DoesNotExist:
|
||||
invoice_name = ""
|
||||
invoice_company = ""
|
||||
email_template = self.event.settings.mail_text_resend_link
|
||||
email_context = {
|
||||
'event': self.event.name,
|
||||
'url': build_absolute_uri(self.event, 'presale:event.order.open', kwargs={
|
||||
'order': self.code,
|
||||
'secret': self.secret,
|
||||
'hash': self.email_confirm_hash()
|
||||
}),
|
||||
'invoice_name': invoice_name,
|
||||
'invoice_company': invoice_company,
|
||||
}
|
||||
email_context = get_email_context(event=self.event, order=self)
|
||||
email_subject = _('Your order: %(code)s') % {'code': self.code}
|
||||
self.send_mail(
|
||||
email_subject, email_template, email_context,
|
||||
@@ -1313,24 +1312,10 @@ class OrderPayment(models.Model):
|
||||
|
||||
def _send_paid_mail_attendee(self, position, user):
|
||||
from pretix.base.services.mail import SendMailException
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
|
||||
with language(self.order.locale):
|
||||
name_scheme = PERSON_NAME_SCHEMES[self.order.event.settings.name_scheme]
|
||||
email_template = self.order.event.settings.mail_text_order_paid_attendee
|
||||
email_context = {
|
||||
'event': self.order.event.name,
|
||||
'downloads': self.order.event.settings.get('ticket_download', as_type=bool),
|
||||
'url': build_absolute_uri(self.order.event, 'presale:event.order.position', kwargs={
|
||||
'order': self.order.code,
|
||||
'secret': position.web_secret,
|
||||
'position': position.positionid
|
||||
}),
|
||||
'attendee_name': position.attendee_name,
|
||||
}
|
||||
for f, l, w in name_scheme['fields']:
|
||||
email_context['attendee_name_%s' % f] = position.attendee_name_parts.get(f, '')
|
||||
|
||||
email_context = get_email_context(event=self.order.event, order=self.order, position=position)
|
||||
email_subject = _('Event registration confirmed: %(code)s') % {'code': self.order.code}
|
||||
try:
|
||||
self.order.send_mail(
|
||||
@@ -1344,28 +1329,10 @@ class OrderPayment(models.Model):
|
||||
|
||||
def _send_paid_mail(self, invoice, user, mail_text):
|
||||
from pretix.base.services.mail import SendMailException
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
|
||||
with language(self.order.locale):
|
||||
try:
|
||||
invoice_name = self.order.invoice_address.name
|
||||
invoice_company = self.order.invoice_address.company
|
||||
except InvoiceAddress.DoesNotExist:
|
||||
invoice_name = ""
|
||||
invoice_company = ""
|
||||
email_template = self.order.event.settings.mail_text_order_paid
|
||||
email_context = {
|
||||
'event': self.order.event.name,
|
||||
'url': build_absolute_uri(self.order.event, 'presale:event.order.open', kwargs={
|
||||
'order': self.order.code,
|
||||
'secret': self.order.secret,
|
||||
'hash': self.order.email_confirm_hash()
|
||||
}),
|
||||
'downloads': self.order.event.settings.get('ticket_download', as_type=bool),
|
||||
'invoice_name': invoice_name,
|
||||
'invoice_company': invoice_company,
|
||||
'payment_info': mail_text
|
||||
}
|
||||
email_context = get_email_context(event=self.order.event, order=self.order, payment_info=mail_text)
|
||||
email_subject = _('Payment received for your order: %(code)s') % {'code': self.order.code}
|
||||
try:
|
||||
self.order.send_mail(
|
||||
@@ -1914,25 +1881,26 @@ class OrderPosition(AbstractPosition):
|
||||
"""
|
||||
from pretix.base.services.mail import SendMailException, mail, render_mail
|
||||
|
||||
if not self.email:
|
||||
if not self.attendee_email:
|
||||
return
|
||||
|
||||
for k, v in self.event.meta_data.items():
|
||||
context['meta_' + k] = v
|
||||
|
||||
with language(self.locale):
|
||||
recipient = self.email
|
||||
with language(self.order.locale):
|
||||
recipient = self.attendee_email
|
||||
try:
|
||||
email_content = render_mail(template, context)
|
||||
mail(
|
||||
recipient, subject, template, context,
|
||||
self.event, self.locale, self, headers, sender,
|
||||
self.event, self.order.locale, order=self.order, headers=headers, sender=sender,
|
||||
position=self,
|
||||
invoices=invoices, attach_tickets=attach_tickets
|
||||
)
|
||||
except SendMailException:
|
||||
raise
|
||||
else:
|
||||
self.log_action(
|
||||
self.order.log_action(
|
||||
log_entry_type,
|
||||
user=user,
|
||||
auth=auth,
|
||||
@@ -1945,6 +1913,18 @@ class OrderPosition(AbstractPosition):
|
||||
}
|
||||
)
|
||||
|
||||
def resend_link(self, user=None, auth=None):
|
||||
|
||||
with language(self.order.locale):
|
||||
email_template = self.event.settings.mail_text_resend_link
|
||||
email_context = get_email_context(event=self.order.event, order=self.order, position=self)
|
||||
email_subject = _('Your event registration: %(code)s') % {'code': self.order.code}
|
||||
self.send_mail(
|
||||
email_subject, email_template, email_context,
|
||||
'pretix.event.order.email.resend', user=user, auth=auth,
|
||||
attach_tickets=True
|
||||
)
|
||||
|
||||
|
||||
class CartPosition(AbstractPosition):
|
||||
"""
|
||||
|
||||
@@ -10,6 +10,7 @@ from django.utils.timezone import now
|
||||
from django.utils.translation import pgettext_lazy, ugettext_lazy as _
|
||||
from django_scopes import ScopedManager, scopes_disabled
|
||||
|
||||
from pretix.base.banlist import banned
|
||||
from pretix.base.models import SeatCategoryMapping
|
||||
|
||||
from ..decimal import round_decimal
|
||||
@@ -21,9 +22,12 @@ from .orders import Order
|
||||
|
||||
def _generate_random_code(prefix=None):
|
||||
charset = list('ABCDEFGHKLMNPQRSTUVWXYZ23456789')
|
||||
rnd = None
|
||||
while not rnd or banned(rnd):
|
||||
rnd = get_random_string(length=settings.ENTROPY['voucher_code'], allowed_chars=charset)
|
||||
if prefix:
|
||||
return prefix + get_random_string(length=settings.ENTROPY['voucher_code'], allowed_chars=charset)
|
||||
return get_random_string(length=settings.ENTROPY['voucher_code'], allowed_chars=charset)
|
||||
return prefix + rnd
|
||||
return rnd
|
||||
|
||||
|
||||
@scopes_disabled()
|
||||
|
||||
@@ -6,10 +6,10 @@ from django.utils.timezone import now
|
||||
from django.utils.translation import pgettext_lazy, ugettext_lazy as _
|
||||
from django_scopes import ScopedManager
|
||||
|
||||
from pretix.base.email import get_email_context
|
||||
from pretix.base.i18n import language
|
||||
from pretix.base.models import Voucher
|
||||
from pretix.base.services.mail import mail
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
|
||||
from .base import LoggedModel
|
||||
from .event import Event, SubEvent
|
||||
@@ -130,13 +130,7 @@ class WaitingListEntry(LoggedModel):
|
||||
self.email,
|
||||
_('You have been selected from the waitinglist for {event}').format(event=str(self.event)),
|
||||
self.event.settings.mail_text_waiting_list,
|
||||
{
|
||||
'event': self.event.name,
|
||||
'url': build_absolute_uri(self.event, 'presale:event.redeem') + '?voucher=' + self.voucher.code,
|
||||
'code': self.voucher.code,
|
||||
'product': str(self.item) + (' - ' + str(self.variation) if self.variation else ''),
|
||||
'hours': self.event.settings.waiting_list_hours,
|
||||
},
|
||||
get_email_context(event=self.event, waiting_list_entry=self),
|
||||
self.event,
|
||||
locale=self.locale
|
||||
)
|
||||
|
||||
@@ -531,7 +531,7 @@ class BasePaymentProvider:
|
||||
containing the URL the user will be redirected to. If you are done with your process
|
||||
you should return the user to the order's detail page.
|
||||
|
||||
If the payment is completed, you should call ``payment.confirm()``. Please note that ``this`` might
|
||||
If the payment is completed, you should call ``payment.confirm()``. Please note that this might
|
||||
raise a ``Quota.QuotaExceededException`` if (and only if) the payment term of this order is over and
|
||||
some of the items are sold out. You should use the exception message to display a meaningful error
|
||||
to the user.
|
||||
|
||||
@@ -266,16 +266,28 @@ DEFAULT_VARIABLES = OrderedDict((
|
||||
@receiver(layout_text_variables, dispatch_uid="pretix_base_layout_text_variables_questions")
|
||||
def variables_from_questions(sender, *args, **kwargs):
|
||||
def get_answer(op, order, event, question_id):
|
||||
try:
|
||||
if 'answers' in getattr(op, '_prefetched_objects_cache', {}):
|
||||
a = [a for a in op.answers.all() if a.question_id == question_id][0]
|
||||
a = None
|
||||
if op.addon_to:
|
||||
if 'answers' in getattr(op.addon_to, '_prefetched_objects_cache', {}):
|
||||
try:
|
||||
a = [a for a in op.addon_to.answers.all() if a.question_id == question_id][0]
|
||||
except IndexError:
|
||||
pass
|
||||
else:
|
||||
a = op.answers.filter(question_id=question_id).first()
|
||||
if not a:
|
||||
return ""
|
||||
return str(a).replace("\n", "<br/>\n")
|
||||
except IndexError:
|
||||
a = op.addon_to.answers.filter(question_id=question_id).first()
|
||||
|
||||
if 'answers' in getattr(op, '_prefetched_objects_cache', {}):
|
||||
try:
|
||||
a = [a for a in op.answers.all() if a.question_id == question_id][0]
|
||||
except IndexError:
|
||||
pass
|
||||
else:
|
||||
a = op.answers.filter(question_id=question_id).first()
|
||||
|
||||
if not a:
|
||||
return ""
|
||||
else:
|
||||
return str(a).replace("\n", "<br/>\n")
|
||||
|
||||
d = {}
|
||||
for q in sender.questions.all():
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
from django.db import transaction
|
||||
from django.db.models import Prefetch
|
||||
from django.dispatch import receiver
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from pretix.base.models import (
|
||||
Checkin, CheckinList, Order, OrderPosition, Question, QuestionOption,
|
||||
)
|
||||
from pretix.base.signals import order_placed
|
||||
|
||||
|
||||
class CheckInError(Exception):
|
||||
@@ -155,3 +157,18 @@ def perform_checkin(op: OrderPosition, clist: CheckinList, given_answers: dict,
|
||||
'datetime': dt,
|
||||
'list': clist.pk
|
||||
}, user=user, auth=auth)
|
||||
|
||||
|
||||
@receiver(order_placed, dispatch_uid="autocheckin_order_placed")
|
||||
def order_placed(sender, **kwargs):
|
||||
order = kwargs['order']
|
||||
event = sender
|
||||
|
||||
cls = list(event.checkin_lists.filter(auto_checkin_sales_channels__contains=order.sales_channel).prefetch_related(
|
||||
'limit_products'))
|
||||
if not cls:
|
||||
return
|
||||
for op in order.positions.all():
|
||||
for cl in cls:
|
||||
if cl.all_products or op.item_id in {i.pk for i in cl.limit_products.all()}:
|
||||
Checkin.objects.create(position=op, list=cl, auto_checked_in=True)
|
||||
|
||||
@@ -378,7 +378,7 @@ def replace_images_with_cid_paths(body_html):
|
||||
def attach_cid_images(msg, cid_images, verify_ssl=True):
|
||||
if cid_images and len(cid_images) > 0:
|
||||
|
||||
msg.mixed_subtype = 'related'
|
||||
msg.mixed_subtype = 'mixed'
|
||||
for key, image in enumerate(cid_images):
|
||||
cid = 'image_%s' % key
|
||||
try:
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import inspect
|
||||
import json
|
||||
import logging
|
||||
from collections import Counter, namedtuple
|
||||
@@ -6,23 +5,20 @@ from datetime import datetime, time, timedelta
|
||||
from decimal import Decimal
|
||||
from typing import List, Optional
|
||||
|
||||
import pytz
|
||||
from celery.exceptions import MaxRetriesExceededError
|
||||
from django.conf import settings
|
||||
from django.db import transaction
|
||||
from django.db.models import Exists, F, Max, OuterRef, Q, Sum
|
||||
from django.db.models.functions import Greatest
|
||||
from django.dispatch import receiver
|
||||
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 ugettext as _
|
||||
from django_scopes import scopes_disabled
|
||||
|
||||
from pretix.api.models import OAuthApplication
|
||||
from pretix.base.i18n import (
|
||||
LazyCurrencyNumber, LazyDate, LazyLocaleException, LazyNumber, language,
|
||||
)
|
||||
from pretix.base.email import get_email_context
|
||||
from pretix.base.i18n import LazyLocaleException, language
|
||||
from pretix.base.models import (
|
||||
CartPosition, Device, Event, Item, ItemVariation, Order, OrderPayment,
|
||||
OrderPosition, Quota, Seat, SeatCategoryMapping, User, Voucher,
|
||||
@@ -45,7 +41,6 @@ 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 ProfiledEventTask, ProfiledTask
|
||||
from pretix.base.settings import PERSON_NAME_SCHEMES
|
||||
from pretix.base.signals import (
|
||||
allow_ticket_download, order_approved, order_canceled, order_changed,
|
||||
order_denied, order_expired, order_fee_calculation, order_placed,
|
||||
@@ -53,7 +48,6 @@ from pretix.base.signals import (
|
||||
)
|
||||
from pretix.celery_app import app
|
||||
from pretix.helpers.models import modelcopy
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
|
||||
error_messages = {
|
||||
'unavailable': _('Some of the products you selected were no longer available. '
|
||||
@@ -206,13 +200,6 @@ def approve_order(order, user=None, send_mail: bool=True, auth=None, force=False
|
||||
# send_mail will trigger PDF generation later
|
||||
|
||||
if send_mail:
|
||||
try:
|
||||
invoice_name = order.invoice_address.name
|
||||
invoice_company = order.invoice_address.company
|
||||
except InvoiceAddress.DoesNotExist:
|
||||
invoice_name = ""
|
||||
invoice_company = ""
|
||||
|
||||
with language(order.locale):
|
||||
if order.total == Decimal('0.00'):
|
||||
email_template = order.event.settings.mail_text_order_free
|
||||
@@ -221,20 +208,7 @@ def approve_order(order, user=None, send_mail: bool=True, auth=None, force=False
|
||||
email_template = order.event.settings.mail_text_order_approved
|
||||
email_subject = _('Order approved and awaiting payment: %(code)s') % {'code': order.code}
|
||||
|
||||
email_context = {
|
||||
'total': LazyNumber(order.total),
|
||||
'currency': order.event.currency,
|
||||
'total_with_currency': LazyCurrencyNumber(order.total, order.event.currency),
|
||||
'date': LazyDate(order.expires),
|
||||
'event': order.event.name,
|
||||
'url': build_absolute_uri(order.event, 'presale:event.order.open', kwargs={
|
||||
'order': order.code,
|
||||
'secret': order.secret,
|
||||
'hash': order.email_confirm_hash()
|
||||
}),
|
||||
'invoice_name': invoice_name,
|
||||
'invoice_company': invoice_company,
|
||||
}
|
||||
email_context = get_email_context(event=order.event, order=order)
|
||||
try:
|
||||
order.send_mail(
|
||||
email_subject, email_template, email_context,
|
||||
@@ -275,28 +249,8 @@ def deny_order(order, comment='', user=None, send_mail: bool=True, auth=None):
|
||||
order_denied.send(order.event, order=order)
|
||||
|
||||
if send_mail:
|
||||
try:
|
||||
invoice_name = order.invoice_address.name
|
||||
invoice_company = order.invoice_address.company
|
||||
except InvoiceAddress.DoesNotExist:
|
||||
invoice_name = ""
|
||||
invoice_company = ""
|
||||
email_template = order.event.settings.mail_text_order_denied
|
||||
email_context = {
|
||||
'total': LazyNumber(order.total),
|
||||
'currency': order.event.currency,
|
||||
'total_with_currency': LazyCurrencyNumber(order.total, order.event.currency),
|
||||
'date': LazyDate(order.expires),
|
||||
'event': order.event.name,
|
||||
'url': build_absolute_uri(order.event, 'presale:event.order.open', kwargs={
|
||||
'order': order.code,
|
||||
'secret': order.secret,
|
||||
'hash': order.email_confirm_hash()
|
||||
}),
|
||||
'comment': comment,
|
||||
'invoice_name': invoice_name,
|
||||
'invoice_company': invoice_company,
|
||||
}
|
||||
email_context = get_email_context(event=order.event, order=order, comment=comment)
|
||||
with language(order.locale):
|
||||
email_subject = _('Order denied: %(code)s') % {'code': order.code}
|
||||
try:
|
||||
@@ -379,16 +333,8 @@ def _cancel_order(order, user=None, send_mail: bool=True, api_token=None, device
|
||||
|
||||
if send_mail:
|
||||
email_template = order.event.settings.mail_text_order_canceled
|
||||
email_context = {
|
||||
'event': order.event.name,
|
||||
'code': order.code,
|
||||
'url': build_absolute_uri(order.event, 'presale:event.order.open', kwargs={
|
||||
'order': order.code,
|
||||
'secret': order.secret,
|
||||
'hash': order.email_confirm_hash()
|
||||
})
|
||||
}
|
||||
with language(order.locale):
|
||||
email_context = get_email_context(event=order.event, order=order)
|
||||
email_subject = _('Order canceled: %(code)s') % {'code': order.code}
|
||||
try:
|
||||
order.send_mail(
|
||||
@@ -682,36 +628,7 @@ def _create_order(event: Event, email: str, positions: List[CartPosition], now_d
|
||||
|
||||
def _order_placed_email(event: Event, order: Order, pprov: BasePaymentProvider, email_template, log_entry: str,
|
||||
invoice, payment: OrderPayment):
|
||||
try:
|
||||
invoice_name = order.invoice_address.name
|
||||
invoice_company = order.invoice_address.company
|
||||
except InvoiceAddress.DoesNotExist:
|
||||
invoice_name = ""
|
||||
invoice_company = ""
|
||||
|
||||
if pprov:
|
||||
if 'payment' in inspect.signature(pprov.order_pending_mail_render).parameters:
|
||||
payment_info = str(pprov.order_pending_mail_render(order, payment))
|
||||
else:
|
||||
payment_info = str(pprov.order_pending_mail_render(order))
|
||||
else:
|
||||
payment_info = None
|
||||
|
||||
email_context = {
|
||||
'total': LazyNumber(order.total),
|
||||
'currency': event.currency,
|
||||
'total_with_currency': LazyCurrencyNumber(order.total, event.currency),
|
||||
'date': LazyDate(order.expires),
|
||||
'event': event.name,
|
||||
'url': build_absolute_uri(event, 'presale:event.order.open', kwargs={
|
||||
'order': order.code,
|
||||
'secret': order.secret,
|
||||
'hash': order.email_confirm_hash()
|
||||
}),
|
||||
'payment_info': payment_info,
|
||||
'invoice_name': invoice_name,
|
||||
'invoice_company': invoice_company,
|
||||
}
|
||||
email_context = get_email_context(event=event, order=order, payment=payment if pprov else None)
|
||||
email_subject = _('Your order: %(code)s') % {'code': order.code}
|
||||
try:
|
||||
order.send_mail(
|
||||
@@ -725,19 +642,7 @@ def _order_placed_email(event: Event, order: Order, pprov: BasePaymentProvider,
|
||||
|
||||
|
||||
def _order_placed_email_attendee(event: Event, order: Order, position: OrderPosition, email_template, log_entry: str):
|
||||
name_scheme = PERSON_NAME_SCHEMES[event.settings.name_scheme]
|
||||
email_context = {
|
||||
'event': event.name,
|
||||
'url': build_absolute_uri(event, 'presale:event.order.position', kwargs={
|
||||
'order': order.code,
|
||||
'secret': position.web_secret,
|
||||
'position': position.positionid
|
||||
}),
|
||||
'attendee_name': position.attendee_name,
|
||||
}
|
||||
for f, l, w in name_scheme['fields']:
|
||||
email_context['attendee_name_%s' % f] = position.attendee_name_parts.get(f, '')
|
||||
|
||||
email_context = get_email_context(event=event, order=order, position=position)
|
||||
email_subject = _('Your event registration: %(code)s') % {'code': order.code}
|
||||
|
||||
try:
|
||||
@@ -884,29 +789,12 @@ def send_expiry_warnings(sender, **kwargs):
|
||||
eventcache[o.event.pk] = eventsettings
|
||||
|
||||
days = eventsettings.get('mail_days_order_expire_warning', as_type=int)
|
||||
tz = pytz.timezone(eventsettings.get('timezone', settings.TIME_ZONE))
|
||||
if days and (o.expires - today).days <= days:
|
||||
with language(o.locale):
|
||||
o.expiry_reminder_sent = True
|
||||
o.save(update_fields=['expiry_reminder_sent'])
|
||||
try:
|
||||
invoice_name = o.invoice_address.name
|
||||
invoice_company = o.invoice_address.company
|
||||
except InvoiceAddress.DoesNotExist:
|
||||
invoice_name = ""
|
||||
invoice_company = ""
|
||||
email_template = eventsettings.mail_text_order_expire_warning
|
||||
email_context = {
|
||||
'event': o.event.name,
|
||||
'url': build_absolute_uri(o.event, 'presale:event.order.open', kwargs={
|
||||
'order': o.code,
|
||||
'secret': o.secret,
|
||||
'hash': o.email_confirm_hash()
|
||||
}),
|
||||
'expire_date': date_format(o.expires.astimezone(tz), 'SHORT_DATE_FORMAT'),
|
||||
'invoice_name': invoice_name,
|
||||
'invoice_company': invoice_company,
|
||||
}
|
||||
email_context = get_email_context(event=o.event, order=o)
|
||||
if eventsettings.payment_term_expire_automatically:
|
||||
email_subject = _('Your order is about to expire: %(code)s') % {'code': o.code}
|
||||
else:
|
||||
@@ -949,14 +837,7 @@ def send_download_reminders(sender, **kwargs):
|
||||
o.download_reminder_sent = True
|
||||
o.save(update_fields=['download_reminder_sent'])
|
||||
email_template = e.settings.mail_text_download_reminder
|
||||
email_context = {
|
||||
'event': o.event.name,
|
||||
'url': build_absolute_uri(o.event, 'presale:event.order.open', kwargs={
|
||||
'order': o.code,
|
||||
'secret': o.secret,
|
||||
'hash': o.email_confirm_hash()
|
||||
}),
|
||||
}
|
||||
email_context = get_email_context(event=e, order=o)
|
||||
email_subject = _('Your ticket is ready for download: %(code)s') % {'code': o.code}
|
||||
try:
|
||||
o.send_mail(
|
||||
@@ -968,21 +849,10 @@ def send_download_reminders(sender, **kwargs):
|
||||
logger.exception('Reminder email could not be sent')
|
||||
|
||||
if e.settings.mail_send_download_reminder_attendee:
|
||||
name_scheme = PERSON_NAME_SCHEMES[e.settings.name_scheme]
|
||||
for p in o.positions.all():
|
||||
if p.addon_to_id is None and p.attendee_email and p.attendee_email != o.email:
|
||||
email_template = e.settings.mail_text_download_reminder_attendee
|
||||
email_context = {
|
||||
'event': e.name,
|
||||
'url': build_absolute_uri(e, 'presale:event.order.position', kwargs={
|
||||
'order': o.code,
|
||||
'secret': p.web_secret,
|
||||
'position': p.positionid
|
||||
}),
|
||||
'attendee_name': p.attendee_name,
|
||||
}
|
||||
for f, l, w in name_scheme['fields']:
|
||||
email_context['attendee_name_%s' % f] = p.attendee_name_parts.get(f, '')
|
||||
email_context = get_email_context(event=e, order=o, position=p)
|
||||
try:
|
||||
o.send_mail(
|
||||
email_subject, email_template, email_context,
|
||||
@@ -995,23 +865,8 @@ def send_download_reminders(sender, **kwargs):
|
||||
|
||||
def notify_user_changed_order(order, user=None, auth=None):
|
||||
with language(order.locale):
|
||||
try:
|
||||
invoice_name = order.invoice_address.name
|
||||
invoice_company = order.invoice_address.company
|
||||
except InvoiceAddress.DoesNotExist:
|
||||
invoice_name = ""
|
||||
invoice_company = ""
|
||||
email_template = order.event.settings.mail_text_order_changed
|
||||
email_context = {
|
||||
'event': order.event.name,
|
||||
'url': build_absolute_uri(order.event, 'presale:event.order.open', kwargs={
|
||||
'order': order.code,
|
||||
'secret': order.secret,
|
||||
'hash': order.email_confirm_hash()
|
||||
}),
|
||||
'invoice_name': invoice_name,
|
||||
'invoice_company': invoice_company,
|
||||
}
|
||||
email_context = get_email_context(event=order.event, order=order)
|
||||
email_subject = _('Your order has been changed: %(code)s') % {'code': order.code}
|
||||
try:
|
||||
order.send_mail(
|
||||
@@ -1049,12 +904,13 @@ class OrderChangeManager:
|
||||
SplitOperation = namedtuple('SplitOperation', ('position',))
|
||||
RegenerateSecretOperation = namedtuple('RegenerateSecretOperation', ('position',))
|
||||
|
||||
def __init__(self, order: Order, user=None, auth=None, notify=True):
|
||||
def __init__(self, order: Order, user=None, auth=None, notify=True, reissue_invoice=True):
|
||||
self.order = order
|
||||
self.user = user
|
||||
self.auth = auth
|
||||
self.event = order.event
|
||||
self.split_order = None
|
||||
self.reissue_invoice = reissue_invoice
|
||||
self._committed = False
|
||||
self._totaldiff = 0
|
||||
self._quotadiff = Counter()
|
||||
@@ -1537,7 +1393,7 @@ class OrderChangeManager:
|
||||
|
||||
def _reissue_invoice(self):
|
||||
i = self.order.invoices.filter(is_cancellation=False).last()
|
||||
if i and self._invoice_dirty:
|
||||
if self.reissue_invoice and i and self._invoice_dirty:
|
||||
generate_cancellation(i)
|
||||
generate_invoice(self.order)
|
||||
|
||||
@@ -1684,7 +1540,9 @@ def cancel_order(self, order: int, user: int=None, send_mail: bool=True, api_tok
|
||||
raise OrderError(error_messages['busy'])
|
||||
|
||||
|
||||
def change_payment_provider(order: Order, payment_provider, amount=None, new_payment=None):
|
||||
def change_payment_provider(order: Order, payment_provider, amount=None, new_payment=None, create_log=True,
|
||||
recreate_invoices=True):
|
||||
oldtotal = order.total
|
||||
e = OrderPayment.objects.filter(fee=OuterRef('pk'), state__in=(OrderPayment.PAYMENT_STATE_CONFIRMED,
|
||||
OrderPayment.PAYMENT_STATE_REFUNDED))
|
||||
open_fees = list(
|
||||
@@ -1730,4 +1588,30 @@ def change_payment_provider(order: Order, payment_provider, amount=None, new_pay
|
||||
|
||||
order.total = (order.positions.aggregate(sum=Sum('price'))['sum'] or 0) + (order.fees.aggregate(sum=Sum('value'))['sum'] or 0)
|
||||
order.save(update_fields=['total'])
|
||||
return old_fee, new_fee, fee
|
||||
|
||||
if not new_payment:
|
||||
new_payment = order.payments.create(
|
||||
state=OrderPayment.PAYMENT_STATE_CREATED,
|
||||
provider=payment_provider.identifier,
|
||||
amount=order.pending_sum,
|
||||
fee=fee
|
||||
)
|
||||
if create_log and new_payment:
|
||||
order.log_action(
|
||||
'pretix.event.order.payment.changed' if open_payment else 'pretix.event.order.payment.started',
|
||||
{
|
||||
'fee': new_fee,
|
||||
'old_fee': old_fee,
|
||||
'provider': payment_provider.identifier,
|
||||
'payment': new_payment.pk,
|
||||
'local_id': new_payment.local_id,
|
||||
}
|
||||
)
|
||||
|
||||
if recreate_invoices:
|
||||
i = order.invoices.filter(is_cancellation=False).last()
|
||||
if i and order.total != oldtotal:
|
||||
generate_cancellation(i)
|
||||
generate_invoice(order)
|
||||
|
||||
return old_fee, new_fee, fee, new_payment
|
||||
|
||||
@@ -106,3 +106,15 @@ class TransactionAwareTask(ProfiledTask):
|
||||
transaction.on_commit(
|
||||
lambda: super(TransactionAwareTask, self).apply_async(*args, **kwargs)
|
||||
)
|
||||
|
||||
|
||||
class TransactionAwareProfiledEventTask(ProfiledEventTask):
|
||||
|
||||
def apply_async(self, *args, **kwargs):
|
||||
"""
|
||||
Unlike the default task in celery, this task does not return an async
|
||||
result
|
||||
"""
|
||||
transaction.on_commit(
|
||||
lambda: super(TransactionAwareProfiledEventTask, self).apply_async(*args, **kwargs)
|
||||
)
|
||||
|
||||
@@ -386,7 +386,7 @@ Your {event} team"""))
|
||||
'default': LazyI18nString.from_gettext(ugettext_noop("""Hello,
|
||||
|
||||
we successfully received your order for {event} with a total value
|
||||
of {total_with_currency}. Please complete your payment before {date}.
|
||||
of {total_with_currency}. Please complete your payment before {expire_date}.
|
||||
|
||||
{payment_info}
|
||||
|
||||
@@ -514,7 +514,7 @@ Your {event} team"""))
|
||||
we approved your order for {event} and will be happy to welcome you
|
||||
at our event.
|
||||
|
||||
Please continue by paying for your order before {date}.
|
||||
Please continue by paying for your order before {expire_date}.
|
||||
|
||||
You can select a payment method and perform the payment here:
|
||||
|
||||
|
||||
@@ -186,6 +186,16 @@ subclass of pretix.base.payment.BasePaymentProvider or a list of these
|
||||
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
|
||||
"""
|
||||
|
||||
register_mail_placeholders = EventPluginSignal(
|
||||
providing_args=[]
|
||||
)
|
||||
"""
|
||||
This signal is sent out to get all known email text placeholders. Receivers should return
|
||||
an instance of a subclass of pretix.base.email.BaseMailTextPlaceholder or a list of these.
|
||||
|
||||
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
|
||||
"""
|
||||
|
||||
register_html_mail_renderers = EventPluginSignal(
|
||||
providing_args=[]
|
||||
)
|
||||
|
||||
@@ -14,6 +14,8 @@ def money_filter(value: Decimal, arg='', hide_currency=False):
|
||||
if isinstance(value, float) or isinstance(value, int):
|
||||
value = Decimal(value)
|
||||
if not isinstance(value, Decimal):
|
||||
if value == '':
|
||||
return value
|
||||
raise TypeError("Invalid data type passed to money filter: %r" % type(value))
|
||||
if not arg:
|
||||
raise ValueError("No currency passed.")
|
||||
|
||||
@@ -54,7 +54,7 @@ ALLOWED_ATTRIBUTES = {
|
||||
'td': ['width', 'align'],
|
||||
'div': ['class'],
|
||||
'p': ['class'],
|
||||
'span': ['class'],
|
||||
'span': ['class', 'title'],
|
||||
# Update doc/user/markdown.rst if you change this!
|
||||
}
|
||||
|
||||
|
||||
@@ -99,11 +99,6 @@ def contextprocessor(request):
|
||||
ctx['js_locale'] = get_moment_locale()
|
||||
ctx['select2locale'] = get_language()[:2]
|
||||
|
||||
if settings.DEBUG and 'runserver' not in sys.argv:
|
||||
ctx['debug_warning'] = True
|
||||
elif 'runserver' in sys.argv:
|
||||
ctx['development_warning'] = True
|
||||
|
||||
ctx['warning_update_available'] = False
|
||||
ctx['warning_update_check_active'] = False
|
||||
gs = GlobalSettingsObject()
|
||||
|
||||
@@ -5,6 +5,7 @@ from django_scopes.forms import (
|
||||
SafeModelChoiceField, SafeModelMultipleChoiceField,
|
||||
)
|
||||
|
||||
from pretix.base.channels import get_all_sales_channels
|
||||
from pretix.base.models.checkin import CheckinList
|
||||
from pretix.control.forms.widgets import Select2
|
||||
|
||||
@@ -15,6 +16,16 @@ class CheckinListForm(forms.ModelForm):
|
||||
kwargs.pop('locales', None)
|
||||
super().__init__(**kwargs)
|
||||
self.fields['limit_products'].queryset = self.event.items.all()
|
||||
self.fields['auto_checkin_sales_channels'] = forms.MultipleChoiceField(
|
||||
label=self.fields['auto_checkin_sales_channels'].label,
|
||||
help_text=self.fields['auto_checkin_sales_channels'].help_text,
|
||||
required=self.fields['auto_checkin_sales_channels'].required,
|
||||
choices=(
|
||||
(c.identifier, c.verbose_name) for c in get_all_sales_channels().values()
|
||||
),
|
||||
widget=forms.CheckboxSelectMultiple
|
||||
)
|
||||
|
||||
if self.event.has_subevents:
|
||||
self.fields['subevent'].queryset = self.event.subevents.all()
|
||||
self.fields['subevent'].widget = Select2(
|
||||
@@ -40,12 +51,14 @@ class CheckinListForm(forms.ModelForm):
|
||||
'all_products',
|
||||
'limit_products',
|
||||
'subevent',
|
||||
'include_pending'
|
||||
'include_pending',
|
||||
'auto_checkin_sales_channels'
|
||||
]
|
||||
widgets = {
|
||||
'limit_products': forms.CheckboxSelectMultiple(attrs={
|
||||
'data-inverse-dependency': '<[name$=all_products]'
|
||||
}),
|
||||
'auto_checkin_sales_channels': forms.CheckboxSelectMultiple()
|
||||
}
|
||||
field_classes = {
|
||||
'limit_products': SafeModelMultipleChoiceField,
|
||||
|
||||
@@ -20,6 +20,7 @@ from i18nfield.forms import (
|
||||
from pytz import common_timezones, timezone
|
||||
|
||||
from pretix.base.channels import get_all_sales_channels
|
||||
from pretix.base.email import get_available_placeholders
|
||||
from pretix.base.forms import I18nModelForm, PlaceholderValidator, SettingsForm
|
||||
from pretix.base.models import Event, Organizer, TaxRule
|
||||
from pretix.base.models.event import EventMetaValue, SubEvent
|
||||
@@ -177,7 +178,7 @@ class EventWizardBasicsForm(I18nModelForm):
|
||||
return slug
|
||||
|
||||
|
||||
class EventChoiceField(forms.ModelChoiceField):
|
||||
class EventChoiceMixin:
|
||||
def label_from_instance(self, obj):
|
||||
return mark_safe('{}<br /><span class="text-muted">{} · {}</span>'.format(
|
||||
escape(str(obj)),
|
||||
@@ -186,6 +187,16 @@ class EventChoiceField(forms.ModelChoiceField):
|
||||
))
|
||||
|
||||
|
||||
class EventChoiceField(forms.ModelChoiceField):
|
||||
pass
|
||||
|
||||
|
||||
class SafeEventMultipleChoiceField(EventChoiceMixin, forms.ModelMultipleChoiceField):
|
||||
def __init__(self, queryset, *args, **kwargs):
|
||||
queryset = queryset.model.objects.none()
|
||||
super().__init__(queryset, *args, **kwargs)
|
||||
|
||||
|
||||
class EventWizardCopyForm(forms.Form):
|
||||
|
||||
@staticmethod
|
||||
@@ -992,10 +1003,6 @@ class MailSettingsForm(SettingsForm):
|
||||
label=_("Text sent to order contact address"),
|
||||
required=False,
|
||||
widget=I18nTextarea,
|
||||
help_text=_("Available placeholders: {event}, {total_with_currency}, {total}, {currency}, {date}, "
|
||||
"{payment_info}, {url}, {invoice_name}, {invoice_company}"),
|
||||
validators=[PlaceholderValidator(['{event}', '{total_with_currency}', '{total}', '{currency}', '{date}',
|
||||
'{payment_info}', '{url}', '{invoice_name}', '{invoice_company}'])]
|
||||
)
|
||||
mail_send_order_placed_attendee = forms.BooleanField(
|
||||
label=_("Send an email to attendees"),
|
||||
@@ -1007,16 +1014,12 @@ class MailSettingsForm(SettingsForm):
|
||||
label=_("Text sent to attendees"),
|
||||
required=False,
|
||||
widget=I18nTextarea,
|
||||
help_text=_("Available placeholders: {event}, {url}, {attendee_name}"),
|
||||
validators=[PlaceholderValidator(['{event}', '{url}', '{attendee_name}'])],
|
||||
)
|
||||
|
||||
mail_text_order_paid = I18nFormField(
|
||||
label=_("Text sent to order contact address"),
|
||||
required=False,
|
||||
widget=I18nTextarea,
|
||||
help_text=_("Available placeholders: {event}, {url}, {invoice_name}, {invoice_company}, {payment_info}"),
|
||||
validators=[PlaceholderValidator(['{event}', '{url}', '{invoice_name}', '{invoice_company}', '{payment_info}'])]
|
||||
)
|
||||
mail_send_order_paid_attendee = forms.BooleanField(
|
||||
label=_("Send an email to attendees"),
|
||||
@@ -1028,16 +1031,12 @@ class MailSettingsForm(SettingsForm):
|
||||
label=_("Text sent to attendees"),
|
||||
required=False,
|
||||
widget=I18nTextarea,
|
||||
help_text=_("Available placeholders: {event}, {url}, {attendee_name}"),
|
||||
validators=[PlaceholderValidator(['{event}', '{url}', '{attendee_name}'])],
|
||||
)
|
||||
|
||||
mail_text_order_free = I18nFormField(
|
||||
label=_("Text sent to order contact address"),
|
||||
required=False,
|
||||
widget=I18nTextarea,
|
||||
help_text=_("Available placeholders: {event}, {url}, {invoice_name}, {invoice_company}"),
|
||||
validators=[PlaceholderValidator(['{event}', '{url}', '{invoice_name}', '{invoice_company}'])]
|
||||
)
|
||||
mail_send_order_free_attendee = forms.BooleanField(
|
||||
label=_("Send an email to attendees"),
|
||||
@@ -1049,30 +1048,22 @@ class MailSettingsForm(SettingsForm):
|
||||
label=_("Text sent to attendees"),
|
||||
required=False,
|
||||
widget=I18nTextarea,
|
||||
help_text=_("Available placeholders: {event}, {url}, {attendee_name}"),
|
||||
validators=[PlaceholderValidator(['{event}', '{url}', '{attendee_name}'])],
|
||||
)
|
||||
|
||||
mail_text_order_changed = I18nFormField(
|
||||
label=_("Text"),
|
||||
required=False,
|
||||
widget=I18nTextarea,
|
||||
help_text=_("Available placeholders: {event}, {url}, {invoice_name}, {invoice_company}"),
|
||||
validators=[PlaceholderValidator(['{event}', '{url}', '{invoice_name}', '{invoice_company}'])]
|
||||
)
|
||||
mail_text_resend_link = I18nFormField(
|
||||
label=_("Text (sent by admin)"),
|
||||
required=False,
|
||||
widget=I18nTextarea,
|
||||
help_text=_("Available placeholders: {event}, {url}, {invoice_name}, {invoice_company}"),
|
||||
validators=[PlaceholderValidator(['{event}', '{url}', '{invoice_name}', '{invoice_company}'])]
|
||||
)
|
||||
mail_text_resend_all_links = I18nFormField(
|
||||
label=_("Text (requested by user)"),
|
||||
required=False,
|
||||
widget=I18nTextarea,
|
||||
help_text=_("Available placeholders: {event}, {orders}"),
|
||||
validators=[PlaceholderValidator(['{event}', '{orders}'])]
|
||||
)
|
||||
mail_days_order_expire_warning = forms.IntegerField(
|
||||
label=_("Number of days"),
|
||||
@@ -1085,38 +1076,26 @@ class MailSettingsForm(SettingsForm):
|
||||
label=_("Text"),
|
||||
required=False,
|
||||
widget=I18nTextarea,
|
||||
help_text=_("Available placeholders: {event}, {url}, {expire_date}, {invoice_name}, {invoice_company}"),
|
||||
validators=[PlaceholderValidator(['{event}', '{url}', '{expire_date}', '{invoice_name}', '{invoice_company}'])]
|
||||
)
|
||||
mail_text_waiting_list = I18nFormField(
|
||||
label=_("Text"),
|
||||
required=False,
|
||||
widget=I18nTextarea,
|
||||
help_text=_("Available placeholders: {event}, {url}, {product}, {hours}, {code}"),
|
||||
validators=[PlaceholderValidator(['{event}', '{url}', '{product}', '{hours}', '{code}'])]
|
||||
)
|
||||
mail_text_order_canceled = I18nFormField(
|
||||
label=_("Text"),
|
||||
required=False,
|
||||
widget=I18nTextarea,
|
||||
help_text=_("Available placeholders: {event}, {code}, {url}"),
|
||||
validators=[PlaceholderValidator(['{event}', '{code}', '{url}'])]
|
||||
)
|
||||
mail_text_order_custom_mail = I18nFormField(
|
||||
label=_("Text"),
|
||||
required=False,
|
||||
widget=I18nTextarea,
|
||||
help_text=_("Available placeholders: {expire_date}, {event}, {code}, {date}, {url}, "
|
||||
"{invoice_name}, {invoice_company}"),
|
||||
validators=[PlaceholderValidator(['{expire_date}', '{event}', '{code}', '{date}', '{url}',
|
||||
'{invoice_name}', '{invoice_company}'])]
|
||||
)
|
||||
mail_text_download_reminder = I18nFormField(
|
||||
label=_("Text sent to order contact address"),
|
||||
required=False,
|
||||
widget=I18nTextarea,
|
||||
help_text=_("Available placeholders: {event}, {url}"),
|
||||
validators=[PlaceholderValidator(['{event}', '{url}'])]
|
||||
)
|
||||
mail_send_download_reminder_attendee = forms.BooleanField(
|
||||
label=_("Send an email to attendees"),
|
||||
@@ -1128,8 +1107,6 @@ class MailSettingsForm(SettingsForm):
|
||||
label=_("Text sent to attendees"),
|
||||
required=False,
|
||||
widget=I18nTextarea,
|
||||
help_text=_("Available placeholders: {attendee_name}, {event}, {url}"),
|
||||
validators=[PlaceholderValidator(['{attendee_name}', '{event}', '{url}'])]
|
||||
)
|
||||
mail_days_download_reminder = forms.IntegerField(
|
||||
label=_("Number of days"),
|
||||
@@ -1142,29 +1119,18 @@ class MailSettingsForm(SettingsForm):
|
||||
label=_("Received order"),
|
||||
required=False,
|
||||
widget=I18nTextarea,
|
||||
help_text=_("Available placeholders: {event}, {total_with_currency}, {total}, {currency}, {date}, "
|
||||
"{url}, {invoice_name}, {invoice_company}"),
|
||||
validators=[PlaceholderValidator(['{event}', '{total_with_currency}', '{total}', '{currency}', '{date}',
|
||||
'{url}', '{invoice_name}', '{invoice_company}'])]
|
||||
)
|
||||
mail_text_order_approved = I18nFormField(
|
||||
label=_("Approved order"),
|
||||
required=False,
|
||||
widget=I18nTextarea,
|
||||
help_text=_("This will only be sent out for non-free orders. Free orders will receive the free order "
|
||||
"template from above instead. Available placeholders: {event}, {total_with_currency}, {total}, "
|
||||
"{currency}, {date}, {payment_info}, {url}, {invoice_name}, {invoice_company}"),
|
||||
validators=[PlaceholderValidator(['{event}', '{total_with_currency}', '{total}', '{currency}', '{date}',
|
||||
'{url}', '{invoice_name}', '{invoice_company}'])]
|
||||
"template from above instead."),
|
||||
)
|
||||
mail_text_order_denied = I18nFormField(
|
||||
label=_("Denied order"),
|
||||
required=False,
|
||||
widget=I18nTextarea,
|
||||
help_text=_("Available placeholders: {event}, {total_with_currency}, {total}, {currency}, {date}, "
|
||||
"{comment}, {url}, {invoice_name}, {invoice_company}"),
|
||||
validators=[PlaceholderValidator(['{event}', '{total_with_currency}', '{total}', '{currency}', '{date}',
|
||||
'{comment}', '{url}', '{invoice_name}', '{invoice_company}'])]
|
||||
)
|
||||
smtp_use_custom = forms.BooleanField(
|
||||
label=_("Use custom SMTP server"),
|
||||
@@ -1203,29 +1169,53 @@ class MailSettingsForm(SettingsForm):
|
||||
help_text=_("Commonly enabled on port 465."),
|
||||
required=False
|
||||
)
|
||||
base_context = {
|
||||
'mail_text_order_placed': ['event', 'order', 'payment'],
|
||||
'mail_text_order_placed_attendee': ['event', 'order', 'position'],
|
||||
'mail_text_order_placed_require_approval': ['event', 'order'],
|
||||
'mail_text_order_approved': ['event', 'order'],
|
||||
'mail_text_order_denied': ['event', 'order', 'comment'],
|
||||
'mail_text_order_paid': ['event', 'order', 'payment_info'],
|
||||
'mail_text_order_paid_attendee': ['event', 'order', 'position'],
|
||||
'mail_text_order_free': ['event', 'order'],
|
||||
'mail_text_order_free_attendee': ['event', 'order', 'position'],
|
||||
'mail_text_order_changed': ['event', 'order'],
|
||||
'mail_text_order_canceled': ['event', 'order'],
|
||||
'mail_text_order_expire_warning': ['event', 'order'],
|
||||
'mail_text_order_custom_mail': ['event', 'order'],
|
||||
'mail_text_download_reminder': ['event', 'order'],
|
||||
'mail_text_download_reminder_attendee': ['event', 'order', 'position'],
|
||||
'mail_text_resend_link': ['event', 'order'],
|
||||
'mail_text_waiting_list': ['event', 'waiting_list_entry'],
|
||||
'mail_text_resend_all_links': ['event', 'orders']
|
||||
}
|
||||
|
||||
def _set_field_placeholders(self, fn, base_parameters):
|
||||
phs = [
|
||||
'{%s}' % p
|
||||
for p in sorted(get_available_placeholders(self.event, base_parameters).keys())
|
||||
]
|
||||
ht = _('Available placeholders: {list}').format(
|
||||
list=', '.join(phs)
|
||||
)
|
||||
if self.fields[fn].help_text:
|
||||
self.fields[fn].help_text += ' ' + str(ht)
|
||||
else:
|
||||
self.fields[fn].help_text = ht
|
||||
self.fields[fn].validators.append(
|
||||
PlaceholderValidator(phs)
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
event = kwargs.get('obj')
|
||||
self.event = event = kwargs.get('obj')
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['mail_html_renderer'].choices = [
|
||||
(r.identifier, r.verbose_name) for r in event.get_html_mail_renderers().values()
|
||||
]
|
||||
keys = list(event.meta_data.keys())
|
||||
name_scheme = PERSON_NAME_SCHEMES[event.settings.name_scheme]
|
||||
for k, v in self.base_context.items():
|
||||
self._set_field_placeholders(k, v)
|
||||
|
||||
for k, v in list(self.fields.items()):
|
||||
if k.startswith('mail_text_'):
|
||||
v.help_text = str(v.help_text) + ', ' + ', '.join({
|
||||
'{meta_' + p + '}' for p in keys
|
||||
})
|
||||
v.validators[0].limit_value += ['{meta_' + p + '}' for p in keys]
|
||||
|
||||
if '{attendee_name}' in v.validators[0].limit_value:
|
||||
for f, l, w in name_scheme['fields']:
|
||||
if f == 'full_name':
|
||||
continue
|
||||
v.help_text = str(v.help_text) + ', ' + '{attendee_name_%s}' % f
|
||||
v.validators[0].limit_value += ['{attendee_name_' + f + '}']
|
||||
|
||||
if k.endswith('_attendee') and not event.settings.attendee_emails_asked:
|
||||
# If we don't ask for attendee emails, we can't send them anything and we don't need to clutter
|
||||
# the user interface with it
|
||||
|
||||
@@ -902,6 +902,43 @@ class VoucherFilterForm(FilterForm):
|
||||
return qs
|
||||
|
||||
|
||||
class VoucherTagFilterForm(FilterForm):
|
||||
subevent = forms.ModelChoiceField(
|
||||
label=pgettext_lazy('subevent', 'Date'),
|
||||
queryset=SubEvent.objects.none(),
|
||||
required=False,
|
||||
empty_label=pgettext_lazy('subevent', 'All dates')
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.event = kwargs.pop('event')
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
if self.event.has_subevents:
|
||||
self.fields['subevent'].queryset = self.event.subevents.all()
|
||||
self.fields['subevent'].widget = Select2(
|
||||
attrs={
|
||||
'data-model-select2': 'event',
|
||||
'data-select2-url': reverse('control:event.subevents.select2', kwargs={
|
||||
'event': self.event.slug,
|
||||
'organizer': self.event.organizer.slug,
|
||||
}),
|
||||
'data-placeholder': pgettext_lazy('subevent', 'All dates')
|
||||
}
|
||||
)
|
||||
self.fields['subevent'].widget.choices = self.fields['subevent'].choices
|
||||
elif 'subevent':
|
||||
del self.fields['subevent']
|
||||
|
||||
def filter_qs(self, qs):
|
||||
fdata = self.cleaned_data
|
||||
|
||||
if fdata.get('subevent'):
|
||||
qs = qs.filter(subevent_id=fdata.get('subevent').pk)
|
||||
|
||||
return qs
|
||||
|
||||
|
||||
class RefundFilterForm(FilterForm):
|
||||
provider = forms.ChoiceField(
|
||||
label=_('Payment provider'),
|
||||
|
||||
@@ -125,7 +125,7 @@ class QuotaForm(I18nModelForm):
|
||||
items = kwargs.pop('items', None) or self.event.items.prefetch_related('variations')
|
||||
self.original_instance = modelcopy(self.instance) if self.instance else None
|
||||
initial = kwargs.get('initial', {})
|
||||
if self.instance and self.instance.pk:
|
||||
if self.instance and self.instance.pk and 'itemvars' not in initial:
|
||||
initial['itemvars'] = [str(i.pk) for i in self.instance.items.all()] + [
|
||||
'{}-{}'.format(v.item_id, v.pk) for v in self.instance.variations.all()
|
||||
]
|
||||
|
||||
@@ -8,6 +8,7 @@ from django.urls import reverse
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import pgettext_lazy, ugettext_lazy as _
|
||||
|
||||
from pretix.base.email import get_available_placeholders
|
||||
from pretix.base.forms import I18nModelForm, PlaceholderValidator
|
||||
from pretix.base.forms.widgets import DatePickerWidget
|
||||
from pretix.base.models import (
|
||||
@@ -167,6 +168,15 @@ class OtherOperationsForm(forms.Form):
|
||||
'Use with care and only if you need to. Note that rounding differences might occur in this procedure.'
|
||||
)
|
||||
)
|
||||
reissue_invoice = forms.BooleanField(
|
||||
label=_('Issue a new invoice if required'),
|
||||
required=False,
|
||||
initial=True,
|
||||
help_text=_(
|
||||
'If an invoice exists for this order and this operation would change its contents, the old invoice will '
|
||||
'be cancelled and a new invoice will be issued.'
|
||||
)
|
||||
)
|
||||
notify = forms.BooleanField(
|
||||
label=_('Notify user'),
|
||||
required=False,
|
||||
@@ -186,10 +196,6 @@ class OtherOperationsForm(forms.Form):
|
||||
|
||||
|
||||
class OrderPositionAddForm(forms.Form):
|
||||
do = forms.BooleanField(
|
||||
label=_('Add a new product to the order'),
|
||||
required=False
|
||||
)
|
||||
itemvar = forms.ChoiceField(
|
||||
label=_('Product')
|
||||
)
|
||||
@@ -281,6 +287,28 @@ class OrderPositionAddForm(forms.Form):
|
||||
change_decimal_field(self.fields['price'], order.event.currency)
|
||||
|
||||
|
||||
class OrderPositionAddFormset(forms.BaseFormSet):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.order = kwargs.pop('order', None)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def _construct_form(self, i, **kwargs):
|
||||
kwargs['order'] = self.order
|
||||
return super()._construct_form(i, **kwargs)
|
||||
|
||||
@property
|
||||
def empty_form(self):
|
||||
form = self.form(
|
||||
auto_id=self.auto_id,
|
||||
prefix=self.add_prefix('__prefix__'),
|
||||
empty_permitted=True,
|
||||
use_required_attribute=False,
|
||||
order=self.order,
|
||||
)
|
||||
self.add_fields(form, None)
|
||||
return form
|
||||
|
||||
|
||||
class OrderPositionChangeForm(forms.Form):
|
||||
itemvar = forms.ChoiceField(
|
||||
required=False,
|
||||
@@ -408,8 +436,24 @@ class OrderMailForm(forms.Form):
|
||||
required=True
|
||||
)
|
||||
|
||||
def _set_field_placeholders(self, fn, base_parameters):
|
||||
phs = [
|
||||
'{%s}' % p
|
||||
for p in sorted(get_available_placeholders(self.order.event, base_parameters).keys())
|
||||
]
|
||||
ht = _('Available placeholders: {list}').format(
|
||||
list=', '.join(phs)
|
||||
)
|
||||
if self.fields[fn].help_text:
|
||||
self.fields[fn].help_text += ' ' + str(ht)
|
||||
else:
|
||||
self.fields[fn].help_text = ht
|
||||
self.fields[fn].validators.append(
|
||||
PlaceholderValidator(phs)
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
order = kwargs.pop('order')
|
||||
order = self.order = kwargs.pop('order')
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['sendto'] = forms.EmailField(
|
||||
label=_("Recipient"),
|
||||
@@ -422,11 +466,8 @@ class OrderMailForm(forms.Form):
|
||||
required=True,
|
||||
widget=forms.Textarea,
|
||||
initial=order.event.settings.mail_text_order_custom_mail.localize(order.locale),
|
||||
help_text=_("Available placeholders: {expire_date}, {event}, {code}, {date}, {url}, "
|
||||
"{invoice_name}, {invoice_company}"),
|
||||
validators=[PlaceholderValidator(['{expire_date}', '{event}', '{code}', '{date}', '{url}',
|
||||
'{invoice_name}', '{invoice_company}'])]
|
||||
)
|
||||
self._set_field_placeholders('message', ['event', 'order'])
|
||||
|
||||
|
||||
class OrderRefundForm(forms.Form):
|
||||
|
||||
@@ -16,6 +16,7 @@ from pretix.base.models import Device, Organizer, Team
|
||||
from pretix.control.forms import (
|
||||
ExtFileField, FontSelect, MultipleLanguagesWidget,
|
||||
)
|
||||
from pretix.control.forms.event import SafeEventMultipleChoiceField
|
||||
from pretix.multidomain.models import KnownDomain
|
||||
from pretix.presale.style import get_fonts
|
||||
|
||||
@@ -136,7 +137,9 @@ class TeamForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
organizer = kwargs.pop('organizer')
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['limit_events'].queryset = organizer.events.all()
|
||||
self.fields['limit_events'].queryset = organizer.events.all().order_by(
|
||||
'-has_subevents', '-date_from'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Team
|
||||
@@ -147,11 +150,12 @@ class TeamForm(forms.ModelForm):
|
||||
'can_view_vouchers', 'can_change_vouchers']
|
||||
widgets = {
|
||||
'limit_events': forms.CheckboxSelectMultiple(attrs={
|
||||
'data-inverse-dependency': '#id_all_events'
|
||||
'data-inverse-dependency': '#id_all_events',
|
||||
'class': 'scrolling-multiple-choice scrolling-multiple-choice-large',
|
||||
}),
|
||||
}
|
||||
field_classes = {
|
||||
'limit_events': SafeModelMultipleChoiceField
|
||||
'limit_events': SafeEventMultipleChoiceField
|
||||
}
|
||||
|
||||
def clean(self):
|
||||
@@ -171,7 +175,9 @@ class DeviceForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
organizer = kwargs.pop('organizer')
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['limit_events'].queryset = organizer.events.all()
|
||||
self.fields['limit_events'].queryset = organizer.events.all().order_by(
|
||||
'-has_subevents', '-date_from'
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
d = super().clean()
|
||||
@@ -185,11 +191,12 @@ class DeviceForm(forms.ModelForm):
|
||||
fields = ['name', 'all_events', 'limit_events']
|
||||
widgets = {
|
||||
'limit_events': forms.CheckboxSelectMultiple(attrs={
|
||||
'data-inverse-dependency': '#id_all_events'
|
||||
'data-inverse-dependency': '#id_all_events',
|
||||
'class': 'scrolling-multiple-choice scrolling-multiple-choice-large',
|
||||
}),
|
||||
}
|
||||
field_classes = {
|
||||
'limit_events': SafeModelMultipleChoiceField
|
||||
'limit_events': SafeEventMultipleChoiceField
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -179,9 +179,7 @@ class VoucherForm(I18nModelForm):
|
||||
return data
|
||||
|
||||
def save(self, commit=True):
|
||||
super().save(commit)
|
||||
|
||||
return ['item']
|
||||
return super().save(commit)
|
||||
|
||||
|
||||
class VoucherBulkForm(VoucherForm):
|
||||
|
||||
@@ -10,12 +10,16 @@
|
||||
{% endcompress %}
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<link rel="shortcut icon" href="{% static "pretixbase/img/favicon.ico" %}">
|
||||
{% if development_warning or debug_warning %}
|
||||
<link rel="shortcut icon" href="{% static "pretixbase/img/favicon-debug.ico" %}">
|
||||
{% else %}
|
||||
<link rel="shortcut icon" href="{% static "pretixbase/img/favicon.ico" %}">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="{% static "pretixbase/img/icons/favicon-32x32.png" %}">
|
||||
<link rel="icon" type="image/png" sizes="194x194" href="{% static "pretixbase/img/icons/favicon-194x194.png" %}">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="{% static "pretixbase/img/icons/favicon-16x16.png" %}">
|
||||
{% endif %}
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="{% static "pretixbase/img/icons/apple-touch-icon.png" %}">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="{% static "pretixbase/img/icons/favicon-32x32.png" %}">
|
||||
<link rel="icon" type="image/png" sizes="194x194" href="{% static "pretixbase/img/icons/favicon-194x194.png" %}">
|
||||
<link rel="icon" type="image/png" sizes="192x192" href="{% static "pretixbase/img/icons/android-chrome-192x192.png" %}">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="{% static "pretixbase/img/icons/favicon-16x16.png" %}">
|
||||
<link rel="manifest" href="{% url "presale:site.webmanifest" %}">
|
||||
<link rel="mask-icon" href="{% static "pretixbase/img/icons/safari-pinned-tab.svg" %}" color="#3b1c4a">
|
||||
<meta name="msapplication-TileColor" content="#3b1c4a">
|
||||
|
||||
@@ -59,12 +59,16 @@
|
||||
{{ html_head|safe }}
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="shortcut icon" href="{% static "pretixbase/img/favicon.ico" %}">
|
||||
{% if development_warning or debug_warning %}
|
||||
<link rel="shortcut icon" href="{% static "pretixbase/img/favicon-debug.ico" %}">
|
||||
{% else %}
|
||||
<link rel="shortcut icon" href="{% static "pretixbase/img/favicon.ico" %}">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="{% static "pretixbase/img/icons/favicon-32x32.png" %}">
|
||||
<link rel="icon" type="image/png" sizes="194x194" href="{% static "pretixbase/img/icons/favicon-194x194.png" %}">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="{% static "pretixbase/img/icons/favicon-16x16.png" %}">
|
||||
{% endif %}
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="{% static "pretixbase/img/icons/apple-touch-icon.png" %}">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="{% static "pretixbase/img/icons/favicon-32x32.png" %}">
|
||||
<link rel="icon" type="image/png" sizes="194x194" href="{% static "pretixbase/img/icons/favicon-194x194.png" %}">
|
||||
<link rel="icon" type="image/png" sizes="192x192" href="{% static "pretixbase/img/icons/android-chrome-192x192.png" %}">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="{% static "pretixbase/img/icons/favicon-16x16.png" %}">
|
||||
<link rel="manifest" href="{% url "presale:site.webmanifest" %}">
|
||||
<link rel="mask-icon" href="{% static "pretixbase/img/icons/safari-pinned-tab.svg" %}" color="#3b1c4a">
|
||||
<meta name="msapplication-TileColor" content="#3b1c4a">
|
||||
@@ -161,13 +165,13 @@
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
{% for item in nav.children %}
|
||||
<li>
|
||||
<a href="{{ item.url }}"
|
||||
<a {% if item.url %}href="{{ item.url }}"{% endif %}
|
||||
{% if item.external %}target="_blank"{% endif %}
|
||||
{% if item.active %}class="active"{% endif %}>
|
||||
{% if item.icon %}
|
||||
<span class="fa fa-{{ item.icon }}"></span>
|
||||
<span class="fa fa-fw fa-{{ item.icon }}"></span>
|
||||
{% endif %}
|
||||
{{ item.label|safe }}
|
||||
</a>
|
||||
{{ item.label|safe }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
@@ -256,7 +260,7 @@
|
||||
<div class="form-box">
|
||||
<input type="text" class="form-control" id="event-dropdown-field"
|
||||
placeholder="{% trans "Search for events" %}"
|
||||
data-typeahead-query>
|
||||
data-typeahead-query autocomplete="off">
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -119,6 +119,10 @@
|
||||
<span class="label label-danger">{% trans "Not checked in" %}</span>
|
||||
{% else %}
|
||||
<span class="label label-success">{% trans "Checked in" %}</span>
|
||||
{% if e.auto_checked_in %}
|
||||
<span class="fa fa-magic text-muted"
|
||||
data-toggle="tooltip" title="{% trans "Checked in automatically" %}"></span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
|
||||
@@ -24,6 +24,9 @@
|
||||
{% bootstrap_field form.subevent layout="control" %}
|
||||
{% endif %}
|
||||
{% bootstrap_field form.include_pending layout="control" %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
|
||||
<legend>{% trans "Products" %}</legend>
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
@@ -33,6 +36,10 @@
|
||||
{% bootstrap_field form.all_products layout="control" %}
|
||||
{% bootstrap_field form.limit_products layout="control" %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Advanced" %}</legend>
|
||||
{% bootstrap_field form.auto_checkin_sales_channels layout="control" %}
|
||||
</fieldset>
|
||||
<div class="form-group submit-group">
|
||||
<button type="submit" class="btn btn-primary btn-save">
|
||||
{% trans "Save" %}
|
||||
|
||||
@@ -60,6 +60,7 @@
|
||||
{% if request.event.has_subevents %}
|
||||
<th>{% trans "Date" context "subevent" %}</th>
|
||||
{% endif %}
|
||||
<th class="iconcol">{% trans "Automated check-in" %}</th>
|
||||
<th>{% trans "Products" %}</th>
|
||||
<th class="action-col-2"></th>
|
||||
</tr>
|
||||
@@ -84,6 +85,12 @@
|
||||
{% if request.event.has_subevents %}
|
||||
<td>{{ cl.subevent.name }} – {{ cl.subevent.get_date_range_display }}</td>
|
||||
{% endif %}
|
||||
<td>
|
||||
{% for channel in cl.auto_checkin_sales_channels %}
|
||||
<span class="fa fa-{{ channel.icon }} text-muted"
|
||||
data-toggle="tooltip" title="{% trans channel.verbose_name %}"></span>
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td>
|
||||
{% if cl.all_products %}
|
||||
<em>{% trans "All" %}</em>
|
||||
|
||||
@@ -20,9 +20,13 @@
|
||||
</a>
|
||||
</div>
|
||||
{% for w in upcoming %}
|
||||
<div class="widget-{{ w.display_size|default:"small" }} {{ w.container_class|default:"widget-container" }}">
|
||||
<div class="widget-{{ w.display_size|default:"small" }} {{ w.container_class|default:"widget-container" }} {% if w.lazy %}widget-lazy-loading{% endif %}" data-lazy-id="{{ w.lazy }}">
|
||||
<div class="widget">
|
||||
{{ w.content|safe }}
|
||||
{% if w.lazy %}
|
||||
<span class="fa fa-cog fa-4x"></span>
|
||||
{% else %}
|
||||
{{ w.content|safe }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
@@ -38,9 +42,13 @@
|
||||
<h2>{% trans "Your most recent events" %}</h2>
|
||||
<div class="dashboard">
|
||||
{% for w in past %}
|
||||
<div class="widget-{{ w.display_size|default:"small" }} {{ w.container_class|default:"widget-container" }}">
|
||||
<div class="widget-{{ w.display_size|default:"small" }} {{ w.container_class|default:"widget-container" }} {% if w.lazy %}widget-lazy-loading{% endif %}" data-lazy-id="{{ w.lazy }}">
|
||||
<div class="widget">
|
||||
{{ w.content|safe }}
|
||||
{% if w.lazy %}
|
||||
<span class="fa fa-cog fa-4x"></span>
|
||||
{% else %}
|
||||
{{ w.content|safe }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
@@ -55,9 +63,13 @@
|
||||
<h2>{% trans "Your event series" %}</h2>
|
||||
<div class="dashboard">
|
||||
{% for w in series %}
|
||||
<div class="widget-{{ w.display_size|default:"small" }} {{ w.container_class|default:"widget-container" }}">
|
||||
<div class="widget-{{ w.display_size|default:"small" }} {{ w.container_class|default:"widget-container" }} {% if w.lazy %}widget-lazy-loading{% endif %}" data-lazy-id="{{ w.lazy }}">
|
||||
<div class="widget">
|
||||
{{ w.content|safe }}
|
||||
{% if w.lazy %}
|
||||
<span class="fa fa-cog fa-4x"></span>
|
||||
{% else %}
|
||||
{{ w.content|safe }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
@@ -72,14 +84,22 @@
|
||||
<h2>{% trans "Other features" %}</h2>
|
||||
<div class="dashboard">
|
||||
{% for w in widgets %}
|
||||
<div class="widget-{{ w.display_size|default:"small" }} {{ w.container_class|default:"widget-container" }}">
|
||||
<div class="widget-{{ w.display_size|default:"small" }} {{ w.container_class|default:"widget-container" }} {% if w.lazy %}widget-lazy-loading{% endif %}" data-lazy-id="{{ w.lazy }}">
|
||||
{% if w.url %}
|
||||
<a href="{{ w.url }}" class="widget">
|
||||
{{ w.content|safe }}
|
||||
{% if w.lazy %}
|
||||
<span class="fa fa-cog fa-4x"></span>
|
||||
{% else %}
|
||||
{{ w.content|safe }}
|
||||
{% endif %}
|
||||
</a>
|
||||
{% else %}
|
||||
<div class="widget">
|
||||
{{ w.content|safe }}
|
||||
{% if w.lazy %}
|
||||
<span class="fa fa-cog fa-4x"></span>
|
||||
{% else %}
|
||||
{{ w.content|safe }}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -40,11 +40,15 @@
|
||||
<strong><a href="{% url "control:event.items.categories.edit" organizer=request.event.organizer.slug event=request.event.slug category=c.id %}">{{ c.name }}</a></strong>
|
||||
</td>
|
||||
<td>
|
||||
<a href="{% url "control:event.items.categories.up" organizer=request.event.organizer.slug event=request.event.slug category=c.id %}" class="btn btn-default btn-sm {% if forloop.counter0 == 0 %}disabled{% endif %}"><i class="fa fa-arrow-up"></i></a>
|
||||
<a href="{% url "control:event.items.categories.down" organizer=request.event.organizer.slug event=request.event.slug category=c.id %}" class="btn btn-default btn-sm {% if forloop.revcounter0 == 0 %}disabled{% endif %}"><i class="fa fa-arrow-down"></i></a>
|
||||
<a href="{% url "control:event.items.categories.up" organizer=request.event.organizer.slug event=request.event.slug category=c.id %}" class="btn btn-default btn-sm {% if forloop.counter0 == 0 and is_paginated and not page_obj.has_previous %}disabled{% endif %}"><i class="fa fa-arrow-up"></i></a>
|
||||
<a href="{% url "control:event.items.categories.down" organizer=request.event.organizer.slug event=request.event.slug category=c.id %}" class="btn btn-default btn-sm {% if forloop.revcounter0 == 0 and is_paginated and not page_obj.has_next %}disabled{% endif %}"><i class="fa fa-arrow-down"></i></a>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<a href="{% url "control:event.items.categories.edit" organizer=request.event.organizer.slug event=request.event.slug category=c.id %}" class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
|
||||
<a href="{% url "control:event.items.categories.add" organizer=request.event.organizer.slug event=request.event.slug %}?copy_from={{ c.id }}"
|
||||
class="btn btn-sm btn-default" title="{% trans "Clone" %}" data-toggle="tooltip">
|
||||
<span class="fa fa-copy"></span>
|
||||
</a>
|
||||
<a href="{% url "control:event.items.categories.delete" organizer=request.event.organizer.slug event=request.event.slug category=c.id %}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -90,8 +90,8 @@
|
||||
</td>
|
||||
<td>{% if i.category %}{{ i.category.name }}{% endif %}</td>
|
||||
<td>
|
||||
<a href="{% url "control:event.items.up" organizer=request.event.organizer.slug event=request.event.slug item=i.id %}" class="btn btn-default btn-sm {% if forloop.counter0 == 0 %}disabled{% endif %}"><i class="fa fa-arrow-up"></i></a>
|
||||
<a href="{% url "control:event.items.down" organizer=request.event.organizer.slug event=request.event.slug item=i.id %}" class="btn btn-default btn-sm {% if forloop.revcounter0 == 0 %}disabled{% endif %}"><i class="fa fa-arrow-down"></i></a>
|
||||
<a href="{% url "control:event.items.up" organizer=request.event.organizer.slug event=request.event.slug item=i.id %}" class="btn btn-default btn-sm {% if forloop.counter0 == 0 and is_paginated and not page_obj.has_previous %}disabled{% endif %}"><i class="fa fa-arrow-up"></i></a>
|
||||
<a href="{% url "control:event.items.down" organizer=request.event.organizer.slug event=request.event.slug item=i.id %}" class="btn btn-default btn-sm {% if forloop.revcounter0 == 0 and is_paginated and not page_obj.has_next %}disabled{% endif %}"><i class="fa fa-arrow-down"></i></a>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<a href="{% url "control:event.item" organizer=request.event.organizer.slug event=request.event.slug item=i.id %}" class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
|
||||
|
||||
@@ -72,8 +72,8 @@
|
||||
</ul>
|
||||
</td>
|
||||
<td>
|
||||
<a href="{% url "control:event.items.questions.up" organizer=request.event.organizer.slug event=request.event.slug question=q.id %}" class="btn btn-default btn-sm {% if forloop.counter0 == 0 %}disabled{% endif %}"><i class="fa fa-arrow-up"></i></a>
|
||||
<a href="{% url "control:event.items.questions.down" organizer=request.event.organizer.slug event=request.event.slug question=q.id %}" class="btn btn-default btn-sm {% if forloop.revcounter0 == 0 %}disabled{% endif %}"><i class="fa fa-arrow-down"></i></a>
|
||||
<a href="{% url "control:event.items.questions.up" organizer=request.event.organizer.slug event=request.event.slug question=q.id %}" class="btn btn-default btn-sm {% if forloop.counter0 == 0 and is_paginated and not page_obj.has_previous %}disabled{% endif %}"><i class="fa fa-arrow-up"></i></a>
|
||||
<a href="{% url "control:event.items.questions.down" organizer=request.event.organizer.slug event=request.event.slug question=q.id %}" class="btn btn-default btn-sm {% if forloop.revcounter0 == 0 and is_paginated and not page_obj.has_next %}disabled{% endif %}"><i class="fa fa-arrow-down"></i></a>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<a href="{% url "control:event.items.questions.edit" organizer=request.event.organizer.slug event=request.event.slug question=q.id %}" class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
|
||||
|
||||
@@ -77,6 +77,10 @@
|
||||
<td>{% include "pretixcontrol/items/fragment_quota_availability.html" with availability=q.availability closed=q.closed %}</td>
|
||||
<td class="text-right">
|
||||
<a href="{% url "control:event.items.quotas.edit" organizer=request.event.organizer.slug event=request.event.slug quota=q.id %}" class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
|
||||
<a href="{% url "control:event.items.quotas.add" organizer=request.event.organizer.slug event=request.event.slug %}?copy_from={{ q.id }}"
|
||||
class="btn btn-sm btn-default" title="{% trans "Clone" %}" data-toggle="tooltip">
|
||||
<span class="fa fa-copy"></span>
|
||||
</a>
|
||||
<a href="{% url "control:event.items.quotas.delete" organizer=request.event.organizer.slug event=request.event.slug quota=q.id %}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{% extends "pretixcontrol/event/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% load formset_tags %}
|
||||
{% load money %}
|
||||
{% block title %}
|
||||
{% blocktrans trimmed with code=order.code %}
|
||||
@@ -64,10 +65,10 @@
|
||||
{% endif %}
|
||||
{% if position.addon_to %}
|
||||
– <em>
|
||||
{% blocktrans trimmed with posid=position.addon_to.positionid %}
|
||||
Add-On to position #{{ posid }}
|
||||
{% endblocktrans %}
|
||||
</em>
|
||||
{% blocktrans trimmed with posid=position.addon_to.positionid %}
|
||||
Add-On to position #{{ posid }}
|
||||
{% endblocktrans %}
|
||||
</em>
|
||||
{% endif %}
|
||||
</h3>
|
||||
</div>
|
||||
@@ -173,33 +174,87 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="panel panel-default items">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
{% trans "Add product" %}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="form-horizontal">
|
||||
{% bootstrap_form_errors add_form %}
|
||||
{% if add_form.custom_error %}
|
||||
<div class="alert alert-danger">
|
||||
{{ add_form.custom_error }}
|
||||
|
||||
|
||||
<div class="formset" data-formset data-formset-prefix="{{ add_formset.prefix }}">
|
||||
{{ add_formset.management_form }}
|
||||
{% bootstrap_formset_errors add_formset %}
|
||||
<div data-formset-body>
|
||||
{% for add_form in add_formset %}
|
||||
<div class="panel panel-default items" data-formset-form>
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
<button type="button" class="btn btn-danger btn-xs pull-right flip"
|
||||
data-formset-delete-button>
|
||||
<i class="fa fa-trash"></i>
|
||||
</button>
|
||||
{% trans "Add product" %}
|
||||
<div class="sr-only">
|
||||
{{ add_form.id }}
|
||||
{% bootstrap_field add_form.DELETE form_group_class="" layout="inline" %}
|
||||
</div>
|
||||
</h3>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% bootstrap_field add_form.do layout="control" %}
|
||||
{% bootstrap_field add_form.itemvar layout="control" %}
|
||||
{% bootstrap_field add_form.price addon_after=request.event.currency layout="control" %}
|
||||
{% if add_form.addon_to %}
|
||||
{% bootstrap_field add_form.addon_to layout="control" %}
|
||||
{% endif %}
|
||||
{% if add_form.subevent %}
|
||||
{% bootstrap_field add_form.subevent layout="control" %}
|
||||
{% endif %}
|
||||
{% bootstrap_field add_form.seat layout="control" %}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="form-horizontal">
|
||||
{% bootstrap_form_errors add_form %}
|
||||
{% if add_form.custom_error %}
|
||||
<div class="alert alert-danger">
|
||||
{{ add_form.custom_error }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% bootstrap_field add_form.itemvar layout="control" %}
|
||||
{% bootstrap_field add_form.price addon_after=request.event.currency layout="control" %}
|
||||
{% if add_form.addon_to %}
|
||||
{% bootstrap_field add_form.addon_to layout="control" %}
|
||||
{% endif %}
|
||||
{% if add_form.subevent %}
|
||||
{% bootstrap_field add_form.subevent layout="control" %}
|
||||
{% endif %}
|
||||
{% bootstrap_field add_form.seat layout="control" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<script type="form-template" data-formset-empty-form>
|
||||
{% escapescript %}
|
||||
<div class="panel panel-default items" data-formset-form>
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
<button type="button" class="btn btn-danger btn-xs pull-right flip"
|
||||
data-formset-delete-button>
|
||||
<i class="fa fa-trash"></i>
|
||||
</button>
|
||||
{% trans "Add product" %}
|
||||
<div class="sr-only">
|
||||
{{ add_formset.empty_form.id }}
|
||||
{% bootstrap_field add_formset.empty_form.DELETE form_group_class="" layout="inline" %}
|
||||
</div>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="form-horizontal">
|
||||
{% bootstrap_field add_formset.empty_form.itemvar layout="control" %}
|
||||
{% bootstrap_field add_formset.empty_form.price addon_after=request.event.currency layout="control" %}
|
||||
{% if add_formset.empty_form.addon_to %}
|
||||
{% bootstrap_field add_formset.empty_form.addon_to layout="control" %}
|
||||
{% endif %}
|
||||
{% if add_formset.empty_form.subevent %}
|
||||
{% bootstrap_field add_formset.empty_form.subevent layout="control" %}
|
||||
{% endif %}
|
||||
{% bootstrap_field add_formset.empty_form.seat layout="control" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endescapescript %}
|
||||
</script>
|
||||
<p>
|
||||
<button type="button" class="btn btn-default" data-formset-add>
|
||||
<i class="fa fa-plus"></i> {% trans "Add product" %}</button>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default items">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
@@ -215,6 +270,7 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
{% bootstrap_field other_form.recalculate_taxes layout="control" %}
|
||||
{% bootstrap_field other_form.reissue_invoice layout="control" %}
|
||||
{% bootstrap_field other_form.notify layout="control" %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -255,7 +255,11 @@
|
||||
{% endif %}
|
||||
{% if line.checkins.all %}
|
||||
{% for c in line.checkins.all %}
|
||||
<span class="fa fa-fw fa-check" data-toggle="tooltip_html" title="{{ c.list.name }}<br>{% blocktrans trimmed with date=c.datetime|date:'SHORT_DATETIME_FORMAT' %}First scanned: {{ date }}{% endblocktrans %}"></span>
|
||||
{% if c.auto_checked_in %}
|
||||
<span class="fa fa-fw fa-magic" data-toggle="tooltip_html" title="{{ c.list.name }}<br>{% blocktrans trimmed with date=c.datetime|date:'SHORT_DATETIME_FORMAT' %}Automatically checked in: {{ date }}{% endblocktrans %}"></span>
|
||||
{% else %}
|
||||
<span class="fa fa-fw fa-check" data-toggle="tooltip_html" title="{{ c.list.name }}<br>{% blocktrans trimmed with date=c.datetime|date:'SHORT_DATETIME_FORMAT' %}First scanned: {{ date }}{% endblocktrans %}"></span>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if line.seat %}
|
||||
@@ -294,8 +298,15 @@
|
||||
{% endif %}
|
||||
<button type="submit" data-toggle="qrcode" data-qrcode="{{ line.secret }}"
|
||||
class="btn btn-xs btn-default">
|
||||
<span class="fa fa-qrcode"></span> {% trans "Show ticket code" %}
|
||||
<span class="fa fa-qrcode"></span> {% trans "Ticket code" %}
|
||||
</button>
|
||||
{% if not line.addon_to %}
|
||||
<a href="{% eventurl request.event "presale:event.order.position" order=order.code secret=line.web_secret position=line.positionid %}"
|
||||
class="btn btn-xs btn-default" target="_blank">
|
||||
<span class="fa fa-eye"></span>
|
||||
{% trans "Ticket page" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% eventsignal event "pretix.control.signals.order_position_buttons" order=order position=line request=request %}
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -308,8 +319,22 @@
|
||||
{% endif %}
|
||||
{% if line.item.admission and event.settings.attendee_emails_asked %}
|
||||
<dt>{% trans "Attendee email" %}</dt>
|
||||
<dd>{% if line.attendee_email %}{{ line.attendee_email }}{% else %}
|
||||
<em>{% trans "not answered" %}</em>{% endif %}</dd>
|
||||
<dd>
|
||||
{% if line.attendee_email %}
|
||||
{{ line.attendee_email }}
|
||||
{% if not line.addon_to %}
|
||||
<form class="form-inline helper-display-inline" method="post"
|
||||
action="{% url "control:event.order.resendlink" event=request.event.slug organizer=request.event.organizer.slug code=order.code position=line.pk %}">
|
||||
{% csrf_token %}
|
||||
<button class="btn btn-default btn-xs">
|
||||
{% trans "Resend link" %}
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<em>{% trans "not answered" %}</em>
|
||||
{% endif %}
|
||||
</dd>
|
||||
{% endif %}
|
||||
{% for q in line.questions %}
|
||||
<dt>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
</h1>
|
||||
{% if "can_create_events" in request.orgapermset %}
|
||||
<p>
|
||||
<a href="{% url "control:events.add" %}" class="btn btn-default">
|
||||
<a href="{% url "control:events.add" %}?organizer={{ request.organizer.slug }}" class="btn btn-default">
|
||||
<span class="fa fa-plus"></span>
|
||||
{% trans "Create a new event" %}
|
||||
</a>
|
||||
|
||||
@@ -138,6 +138,10 @@
|
||||
<td>{{ v.subevent.name }} – {{ v.subevent.get_date_range_display }}</td>
|
||||
{% endif %}
|
||||
<td class="text-right">
|
||||
<a href="{% url "control:event.vouchers.bulk" organizer=request.event.organizer.slug event=request.event.slug %}?copy_from={{ v.id }}"
|
||||
class="btn btn-sm btn-default" title="{% trans "Use as a template for new vouchers" %}" data-toggle="tooltip">
|
||||
<span class="fa fa-copy"></span>
|
||||
</a>
|
||||
<a href="{% url "control:event.voucher.delete" organizer=request.event.organizer.slug event=request.event.slug voucher=v.id %}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{% extends "pretixcontrol/event/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% block title %}{% trans "Voucher tags" %}{% endblock %}
|
||||
{% block content %}
|
||||
<h1>{% trans "Voucher tags" %}</h1>
|
||||
@@ -8,6 +9,23 @@
|
||||
If you add a "tag" to a voucher, you can here see statistics on their usage.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
{% if request.event.has_subevents %}
|
||||
<div class="row filter-form">
|
||||
<form class="" action="" method="get">
|
||||
<div class="col-lg-2 col-sm-3 col-xs-6">
|
||||
{% bootstrap_field filter_form.subevent layout='inline' %}
|
||||
</div>
|
||||
<div class="col-lg-1 col-sm-6 col-xs-6">
|
||||
<button class="btn btn-primary btn-block" type="submit">
|
||||
<span class="fa fa-filter"></span>
|
||||
<span class="hidden-md">
|
||||
{% trans "Filter" %}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if tags|length == 0 %}
|
||||
<div class="empty-collection">
|
||||
<p>
|
||||
|
||||
@@ -15,6 +15,7 @@ urlpatterns = [
|
||||
url(r'^forgot$', auth.Forgot.as_view(), name='auth.forgot'),
|
||||
url(r'^forgot/recover$', auth.Recover.as_view(), name='auth.forgot.recover'),
|
||||
url(r'^$', dashboards.user_index, name='index'),
|
||||
url(r'^widgets.json$', dashboards.user_index_widgets_lazy, name='index.widgets'),
|
||||
url(r'^global/settings/$', global_settings.GlobalSettingsView.as_view(), name='global.settings'),
|
||||
url(r'^global/update/$', global_settings.UpdateCheckView.as_view(), name='global.update'),
|
||||
url(r'^global/message/$', global_settings.MessageView.as_view(), name='global.message'),
|
||||
@@ -196,6 +197,8 @@ urlpatterns = [
|
||||
name='event.order.transition'),
|
||||
url(r'^orders/(?P<code>[0-9A-Z]+)/resend$', orders.OrderResendLink.as_view(),
|
||||
name='event.order.resendlink'),
|
||||
url(r'^orders/(?P<code>[0-9A-Z]+)/(?P<position>\d+)/resend$', orders.OrderResendLink.as_view(),
|
||||
name='event.order.resendlink'),
|
||||
url(r'^orders/(?P<code>[0-9A-Z]+)/invoice$', orders.OrderInvoiceCreate.as_view(),
|
||||
name='event.order.geninvoice'),
|
||||
url(r'^orders/(?P<code>[0-9A-Z]+)/invoices/(?P<id>\d+)/regenerate$', orders.OrderInvoiceRegenerate.as_view(),
|
||||
|
||||
@@ -74,6 +74,8 @@ def logout(request):
|
||||
next = reverse('control:auth.login')
|
||||
if 'next' in request.GET and is_safe_url(request.GET.get('next'), allowed_hosts=None):
|
||||
next += '?next=' + quote(request.GET.get('next'))
|
||||
if 'back' in request.GET and is_safe_url(request.GET.get('back'), allowed_hosts=None):
|
||||
return redirect(request.GET.get('back'))
|
||||
return redirect(next)
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import dateutil.parser
|
||||
from django.contrib import messages
|
||||
from django.db import transaction
|
||||
from django.db.models import Max, OuterRef, Subquery
|
||||
from django.db.models import Exists, Max, OuterRef, Subquery
|
||||
from django.http import Http404, HttpResponseRedirect
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.urls import reverse
|
||||
@@ -11,6 +11,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||
from django.views.generic import DeleteView, ListView
|
||||
from pytz import UTC
|
||||
|
||||
from pretix.base.channels import get_all_sales_channels
|
||||
from pretix.base.models import Checkin, Order, OrderPosition
|
||||
from pretix.base.models.checkin import CheckinList
|
||||
from pretix.control.forms.checkin import CheckinListForm
|
||||
@@ -38,7 +39,10 @@ class CheckInListShow(EventPermissionRequiredMixin, PaginationMixin, ListView):
|
||||
order__status__in=[Order.STATUS_PAID, Order.STATUS_PENDING] if self.list.include_pending else [Order.STATUS_PAID],
|
||||
subevent=self.list.subevent
|
||||
).annotate(
|
||||
last_checked_in=Subquery(cqs)
|
||||
last_checked_in=Subquery(cqs),
|
||||
auto_checked_in=Exists(
|
||||
Checkin.objects.filter(position_id=OuterRef('pk'), list_id=self.list.pk, auto_checked_in=True)
|
||||
)
|
||||
).select_related('item', 'variation', 'order', 'addon_to')
|
||||
|
||||
if not self.list.all_products:
|
||||
@@ -146,21 +150,14 @@ class CheckinListList(EventPermissionRequiredMixin, PaginationMixin, ListView):
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
clists = list(ctx['checkinlists'])
|
||||
sales_channels = get_all_sales_channels()
|
||||
|
||||
# Optimization: Fetch expensive columns for this page only
|
||||
annotations = {
|
||||
a['pk']: a
|
||||
for a in CheckinList.annotate_with_numbers(CheckinList.objects.filter(pk__in=[l.pk for l in clists]), self.request.event).values(
|
||||
'pk', 'checkin_count', 'position_count', 'percent'
|
||||
)
|
||||
}
|
||||
for cl in clists:
|
||||
if cl.subevent:
|
||||
cl.subevent.event = self.request.event # re-use same event object to make sure settings are cached
|
||||
cl.checkin_count = annotations.get(cl.pk, {}).get('checkin_count', 0)
|
||||
cl.position_count = annotations.get(cl.pk, {}).get('position_count', 0)
|
||||
cl.percent = annotations.get(cl.pk, {}).get('percent', 0)
|
||||
cl.auto_checkin_sales_channels = [sales_channels[channel] for channel in cl.auto_checkin_sales_channels]
|
||||
ctx['checkinlists'] = clists
|
||||
|
||||
return ctx
|
||||
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ from pretix.base.models import (
|
||||
Item, Order, OrderPosition, OrderRefund, RequiredAction, SubEvent, Voucher,
|
||||
WaitingListEntry,
|
||||
)
|
||||
from pretix.base.models.checkin import CheckinList
|
||||
from pretix.base.timeline import timeline_for_event
|
||||
from pretix.control.forms.event import CommentForm
|
||||
from pretix.control.signals import (
|
||||
@@ -227,8 +226,6 @@ def shop_state_widget(sender, **kwargs):
|
||||
def checkin_widget(sender, subevent=None, lazy=False, **kwargs):
|
||||
widgets = []
|
||||
qs = sender.checkin_lists.filter(subevent=subevent)
|
||||
if not lazy:
|
||||
qs = CheckinList.annotate_with_numbers(qs, sender)
|
||||
for cl in qs:
|
||||
widgets.append({
|
||||
'content': None if lazy else NUM_WIDGET.format(
|
||||
@@ -355,7 +352,7 @@ def event_index_widgets_lazy(request, organizer, event):
|
||||
return JsonResponse({'widgets': widgets})
|
||||
|
||||
|
||||
def annotated_event_query(request):
|
||||
def annotated_event_query(request, lazy=False):
|
||||
active_orders = Order.objects.filter(
|
||||
event=OuterRef('pk'),
|
||||
status__in=[Order.STATUS_PENDING, Order.STATUS_PAID]
|
||||
@@ -369,10 +366,13 @@ def annotated_event_query(request):
|
||||
event=OuterRef('pk'),
|
||||
done=False
|
||||
)
|
||||
qs = request.user.get_events_with_any_permission(request).annotate(
|
||||
order_count=Subquery(active_orders, output_field=IntegerField()),
|
||||
has_ra=Exists(required_actions)
|
||||
).annotate(
|
||||
qs = request.user.get_events_with_any_permission(request)
|
||||
if not lazy:
|
||||
qs = qs.annotate(
|
||||
order_count=Subquery(active_orders, output_field=IntegerField()),
|
||||
has_ra=Exists(required_actions)
|
||||
)
|
||||
qs = qs.annotate(
|
||||
min_from=Min('subevents__date_from'),
|
||||
max_from=Max('subevents__date_from'),
|
||||
max_to=Max('subevents__date_to'),
|
||||
@@ -383,14 +383,15 @@ def annotated_event_query(request):
|
||||
return qs
|
||||
|
||||
|
||||
def widgets_for_event_qs(request, qs, user, nmax):
|
||||
def widgets_for_event_qs(request, qs, user, nmax, lazy=False):
|
||||
widgets = []
|
||||
|
||||
# Get set of events where we have the permission to show the # of orders
|
||||
events_with_orders = set(qs.filter(
|
||||
Q(organizer_id__in=user.teams.filter(all_events=True, can_view_orders=True).values_list('organizer', flat=True))
|
||||
| Q(id__in=user.teams.filter(can_view_orders=True).values_list('limit_events__id', flat=True))
|
||||
).values_list('id', flat=True))
|
||||
if not lazy:
|
||||
events_with_orders = set(qs.filter(
|
||||
Q(organizer_id__in=user.teams.filter(all_events=True, can_view_orders=True).values_list('organizer', flat=True))
|
||||
| Q(id__in=user.teams.filter(can_view_orders=True).values_list('limit_events__id', flat=True))
|
||||
).values_list('id', flat=True))
|
||||
|
||||
tpl = """
|
||||
<a href="{url}" class="event">
|
||||
@@ -406,36 +407,40 @@ def widgets_for_event_qs(request, qs, user, nmax):
|
||||
</div>
|
||||
"""
|
||||
|
||||
events = qs.prefetch_related(
|
||||
'_settings_objects', 'organizer___settings_objects'
|
||||
).select_related('organizer')[:nmax]
|
||||
if lazy:
|
||||
events = qs[:nmax]
|
||||
else:
|
||||
events = qs.prefetch_related(
|
||||
'_settings_objects', 'organizer___settings_objects'
|
||||
).select_related('organizer')[:nmax]
|
||||
for event in events:
|
||||
tzname = event.cache.get_or_set('timezone', lambda: event.settings.timezone)
|
||||
tz = pytz.timezone(tzname)
|
||||
if event.has_subevents:
|
||||
if event.min_from is None:
|
||||
dr = pgettext("subevent", "No dates")
|
||||
if not lazy:
|
||||
tzname = event.cache.get_or_set('timezone', lambda: event.settings.timezone)
|
||||
tz = pytz.timezone(tzname)
|
||||
if event.has_subevents:
|
||||
if event.min_from is None:
|
||||
dr = pgettext("subevent", "No dates")
|
||||
else:
|
||||
dr = daterange(
|
||||
(event.min_from).astimezone(tz),
|
||||
(event.max_fromto or event.max_to or event.max_from).astimezone(tz)
|
||||
)
|
||||
else:
|
||||
dr = daterange(
|
||||
(event.min_from).astimezone(tz),
|
||||
(event.max_fromto or event.max_to or event.max_from).astimezone(tz)
|
||||
)
|
||||
else:
|
||||
if event.date_to:
|
||||
dr = daterange(event.date_from.astimezone(tz), event.date_to.astimezone(tz))
|
||||
else:
|
||||
dr = date_format(event.date_from.astimezone(tz), "DATE_FORMAT")
|
||||
if event.date_to:
|
||||
dr = daterange(event.date_from.astimezone(tz), event.date_to.astimezone(tz))
|
||||
else:
|
||||
dr = date_format(event.date_from.astimezone(tz), "DATE_FORMAT")
|
||||
|
||||
if event.has_ra:
|
||||
status = ('danger', _('Action required'))
|
||||
elif not event.live:
|
||||
status = ('warning', _('Shop disabled'))
|
||||
elif event.presale_has_ended:
|
||||
status = ('default', _('Sale over'))
|
||||
elif not event.presale_is_running:
|
||||
status = ('default', _('Soon'))
|
||||
else:
|
||||
status = ('success', _('On sale'))
|
||||
if event.has_ra:
|
||||
status = ('danger', _('Action required'))
|
||||
elif not event.live:
|
||||
status = ('warning', _('Shop disabled'))
|
||||
elif event.presale_has_ended:
|
||||
status = ('default', _('Sale over'))
|
||||
elif not event.presale_is_running:
|
||||
status = ('default', _('Soon'))
|
||||
else:
|
||||
status = ('success', _('On sale'))
|
||||
|
||||
widgets.append({
|
||||
'content': tpl.format(
|
||||
@@ -466,8 +471,9 @@ def widgets_for_event_qs(request, qs, user, nmax):
|
||||
daterange=dr,
|
||||
status=status[1],
|
||||
statusclass=status[0],
|
||||
),
|
||||
) if not lazy else '',
|
||||
'display_size': 'small',
|
||||
'lazy': 'event-{}'.format(event.pk),
|
||||
'priority': 100,
|
||||
'container_class': 'widget-container widget-container-event',
|
||||
})
|
||||
@@ -485,6 +491,43 @@ def widgets_for_event_qs(request, qs, user, nmax):
|
||||
return widgets
|
||||
|
||||
|
||||
def user_index_widgets_lazy(request):
|
||||
widgets = []
|
||||
widgets += widgets_for_event_qs(
|
||||
request,
|
||||
annotated_event_query(request).filter(
|
||||
Q(has_subevents=False) &
|
||||
Q(
|
||||
Q(Q(date_to__isnull=True) & Q(date_from__gte=now()))
|
||||
| Q(Q(date_to__isnull=False) & Q(date_to__gte=now()))
|
||||
)
|
||||
).order_by('date_from'),
|
||||
request.user,
|
||||
7
|
||||
)
|
||||
widgets += widgets_for_event_qs(
|
||||
request,
|
||||
annotated_event_query(request).filter(
|
||||
Q(has_subevents=False) &
|
||||
Q(
|
||||
Q(Q(date_to__isnull=True) & Q(date_from__lt=now()))
|
||||
| Q(Q(date_to__isnull=False) & Q(date_to__lt=now()))
|
||||
)
|
||||
).order_by('-order_to'),
|
||||
request.user,
|
||||
8
|
||||
)
|
||||
widgets += widgets_for_event_qs(
|
||||
request,
|
||||
annotated_event_query(request).filter(
|
||||
has_subevents=True
|
||||
).order_by('-order_to'),
|
||||
request.user,
|
||||
8
|
||||
)
|
||||
return JsonResponse({'widgets': widgets})
|
||||
|
||||
|
||||
def user_index(request):
|
||||
widgets = []
|
||||
for r, result in user_dashboard_widgets.send(request, user=request.user):
|
||||
@@ -494,7 +537,7 @@ def user_index(request):
|
||||
'widgets': rearrange(widgets),
|
||||
'upcoming': widgets_for_event_qs(
|
||||
request,
|
||||
annotated_event_query(request).filter(
|
||||
annotated_event_query(request, lazy=True).filter(
|
||||
Q(has_subevents=False) &
|
||||
Q(
|
||||
Q(Q(date_to__isnull=True) & Q(date_from__gte=now()))
|
||||
@@ -502,11 +545,12 @@ def user_index(request):
|
||||
)
|
||||
).order_by('date_from'),
|
||||
request.user,
|
||||
7
|
||||
7,
|
||||
lazy=True
|
||||
),
|
||||
'past': widgets_for_event_qs(
|
||||
request,
|
||||
annotated_event_query(request).filter(
|
||||
annotated_event_query(request, lazy=True).filter(
|
||||
Q(has_subevents=False) &
|
||||
Q(
|
||||
Q(Q(date_to__isnull=True) & Q(date_from__lt=now()))
|
||||
@@ -514,15 +558,17 @@ def user_index(request):
|
||||
)
|
||||
).order_by('-order_to'),
|
||||
request.user,
|
||||
8
|
||||
8,
|
||||
lazy=True
|
||||
),
|
||||
'series': widgets_for_event_qs(
|
||||
request,
|
||||
annotated_event_query(request).filter(
|
||||
annotated_event_query(request, lazy=True).filter(
|
||||
has_subevents=True
|
||||
).order_by('-order_to'),
|
||||
request.user,
|
||||
8
|
||||
8,
|
||||
lazy=True
|
||||
),
|
||||
}
|
||||
return render(request, 'pretixcontrol/dashboard.html', ctx)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import json
|
||||
import re
|
||||
from collections import OrderedDict
|
||||
from datetime import timedelta
|
||||
from decimal import Decimal
|
||||
from urllib.parse import urlsplit
|
||||
|
||||
@@ -18,7 +17,6 @@ from django.http import (
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.urls import reverse
|
||||
from django.utils import translation
|
||||
from django.utils.formats import date_format
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import ugettext, ugettext_lazy as _
|
||||
@@ -29,7 +27,7 @@ from i18nfield.strings import LazyI18nString
|
||||
from pytz import timezone
|
||||
|
||||
from pretix.base.channels import get_all_sales_channels
|
||||
from pretix.base.i18n import LazyCurrencyNumber
|
||||
from pretix.base.email import get_available_placeholders
|
||||
from pretix.base.models import (
|
||||
Event, LogEntry, Order, RequiredAction, TaxRule, Voucher,
|
||||
)
|
||||
@@ -37,7 +35,6 @@ from pretix.base.models.event import EventMetaValue
|
||||
from pretix.base.services import tickets
|
||||
from pretix.base.services.invoices import build_preview_invoice_pdf
|
||||
from pretix.base.signals import register_ticket_outputs
|
||||
from pretix.base.templatetags.money import money_filter
|
||||
from pretix.base.templatetags.rich_text import markdown_compile_email
|
||||
from pretix.control.forms.event import (
|
||||
CancelSettingsForm, CommentForm, EventDeleteForm, EventMetaValueForm,
|
||||
@@ -48,7 +45,6 @@ from pretix.control.forms.event import (
|
||||
)
|
||||
from pretix.control.permissions import EventPermissionRequiredMixin
|
||||
from pretix.helpers.database import rolledback_transaction
|
||||
from pretix.helpers.urls import build_absolute_uri
|
||||
from pretix.multidomain.urlreverse import get_domain
|
||||
from pretix.plugins.stripe.payment import StripeSettingsHolder
|
||||
from pretix.presale.style import regenerate_css
|
||||
@@ -538,20 +534,6 @@ class MailSettingsPreview(EventPermissionRequiredMixin, View):
|
||||
def __missing__(self, key):
|
||||
return '{' + key + '}'
|
||||
|
||||
@staticmethod
|
||||
def generate_order_fullname(slug, code):
|
||||
return '{event}-{code}'.format(event=slug.upper(), code=code)
|
||||
|
||||
# create data which depend on locale
|
||||
def localized_data(self):
|
||||
return {
|
||||
'date': date_format(now() + timedelta(days=7), 'SHORT_DATE_FORMAT'),
|
||||
'expire_date': date_format(now() + timedelta(days=15), 'SHORT_DATE_FORMAT'),
|
||||
'payment_info': _('{} has been transferred to account <9999-9999-9999-9999> at {}').format(
|
||||
money_filter(Decimal('42.23'), self.request.event.currency),
|
||||
date_format(now(), 'SHORT_DATETIME_FORMAT'))
|
||||
}
|
||||
|
||||
# create index-language mapping
|
||||
@cached_property
|
||||
def supported_locale(self):
|
||||
@@ -561,91 +543,23 @@ class MailSettingsPreview(EventPermissionRequiredMixin, View):
|
||||
locales[str(idx)] = val[0]
|
||||
return locales
|
||||
|
||||
@cached_property
|
||||
def meta_properties(self):
|
||||
return [p.name for p in self.request.organizer.meta_properties.all()]
|
||||
|
||||
@cached_property
|
||||
def items(self):
|
||||
kv = {
|
||||
'mail_text_order_placed': ['total', 'currency', 'date', 'invoice_company', 'total_with_currency',
|
||||
'event', 'payment_info', 'url', 'invoice_name'],
|
||||
'mail_text_order_placed_attendee': ['event', 'url', 'attendee_name'],
|
||||
'mail_text_order_paid': ['event', 'url', 'invoice_name', 'invoice_company', 'payment_info'],
|
||||
'mail_text_order_paid_attendee': ['event', 'url', 'attendee_name'],
|
||||
'mail_text_order_free': ['event', 'url', 'invoice_name', 'invoice_company'],
|
||||
'mail_text_order_free_attendee': ['event', 'url', 'attendee_name'],
|
||||
'mail_text_resend_link': ['event', 'url', 'invoice_name', 'invoice_company'],
|
||||
'mail_text_resend_all_links': ['event', 'orders'],
|
||||
'mail_text_order_changed': ['event', 'url', 'invoice_name', 'invoice_company'],
|
||||
'mail_text_order_expire_warning': ['event', 'url', 'expire_date', 'invoice_name', 'invoice_company'],
|
||||
'mail_text_waiting_list': ['event', 'url', 'product', 'hours', 'code'],
|
||||
'mail_text_order_canceled': ['code', 'event', 'url'],
|
||||
'mail_text_order_custom_mail': ['expire_date', 'event', 'code', 'date', 'url',
|
||||
'invoice_name', 'invoice_company'],
|
||||
'mail_text_download_reminder': ['event', 'url'],
|
||||
'mail_text_download_reminder_attendee': ['attendee_name', 'event', 'url'],
|
||||
'mail_text_order_placed_require_approval': ['total', 'currency', 'date', 'invoice_company',
|
||||
'total_with_currency', 'event', 'url', 'invoice_name'],
|
||||
'mail_text_order_approved': ['total', 'currency', 'date', 'invoice_company',
|
||||
'total_with_currency', 'event', 'url', 'invoice_name'],
|
||||
'mail_text_order_denied': ['total', 'currency', 'date', 'invoice_company',
|
||||
'total_with_currency', 'event', 'url', 'invoice_name'],
|
||||
}
|
||||
for v in kv.values():
|
||||
for p in self.meta_properties:
|
||||
v.append('meta_' + p)
|
||||
return kv
|
||||
|
||||
@cached_property
|
||||
def base_data(self):
|
||||
user_orders = [
|
||||
{'code': 'F8VVL', 'secret': '6zzjnumtsx136ddy'},
|
||||
{'code': 'HIDHK', 'secret': '98kusd8ofsj8dnkd'},
|
||||
{'code': 'OPKSB', 'secret': '09pjdksflosk3njd'}
|
||||
]
|
||||
orders = [' - {} - {}'.format(self.generate_order_fullname(self.request.event.slug, order['code']),
|
||||
self.generate_order_url(order['code'], order['secret']))
|
||||
for order in user_orders]
|
||||
d = {
|
||||
'event': self.request.event.name,
|
||||
'total': 42.23,
|
||||
'total_with_currency': LazyCurrencyNumber(42.23, self.request.event.currency),
|
||||
'currency': self.request.event.currency,
|
||||
'url': self.generate_order_url(user_orders[0]['code'], user_orders[0]['secret']),
|
||||
'orders': '\n'.join(orders),
|
||||
'hours': self.request.event.settings.waiting_list_hours,
|
||||
'product': _('Sample Admission Ticket'),
|
||||
'code': '68CYU2H6ZTP3WLK5',
|
||||
'invoice_name': _('John Doe'),
|
||||
'invoice_company': _('Sample Corporation'),
|
||||
'common': _('An individual text with a reason can be inserted here.'),
|
||||
'payment_info': _('Please transfer money to this bank account: 9999-9999-9999-9999'),
|
||||
'attendee_name': _('John Doe'),
|
||||
}
|
||||
for k, v in self.request.event.meta_data.items():
|
||||
d['meta_' + k] = v
|
||||
return d
|
||||
|
||||
def generate_order_url(self, code, secret):
|
||||
return build_absolute_uri('presale:event.order', kwargs={
|
||||
'event': self.request.event.slug,
|
||||
'organizer': self.request.event.organizer.slug,
|
||||
'order': code,
|
||||
'secret': secret
|
||||
})
|
||||
|
||||
# get all supported placeholders with dummy values
|
||||
def placeholders(self, item):
|
||||
supported = {}
|
||||
local_data = self.localized_data()
|
||||
for key in self.items.get(item):
|
||||
supported[key] = self.base_data.get(key) if key in self.base_data else local_data.get(key)
|
||||
return self.SafeDict(supported)
|
||||
ctx = {}
|
||||
for p in get_available_placeholders(self.request.event, MailSettingsForm.base_context[item]).values():
|
||||
s = str(p.render_sample(self.request.event))
|
||||
if s.strip().startswith('*'):
|
||||
ctx[p.identifier] = s
|
||||
else:
|
||||
ctx[p.identifier] = '<span class="placeholder" title="{}">{}</span>'.format(
|
||||
_('This value will be replaced based on dynamic parameters.'),
|
||||
s
|
||||
)
|
||||
return self.SafeDict(ctx)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
preview_item = request.POST.get('item', '')
|
||||
if preview_item not in self.items:
|
||||
if preview_item not in MailSettingsForm.base_context:
|
||||
return HttpResponseBadRequest(_('invalid item'))
|
||||
|
||||
regex = r"^" + re.escape(preview_item) + r"_(?P<idx>[\d+])$"
|
||||
|
||||
@@ -39,6 +39,7 @@ from pretix.control.permissions import (
|
||||
EventPermissionRequiredMixin, event_permission_required,
|
||||
)
|
||||
from pretix.control.signals import item_forms, item_formsets
|
||||
from pretix.helpers.models import modelcopy
|
||||
|
||||
from . import ChartContainingView, CreateView, PaginationMixin, UpdateView
|
||||
|
||||
@@ -189,6 +190,25 @@ class CategoryCreate(EventPermissionRequiredMixin, CreateView):
|
||||
'event': self.request.event.slug,
|
||||
})
|
||||
|
||||
@cached_property
|
||||
def copy_from(self):
|
||||
if self.request.GET.get("copy_from") and not getattr(self, 'object', None):
|
||||
try:
|
||||
return self.request.event.categories.get(pk=self.request.GET.get("copy_from"))
|
||||
except ItemCategory.DoesNotExist:
|
||||
pass
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
|
||||
if self.copy_from:
|
||||
i = modelcopy(self.copy_from)
|
||||
i.pk = None
|
||||
kwargs['instance'] = i
|
||||
else:
|
||||
kwargs['instance'] = ItemCategory(event=self.request.event)
|
||||
return kwargs
|
||||
|
||||
@transaction.atomic
|
||||
def form_valid(self, form):
|
||||
form.instance.event = self.request.event
|
||||
@@ -605,6 +625,29 @@ class QuotaCreate(EventPermissionRequiredMixin, CreateView):
|
||||
form.instance.log_action('pretix.event.quota.added', user=self.request.user, data=dict(form.cleaned_data))
|
||||
return ret
|
||||
|
||||
@cached_property
|
||||
def copy_from(self):
|
||||
if self.request.GET.get("copy_from") and not getattr(self, 'object', None):
|
||||
try:
|
||||
return self.request.event.quotas.get(pk=self.request.GET.get("copy_from"))
|
||||
except Quota.DoesNotExist:
|
||||
pass
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
|
||||
if self.copy_from:
|
||||
i = modelcopy(self.copy_from)
|
||||
i.pk = None
|
||||
kwargs['instance'] = i
|
||||
kwargs.setdefault('initial', {})
|
||||
kwargs['initial']['itemvars'] = [str(i.pk) for i in self.copy_from.items.all()] + [
|
||||
'{}-{}'.format(v.item_id, v.pk) for v in self.copy_from.variations.all()
|
||||
]
|
||||
else:
|
||||
kwargs['instance'] = Quota(event=self.request.event)
|
||||
return kwargs
|
||||
|
||||
def form_invalid(self, form):
|
||||
messages.error(self.request, _('We could not save your changes. See below for details.'))
|
||||
return super().form_invalid(form)
|
||||
|
||||
@@ -141,6 +141,17 @@ class EventWizard(SafeSessionWizardView):
|
||||
initial['locale'] = self.clone_from.settings.locale
|
||||
if self.clone_from.settings.tax_rate_default:
|
||||
initial['tax_rate'] = self.clone_from.settings.tax_rate_default.rate
|
||||
if 'organizer' in self.request.GET:
|
||||
if step == 'foundation':
|
||||
try:
|
||||
qs = Organizer.objects.all()
|
||||
if not self.request.user.has_active_staff_session(self.request.session.session_key):
|
||||
qs = qs.filter(
|
||||
id__in=self.request.user.teams.filter(can_create_events=True).values_list('organizer', flat=True)
|
||||
)
|
||||
initial['organizer'] = qs.get(slug=self.request.GET.get('organizer'))
|
||||
except Organizer.DoesNotExist:
|
||||
pass
|
||||
|
||||
return initial
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import re
|
||||
from datetime import datetime, time, timedelta
|
||||
from decimal import Decimal, DecimalException
|
||||
|
||||
import pytz
|
||||
import vat_moss.id
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
@@ -15,13 +14,13 @@ from django.db import transaction
|
||||
from django.db.models import (
|
||||
Count, IntegerField, OuterRef, Prefetch, ProtectedError, Q, Subquery, Sum,
|
||||
)
|
||||
from django.forms import formset_factory
|
||||
from django.http import (
|
||||
FileResponse, Http404, HttpResponseNotAllowed, JsonResponse,
|
||||
)
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.urls import reverse
|
||||
from django.utils import formats
|
||||
from django.utils.formats import date_format
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.http import is_safe_url
|
||||
from django.utils.timezone import make_aware, now
|
||||
@@ -32,6 +31,7 @@ from django.views.generic import (
|
||||
from i18nfield.strings import LazyI18nString
|
||||
|
||||
from pretix.base.channels import get_all_sales_channels
|
||||
from pretix.base.email import get_email_context
|
||||
from pretix.base.i18n import language
|
||||
from pretix.base.models import (
|
||||
CachedCombinedTicket, CachedFile, CachedTicket, Invoice, InvoiceAddress,
|
||||
@@ -71,13 +71,12 @@ from pretix.control.forms.filter import (
|
||||
from pretix.control.forms.orders import (
|
||||
CancelForm, CommentForm, ConfirmPaymentForm, ExporterForm, ExtendForm,
|
||||
MarkPaidForm, OrderContactForm, OrderLocaleForm, OrderMailForm,
|
||||
OrderPositionAddForm, OrderPositionChangeForm, OrderRefundForm,
|
||||
OtherOperationsForm,
|
||||
OrderPositionAddForm, OrderPositionAddFormset, OrderPositionChangeForm,
|
||||
OrderRefundForm, OtherOperationsForm,
|
||||
)
|
||||
from pretix.control.permissions import EventPermissionRequiredMixin
|
||||
from pretix.control.views import PaginationMixin
|
||||
from pretix.helpers.safedownload import check_token
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.presale.signals import question_form_fields
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -1067,7 +1066,11 @@ class OrderResendLink(OrderView):
|
||||
|
||||
def post(self, *args, **kwargs):
|
||||
try:
|
||||
self.order.resend_link(user=self.request.user)
|
||||
if 'position' in kwargs:
|
||||
p = get_object_or_404(self.order.positions, pk=kwargs['position'])
|
||||
p.resend_link(user=self.request.user)
|
||||
else:
|
||||
self.order.resend_link(user=self.request.user)
|
||||
except SendMailException:
|
||||
messages.error(self.request, _('There was an error sending the mail. Please try again later.'))
|
||||
return redirect(self.get_order_url())
|
||||
@@ -1186,9 +1189,16 @@ class OrderChange(OrderView):
|
||||
data=self.request.POST if self.request.method == "POST" else None)
|
||||
|
||||
@cached_property
|
||||
def add_form(self):
|
||||
return OrderPositionAddForm(prefix='add', order=self.order,
|
||||
data=self.request.POST if self.request.method == "POST" else None)
|
||||
def add_formset(self):
|
||||
ff = formset_factory(
|
||||
OrderPositionAddForm, formset=OrderPositionAddFormset,
|
||||
can_order=False, can_delete=True, extra=0
|
||||
)
|
||||
return ff(
|
||||
prefix='add',
|
||||
order=self.order,
|
||||
data=self.request.POST if self.request.method == "POST" else None
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def positions(self):
|
||||
@@ -1206,7 +1216,7 @@ class OrderChange(OrderView):
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
ctx['positions'] = self.positions
|
||||
ctx['add_form'] = self.add_form
|
||||
ctx['add_formset'] = self.add_formset
|
||||
ctx['other_form'] = self.other_form
|
||||
return ctx
|
||||
|
||||
@@ -1219,16 +1229,17 @@ class OrderChange(OrderView):
|
||||
return True
|
||||
|
||||
def _process_add(self, ocm):
|
||||
if 'add-do' not in self.request.POST:
|
||||
return True
|
||||
if not self.add_form.is_valid():
|
||||
if not self.add_formset.is_valid():
|
||||
return False
|
||||
else:
|
||||
if self.add_form.cleaned_data['do']:
|
||||
if '-' in self.add_form.cleaned_data['itemvar']:
|
||||
itemid, varid = self.add_form.cleaned_data['itemvar'].split('-')
|
||||
for f in self.add_formset.forms:
|
||||
if f in self.add_formset.deleted_forms or not f.has_changed():
|
||||
continue
|
||||
|
||||
if '-' in f.cleaned_data['itemvar']:
|
||||
itemid, varid = f.cleaned_data['itemvar'].split('-')
|
||||
else:
|
||||
itemid, varid = self.add_form.cleaned_data['itemvar'], None
|
||||
itemid, varid = f.cleaned_data['itemvar'], None
|
||||
|
||||
item = Item.objects.get(pk=itemid, event=self.request.event)
|
||||
if varid:
|
||||
@@ -1237,12 +1248,12 @@ class OrderChange(OrderView):
|
||||
variation = None
|
||||
try:
|
||||
ocm.add_position(item, variation,
|
||||
self.add_form.cleaned_data['price'],
|
||||
self.add_form.cleaned_data.get('addon_to'),
|
||||
self.add_form.cleaned_data.get('subevent'),
|
||||
self.add_form.cleaned_data.get('seat'))
|
||||
f.cleaned_data['price'],
|
||||
f.cleaned_data.get('addon_to'),
|
||||
f.cleaned_data.get('subevent'),
|
||||
f.cleaned_data.get('seat'))
|
||||
except OrderError as e:
|
||||
self.add_form.custom_error = str(e)
|
||||
f.custom_error = str(e)
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -1295,7 +1306,8 @@ class OrderChange(OrderView):
|
||||
ocm = OrderChangeManager(
|
||||
self.order,
|
||||
user=self.request.user,
|
||||
notify=notify
|
||||
notify=notify,
|
||||
reissue_invoice=self.other_form.cleaned_data['reissue_invoice'] if self.other_form.is_valid() else True
|
||||
)
|
||||
form_valid = self._process_add(ocm) and self._process_change(ocm) and self._process_other(ocm)
|
||||
|
||||
@@ -1486,32 +1498,13 @@ class OrderSendMail(EventPermissionRequiredMixin, OrderViewMixin, FormView):
|
||||
return super().form_invalid(form)
|
||||
|
||||
def form_valid(self, form):
|
||||
tz = pytz.timezone(self.request.event.settings.timezone)
|
||||
order = Order.objects.get(
|
||||
event=self.request.event,
|
||||
code=self.kwargs['code'].upper()
|
||||
)
|
||||
self.preview_output = {}
|
||||
try:
|
||||
invoice_name = order.invoice_address.name
|
||||
invoice_company = order.invoice_address.company
|
||||
except InvoiceAddress.DoesNotExist:
|
||||
invoice_name = ""
|
||||
invoice_company = ""
|
||||
with language(order.locale):
|
||||
email_context = {
|
||||
'event': order.event,
|
||||
'code': order.code,
|
||||
'date': date_format(order.datetime.astimezone(tz), 'SHORT_DATETIME_FORMAT'),
|
||||
'expire_date': date_format(order.expires, 'SHORT_DATE_FORMAT'),
|
||||
'url': build_absolute_uri(order.event, 'presale:event.order.open', kwargs={
|
||||
'order': order.code,
|
||||
'secret': order.secret,
|
||||
'hash': order.email_confirm_hash()
|
||||
}),
|
||||
'invoice_name': invoice_name,
|
||||
'invoice_company': invoice_company,
|
||||
}
|
||||
email_context = get_email_context(event=order.event, order=order)
|
||||
email_template = LazyI18nString(form.cleaned_data['message'])
|
||||
email_content = render_mail(email_template, email_context)
|
||||
if self.request.POST.get('action') == 'preview':
|
||||
@@ -1557,10 +1550,11 @@ class OrderEmailHistory(EventPermissionRequiredMixin, OrderViewMixin, ListView):
|
||||
paginate_by = 10
|
||||
|
||||
def get_queryset(self):
|
||||
order = Order.objects.filter(
|
||||
order = get_object_or_404(
|
||||
Order,
|
||||
event=self.request.event,
|
||||
code=self.kwargs['code'].upper()
|
||||
).first()
|
||||
)
|
||||
qs = order.all_logentries()
|
||||
qs = qs.filter(
|
||||
action_type__contains="order.email"
|
||||
|
||||
@@ -92,6 +92,7 @@ class OrganizerDetail(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin
|
||||
template_name = 'pretixcontrol/organizers/detail.html'
|
||||
permission = None
|
||||
context_object_name = 'events'
|
||||
paginate_by = 50
|
||||
|
||||
@property
|
||||
def organizer(self):
|
||||
|
||||
@@ -123,10 +123,16 @@ class BaseEditorView(EventPermissionRequiredMixin, TemplateView):
|
||||
def post(self, request, *args, **kwargs):
|
||||
if "emptybackground" in request.POST:
|
||||
p = PdfFileWriter()
|
||||
p.addBlankPage(
|
||||
width=float(request.POST.get('width')) * mm,
|
||||
height=float(request.POST.get('height')) * mm,
|
||||
)
|
||||
try:
|
||||
p.addBlankPage(
|
||||
width=float(request.POST.get('width')) * mm,
|
||||
height=float(request.POST.get('height')) * mm,
|
||||
)
|
||||
except ValueError:
|
||||
return JsonResponse({
|
||||
"status": "error",
|
||||
"error": "Invalid height/width given."
|
||||
})
|
||||
buffer = BytesIO()
|
||||
p.write(buffer)
|
||||
buffer.seek(0)
|
||||
|
||||
@@ -12,6 +12,8 @@ from django.http import (
|
||||
from django.shortcuts import redirect, render
|
||||
from django.urls import resolve, reverse
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.html import format_html
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.views.generic import (
|
||||
CreateView, DeleteView, ListView, TemplateView, UpdateView, View,
|
||||
@@ -19,11 +21,12 @@ from django.views.generic import (
|
||||
|
||||
from pretix.base.models import CartPosition, LogEntry, OrderPosition, Voucher
|
||||
from pretix.base.models.vouchers import _generate_random_code
|
||||
from pretix.control.forms.filter import VoucherFilterForm
|
||||
from pretix.control.forms.filter import VoucherFilterForm, VoucherTagFilterForm
|
||||
from pretix.control.forms.vouchers import VoucherBulkForm, VoucherForm
|
||||
from pretix.control.permissions import EventPermissionRequiredMixin
|
||||
from pretix.control.signals import voucher_form_class
|
||||
from pretix.control.views import PaginationMixin
|
||||
from pretix.helpers.models import modelcopy
|
||||
|
||||
|
||||
class VoucherList(PaginationMixin, EventPermissionRequiredMixin, ListView):
|
||||
@@ -94,16 +97,27 @@ class VoucherTags(EventPermissionRequiredMixin, TemplateView):
|
||||
template_name = 'pretixcontrol/vouchers/tags.html'
|
||||
permission = 'can_view_vouchers'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
|
||||
tags = self.request.event.vouchers.order_by('tag').filter(
|
||||
def get_queryset(self):
|
||||
qs = self.request.event.vouchers.order_by('tag').filter(
|
||||
tag__isnull=False,
|
||||
waitinglistentries__isnull=True
|
||||
).values('tag').annotate(
|
||||
)
|
||||
|
||||
if self.filter_form.is_valid():
|
||||
qs = self.filter_form.filter_qs(qs)
|
||||
|
||||
qs = qs.values('tag').annotate(
|
||||
total=Sum('max_usages'),
|
||||
redeemed=Sum('redeemed')
|
||||
)
|
||||
|
||||
return qs.distinct()
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
|
||||
tags = self.get_queryset()
|
||||
|
||||
for t in tags:
|
||||
if t['total'] == 0:
|
||||
t['percentage'] = 0
|
||||
@@ -111,8 +125,13 @@ class VoucherTags(EventPermissionRequiredMixin, TemplateView):
|
||||
t['percentage'] = int((t['redeemed'] / t['total']) * 100)
|
||||
|
||||
ctx['tags'] = tags
|
||||
ctx['filter_form'] = self.filter_form
|
||||
return ctx
|
||||
|
||||
@cached_property
|
||||
def filter_form(self):
|
||||
return VoucherTagFilterForm(data=self.request.GET, event=self.request.event)
|
||||
|
||||
|
||||
class VoucherDelete(EventPermissionRequiredMixin, DeleteView):
|
||||
model = Voucher
|
||||
@@ -223,8 +242,15 @@ class VoucherCreate(EventPermissionRequiredMixin, CreateView):
|
||||
@transaction.atomic
|
||||
def form_valid(self, form):
|
||||
form.instance.event = self.request.event
|
||||
messages.success(self.request, _('The new voucher has been created: {code}').format(code=form.instance.code))
|
||||
ret = super().form_valid(form)
|
||||
url = reverse('control:event.voucher', kwargs={
|
||||
'organizer': self.request.event.organizer.slug,
|
||||
'event': self.request.event.slug,
|
||||
'voucher': self.object.pk
|
||||
})
|
||||
messages.success(self.request, mark_safe(_('The new voucher has been created: {code}').format(
|
||||
code=format_html('<a href="{url}">{code}</a>', url=url, code=self.object.code)
|
||||
)))
|
||||
form.instance.log_action('pretix.voucher.added', data=dict(form.cleaned_data), user=self.request.user)
|
||||
return ret
|
||||
|
||||
@@ -263,9 +289,23 @@ class VoucherBulkCreate(EventPermissionRequiredMixin, CreateView):
|
||||
'event': self.request.event.slug,
|
||||
})
|
||||
|
||||
@cached_property
|
||||
def copy_from(self):
|
||||
if self.request.GET.get("copy_from") and not getattr(self, 'object', None):
|
||||
try:
|
||||
return self.request.event.vouchers.get(pk=self.request.GET.get("copy_from"))
|
||||
except Voucher.DoesNotExist:
|
||||
pass
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
kwargs['instance'] = Voucher(event=self.request.event)
|
||||
|
||||
if self.copy_from:
|
||||
i = modelcopy(self.copy_from)
|
||||
i.pk = None
|
||||
kwargs['instance'] = i
|
||||
else:
|
||||
kwargs['instance'] = Voucher(event=self.request.event)
|
||||
return kwargs
|
||||
|
||||
@transaction.atomic
|
||||
|
||||
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-09-10 08:00+0000\n"
|
||||
"POT-Creation-Date: 2019-10-08 13:57+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: Automatically generated\n"
|
||||
"Language-Team: none\n"
|
||||
@@ -77,7 +77,7 @@ msgstr ""
|
||||
#: pretix/static/pretixbase/js/asynctask.js:76
|
||||
#: pretix/static/pretixbase/js/asynctask.js:142
|
||||
#: pretix/static/pretixbase/js/asynctask.js:147
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:23
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:24
|
||||
msgid "An error of type {code} occurred."
|
||||
msgstr ""
|
||||
|
||||
@@ -88,12 +88,12 @@ msgid ""
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:125
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:20
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:21
|
||||
msgid "The request took to long. Please try again."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:150
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:25
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:26
|
||||
msgid ""
|
||||
"We currently cannot reach the server. Please try again. Error code: {code}"
|
||||
msgstr ""
|
||||
@@ -171,11 +171,11 @@ msgstr ""
|
||||
msgid "Do you really want to leave the editor without saving your changes?"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:18
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:19
|
||||
msgid "An error has occurred."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:52
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:54
|
||||
msgid "Generating messages …"
|
||||
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-09-10 08:00+0000\n"
|
||||
"POT-Creation-Date: 2019-10-08 13:57+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: Automatically generated\n"
|
||||
"Language-Team: none\n"
|
||||
@@ -76,7 +76,7 @@ msgstr ""
|
||||
#: pretix/static/pretixbase/js/asynctask.js:76
|
||||
#: pretix/static/pretixbase/js/asynctask.js:142
|
||||
#: pretix/static/pretixbase/js/asynctask.js:147
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:23
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:24
|
||||
msgid "An error of type {code} occurred."
|
||||
msgstr ""
|
||||
|
||||
@@ -87,12 +87,12 @@ msgid ""
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:125
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:20
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:21
|
||||
msgid "The request took to long. Please try again."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:150
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:25
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:26
|
||||
msgid ""
|
||||
"We currently cannot reach the server. Please try again. Error code: {code}"
|
||||
msgstr ""
|
||||
@@ -170,11 +170,11 @@ msgstr ""
|
||||
msgid "Do you really want to leave the editor without saving your changes?"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:18
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:19
|
||||
msgid "An error has occurred."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:52
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:54
|
||||
msgid "Generating messages …"
|
||||
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-09-10 08:00+0000\n"
|
||||
"POT-Creation-Date: 2019-10-08 13:57+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: Automatically generated\n"
|
||||
"Language-Team: none\n"
|
||||
@@ -76,7 +76,7 @@ msgstr ""
|
||||
#: pretix/static/pretixbase/js/asynctask.js:76
|
||||
#: pretix/static/pretixbase/js/asynctask.js:142
|
||||
#: pretix/static/pretixbase/js/asynctask.js:147
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:23
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:24
|
||||
msgid "An error of type {code} occurred."
|
||||
msgstr ""
|
||||
|
||||
@@ -87,12 +87,12 @@ msgid ""
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:125
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:20
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:21
|
||||
msgid "The request took to long. Please try again."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:150
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:25
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:26
|
||||
msgid ""
|
||||
"We currently cannot reach the server. Please try again. Error code: {code}"
|
||||
msgstr ""
|
||||
@@ -170,11 +170,11 @@ msgstr ""
|
||||
msgid "Do you really want to leave the editor without saving your changes?"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:18
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:19
|
||||
msgid "An error has occurred."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:52
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:54
|
||||
msgid "Generating messages …"
|
||||
msgstr ""
|
||||
|
||||
|
||||
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-09-10 08:00+0000\n"
|
||||
"POT-Creation-Date: 2019-10-08 13:57+0000\n"
|
||||
"PO-Revision-Date: 2019-09-05 18:00+0000\n"
|
||||
"Last-Translator: Ture Gjørup <ture@ignatz.dk>\n"
|
||||
"Language-Team: Danish <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
@@ -81,7 +81,7 @@ msgstr ""
|
||||
#: pretix/static/pretixbase/js/asynctask.js:76
|
||||
#: pretix/static/pretixbase/js/asynctask.js:142
|
||||
#: pretix/static/pretixbase/js/asynctask.js:147
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:23
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:24
|
||||
msgid "An error of type {code} occurred."
|
||||
msgstr "Der er sket en fejl ({code})."
|
||||
|
||||
@@ -94,12 +94,12 @@ msgstr ""
|
||||
"{code}"
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:125
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:20
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:21
|
||||
msgid "The request took to long. Please try again."
|
||||
msgstr "Forespørgselen tog for lang tid. Prøv venligst igen."
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:150
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:25
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:26
|
||||
msgid ""
|
||||
"We currently cannot reach the server. Please try again. Error code: {code}"
|
||||
msgstr ""
|
||||
@@ -182,11 +182,11 @@ msgid "Do you really want to leave the editor without saving your changes?"
|
||||
msgstr ""
|
||||
"Er du sikker på at du vil forlade editoren uden at gemme dine ændringer?"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:18
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:19
|
||||
msgid "An error has occurred."
|
||||
msgstr "Der er sket en fejl."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:52
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:54
|
||||
msgid "Generating messages …"
|
||||
msgstr "Opretter beskeder …"
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-09-10 08:00+0000\n"
|
||||
"POT-Creation-Date: 2019-10-08 13:57+0000\n"
|
||||
"PO-Revision-Date: 2019-07-29 08:36+0000\n"
|
||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||
"Language-Team: German <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
@@ -84,7 +84,7 @@ msgstr ""
|
||||
#: pretix/static/pretixbase/js/asynctask.js:76
|
||||
#: pretix/static/pretixbase/js/asynctask.js:142
|
||||
#: pretix/static/pretixbase/js/asynctask.js:147
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:23
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:24
|
||||
msgid "An error of type {code} occurred."
|
||||
msgstr "Ein Fehler ist aufgetreten. Fehlercode: {code}"
|
||||
|
||||
@@ -97,12 +97,12 @@ msgstr ""
|
||||
"Letzter Fehlercode: {code}"
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:125
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:20
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:21
|
||||
msgid "The request took to long. Please try again."
|
||||
msgstr "Diese Anfrage hat zu lange gedauert. Bitte erneut versuchen."
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:150
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:25
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:26
|
||||
msgid ""
|
||||
"We currently cannot reach the server. Please try again. Error code: {code}"
|
||||
msgstr ""
|
||||
@@ -187,11 +187,11 @@ msgid "Do you really want to leave the editor without saving your changes?"
|
||||
msgstr ""
|
||||
"Möchten Sie den Editor wirklich schließen ohne Ihre Änderungen zu speichern?"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:18
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:19
|
||||
msgid "An error has occurred."
|
||||
msgstr "Ein Fehler ist aufgetreten."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:52
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:54
|
||||
msgid "Generating messages …"
|
||||
msgstr "Generiere Nachrichten…"
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-09-10 08:00+0000\n"
|
||||
"POT-Creation-Date: 2019-10-08 13:57+0000\n"
|
||||
"PO-Revision-Date: 2019-07-29 08:36+0000\n"
|
||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||
"Language-Team: German (informal) <https://translate.pretix.eu/projects/"
|
||||
@@ -83,7 +83,7 @@ msgstr ""
|
||||
#: pretix/static/pretixbase/js/asynctask.js:76
|
||||
#: pretix/static/pretixbase/js/asynctask.js:142
|
||||
#: pretix/static/pretixbase/js/asynctask.js:147
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:23
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:24
|
||||
msgid "An error of type {code} occurred."
|
||||
msgstr "Ein Fehler vom Typ {code} ist aufgetreten."
|
||||
|
||||
@@ -96,12 +96,12 @@ msgstr ""
|
||||
"Letzter Fehlercode: {code}"
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:125
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:20
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:21
|
||||
msgid "The request took to long. Please try again."
|
||||
msgstr "Diese Anfrage hat zu lange gedauert. Bitte erneut versuchen."
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:150
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:25
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:26
|
||||
msgid ""
|
||||
"We currently cannot reach the server. Please try again. Error code: {code}"
|
||||
msgstr ""
|
||||
@@ -186,11 +186,11 @@ msgid "Do you really want to leave the editor without saving your changes?"
|
||||
msgstr ""
|
||||
"Möchtest du den Editor wirklich schließen ohne Ihre Änderungen zu speichern?"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:18
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:19
|
||||
msgid "An error has occurred."
|
||||
msgstr "Ein Fehler ist aufgetreten."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:52
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:54
|
||||
msgid "Generating messages …"
|
||||
msgstr "Generiere Nachrichten…"
|
||||
|
||||
|
||||
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-09-10 08:00+0000\n"
|
||||
"POT-Creation-Date: 2019-10-08 13:57+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"
|
||||
@@ -77,7 +77,7 @@ msgstr ""
|
||||
#: pretix/static/pretixbase/js/asynctask.js:76
|
||||
#: pretix/static/pretixbase/js/asynctask.js:142
|
||||
#: pretix/static/pretixbase/js/asynctask.js:147
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:23
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:24
|
||||
msgid "An error of type {code} occurred."
|
||||
msgstr ""
|
||||
|
||||
@@ -88,12 +88,12 @@ msgid ""
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:125
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:20
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:21
|
||||
msgid "The request took to long. Please try again."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:150
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:25
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:26
|
||||
msgid ""
|
||||
"We currently cannot reach the server. Please try again. Error code: {code}"
|
||||
msgstr ""
|
||||
@@ -171,11 +171,11 @@ msgstr ""
|
||||
msgid "Do you really want to leave the editor without saving your changes?"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:18
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:19
|
||||
msgid "An error has occurred."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:52
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:54
|
||||
msgid "Generating messages …"
|
||||
msgstr ""
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,9 +7,9 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-09-10 08:00+0000\n"
|
||||
"PO-Revision-Date: 2019-06-04 16:00+0000\n"
|
||||
"Last-Translator: ThanosTeste <testebasisth@unisystems.eu>\n"
|
||||
"POT-Creation-Date: 2019-10-08 13:57+0000\n"
|
||||
"PO-Revision-Date: 2019-10-03 19:00+0000\n"
|
||||
"Last-Translator: Chris Spy <chrispiropoulou@hotmail.com>\n"
|
||||
"Language-Team: Greek <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
"el/>\n"
|
||||
"Language: el\n"
|
||||
@@ -86,7 +86,7 @@ msgstr ""
|
||||
#: pretix/static/pretixbase/js/asynctask.js:76
|
||||
#: pretix/static/pretixbase/js/asynctask.js:142
|
||||
#: pretix/static/pretixbase/js/asynctask.js:147
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:23
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:24
|
||||
msgid "An error of type {code} occurred."
|
||||
msgstr "Παρουσιάστηκε σφάλμα τύπου {code}."
|
||||
|
||||
@@ -99,12 +99,12 @@ msgstr ""
|
||||
"προσπαθούμε. Τελευταίος κωδικός σφάλματος: {code}"
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:125
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:20
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:21
|
||||
msgid "The request took to long. Please try again."
|
||||
msgstr "Το αίτημα διήρκησε πολύ. Παρακαλώ προσπαθήστε ξανά."
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:150
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:25
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:26
|
||||
msgid ""
|
||||
"We currently cannot reach the server. Please try again. Error code: {code}"
|
||||
msgstr ""
|
||||
@@ -190,17 +190,17 @@ msgstr ""
|
||||
"Θέλετε πραγματικά να αφήσετε τον επεξεργαστή χωρίς να αποθηκεύσετε τις "
|
||||
"αλλαγές σας;"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:18
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:19
|
||||
msgid "An error has occurred."
|
||||
msgstr "Παρουσιάστηκε σφάλμα."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:52
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:54
|
||||
msgid "Generating messages …"
|
||||
msgstr "Δημιουργία μηνυμάτων …"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:69
|
||||
msgid "Unknown error."
|
||||
msgstr "Αγνωστο σφάλμα."
|
||||
msgstr "Άγνωστο σφάλμα."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:231
|
||||
msgid "Your color has great contrast and is very easy to read!"
|
||||
@@ -223,7 +223,7 @@ msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:355
|
||||
msgid "All"
|
||||
msgstr "Ολα"
|
||||
msgstr "Όλα"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:356
|
||||
msgid "None"
|
||||
|
||||
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-09-10 08:00+0000\n"
|
||||
"POT-Creation-Date: 2019-10-08 13:57+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-"
|
||||
@@ -85,7 +85,7 @@ msgstr ""
|
||||
#: pretix/static/pretixbase/js/asynctask.js:76
|
||||
#: pretix/static/pretixbase/js/asynctask.js:142
|
||||
#: pretix/static/pretixbase/js/asynctask.js:147
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:23
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:24
|
||||
msgid "An error of type {code} occurred."
|
||||
msgstr "Ha ocurrido un error de tipo {code}."
|
||||
|
||||
@@ -98,12 +98,12 @@ msgstr ""
|
||||
"intentando. El último código de error fue: {code}"
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:125
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:20
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:21
|
||||
msgid "The request took to long. Please try again."
|
||||
msgstr "La solicitud ha tomado demasiado tiempo. Por favor, pruebe de nuevo."
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:150
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:25
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:26
|
||||
msgid ""
|
||||
"We currently cannot reach the server. Please try again. Error code: {code}"
|
||||
msgstr ""
|
||||
@@ -189,11 +189,11 @@ msgstr ""
|
||||
msgid "Do you really want to leave the editor without saving your changes?"
|
||||
msgstr "¿Realmente desea salir del editor sin haber guardado sus cambios?"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:18
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:19
|
||||
msgid "An error has occurred."
|
||||
msgstr "Ha ocurrido un error."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:52
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:54
|
||||
msgid "Generating messages …"
|
||||
msgstr "Generando mensajes…"
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,9 +6,9 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: French\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-09-10 08:00+0000\n"
|
||||
"PO-Revision-Date: 2019-08-01 18:41+0000\n"
|
||||
"Last-Translator: Martin Gross <martin@pc-coholic.de>\n"
|
||||
"POT-Creation-Date: 2019-10-08 13:57+0000\n"
|
||||
"PO-Revision-Date: 2019-10-01 01:00+0000\n"
|
||||
"Last-Translator: Fabian Rodriguez <magicfab@legoutdulibre.com>\n"
|
||||
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
"fr/>\n"
|
||||
"Language: fr\n"
|
||||
@@ -53,13 +53,11 @@ msgstr "Total"
|
||||
#: pretix/plugins/stripe/static/pretixplugins/stripe/pretix-stripe.js:146
|
||||
#: pretix/plugins/stripe/static/pretixplugins/stripe/pretix-stripe.js:177
|
||||
msgid "Confirming your payment …"
|
||||
msgstr ""
|
||||
msgstr "Confirmation de votre paiment…"
|
||||
|
||||
#: pretix/plugins/stripe/static/pretixplugins/stripe/pretix-stripe.js:153
|
||||
#, fuzzy
|
||||
#| msgid "Contacting Stripe …"
|
||||
msgid "Contacting your bank …"
|
||||
msgstr "Contacter Stripe …"
|
||||
msgstr "Communication avec votre banque …"
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:39
|
||||
#: pretix/static/pretixbase/js/asynctask.js:105
|
||||
@@ -72,11 +70,6 @@ msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:45
|
||||
#: pretix/static/pretixbase/js/asynctask.js:111
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "Your request has been queued on the server and will now be processed. If "
|
||||
#| "this takes longer than two minutes, please contact us or go back in your "
|
||||
#| "browser and try again."
|
||||
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 "
|
||||
@@ -89,7 +82,7 @@ msgstr ""
|
||||
#: pretix/static/pretixbase/js/asynctask.js:76
|
||||
#: pretix/static/pretixbase/js/asynctask.js:142
|
||||
#: pretix/static/pretixbase/js/asynctask.js:147
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:23
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:24
|
||||
msgid "An error of type {code} occurred."
|
||||
msgstr "Une erreur de type {code} s'est produite."
|
||||
|
||||
@@ -102,12 +95,12 @@ msgstr ""
|
||||
"d'essayer. Dernier code d'erreur: {code}"
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:125
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:20
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:21
|
||||
msgid "The request took to long. Please try again."
|
||||
msgstr "La requête a prit trop de temps. Veuillez réessayer."
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:150
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:25
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:26
|
||||
msgid ""
|
||||
"We currently cannot reach the server. Please try again. Error code: {code}"
|
||||
msgstr ""
|
||||
@@ -194,11 +187,11 @@ msgid "Do you really want to leave the editor without saving your changes?"
|
||||
msgstr ""
|
||||
"Voulez-vous vraiment quitter l'éditeur sans sauvegarder vos modifications ?"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:18
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:19
|
||||
msgid "An error has occurred."
|
||||
msgstr "Une erreur s'est produite."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:52
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:54
|
||||
msgid "Generating messages …"
|
||||
msgstr "Création de messages …"
|
||||
|
||||
@@ -209,16 +202,20 @@ msgstr "Erreur inconnue."
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:231
|
||||
msgid "Your color has great contrast and is very easy to read!"
|
||||
msgstr ""
|
||||
"Votre choix de couleur est très facile à lire, il a un excellent contraste !"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:235
|
||||
msgid "Your color has decent contrast and is probably good-enough to read!"
|
||||
msgstr ""
|
||||
"Votre choix de couleur est assez bon pour la lecture et a un bon contraste !"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:239
|
||||
msgid ""
|
||||
"Your color has bad contrast for text on white background, please choose a "
|
||||
"darker shade."
|
||||
msgstr ""
|
||||
"Votre choix de couleur n'a pas un bon contraste avec du texte sur un fond "
|
||||
"blanc, SVP choisissez un ton plus sombre."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:355
|
||||
msgid "All"
|
||||
@@ -234,13 +231,11 @@ msgstr "Utiliser un nom différent en interne"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:702
|
||||
msgid "Click to close"
|
||||
msgstr ""
|
||||
msgstr "Cliquez pour fermer"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/orderchange.js:24
|
||||
#, fuzzy
|
||||
#| msgid "Contacting Stripe …"
|
||||
msgid "Calculating default price…"
|
||||
msgstr "Contacter Stripe …"
|
||||
msgstr "Calcul du prix par défaut …"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:42
|
||||
msgid "Others"
|
||||
@@ -252,13 +247,11 @@ msgstr "Compter"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:120
|
||||
msgid "Yes"
|
||||
msgstr ""
|
||||
msgstr "Oui"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:121
|
||||
#, fuzzy
|
||||
#| msgid "None"
|
||||
msgid "No"
|
||||
msgstr "Aucun"
|
||||
msgstr "Non"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/subevent.js:108
|
||||
msgid "(one more date)"
|
||||
@@ -282,7 +275,7 @@ msgstr[1] "Les articles de votre panier vous sont réservés pour {num} minutes.
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:212
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr ""
|
||||
msgstr "SVP entrez une quantité pour un de vos types de billets."
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:15
|
||||
msgctxt "widget"
|
||||
@@ -327,12 +320,12 @@ msgstr "plus %(rate)s% %(taxname)s"
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:23
|
||||
msgctxt "widget"
|
||||
msgid "incl. taxes"
|
||||
msgstr ""
|
||||
msgstr "taxes incluses"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:24
|
||||
msgctxt "widget"
|
||||
msgid "plus taxes"
|
||||
msgstr ""
|
||||
msgstr "taxes en sus"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:25
|
||||
#, javascript-format
|
||||
@@ -425,11 +418,9 @@ msgid "See variations"
|
||||
msgstr "Voir les variations"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:43
|
||||
#, fuzzy
|
||||
#| msgid "Use a different name internally"
|
||||
msgctxt "widget"
|
||||
msgid "Choose a different event"
|
||||
msgstr "Utiliser un nom différent en interne"
|
||||
msgstr "Choisissez un autre événement"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:44
|
||||
msgctxt "widget"
|
||||
@@ -439,98 +430,98 @@ msgstr "Choisir une autre date"
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:45
|
||||
msgctxt "widget"
|
||||
msgid "Back"
|
||||
msgstr ""
|
||||
msgstr "Retour"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:46
|
||||
msgctxt "widget"
|
||||
msgid "Next month"
|
||||
msgstr ""
|
||||
msgstr "Mois suivant"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:47
|
||||
msgctxt "widget"
|
||||
msgid "Previous month"
|
||||
msgstr ""
|
||||
msgstr "Moins précédent"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:48
|
||||
msgctxt "widget"
|
||||
msgid "Open seat selection"
|
||||
msgstr ""
|
||||
msgstr "Ouvrir la sélection de sièges"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:50
|
||||
msgid "Mo"
|
||||
msgstr ""
|
||||
msgstr "Lu"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:51
|
||||
msgid "Tu"
|
||||
msgstr ""
|
||||
msgstr "Ma"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:52
|
||||
msgid "We"
|
||||
msgstr ""
|
||||
msgstr "Me"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:53
|
||||
msgid "Th"
|
||||
msgstr ""
|
||||
msgstr "Je"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:54
|
||||
msgid "Fr"
|
||||
msgstr ""
|
||||
msgstr "Ve"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:55
|
||||
msgid "Sa"
|
||||
msgstr ""
|
||||
msgstr "Sa"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:56
|
||||
msgid "Su"
|
||||
msgstr ""
|
||||
msgstr "Di"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:59
|
||||
msgid "January"
|
||||
msgstr ""
|
||||
msgstr "Janvier"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:60
|
||||
msgid "February"
|
||||
msgstr ""
|
||||
msgstr "Février"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:61
|
||||
msgid "March"
|
||||
msgstr ""
|
||||
msgstr "Mars"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:62
|
||||
msgid "April"
|
||||
msgstr ""
|
||||
msgstr "Avril"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:63
|
||||
msgid "May"
|
||||
msgstr ""
|
||||
msgstr "Mai"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:64
|
||||
msgid "June"
|
||||
msgstr ""
|
||||
msgstr "Juin"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:65
|
||||
msgid "July"
|
||||
msgstr ""
|
||||
msgstr "Juillet"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:66
|
||||
msgid "August"
|
||||
msgstr ""
|
||||
msgstr "Août"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:67
|
||||
msgid "September"
|
||||
msgstr ""
|
||||
msgstr "Septembre"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:68
|
||||
msgid "October"
|
||||
msgstr ""
|
||||
msgstr "Octobre"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:69
|
||||
msgid "November"
|
||||
msgstr ""
|
||||
msgstr "Novembre"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:70
|
||||
msgid "December"
|
||||
msgstr ""
|
||||
msgstr "Décembre"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "Your request has been queued on the server and will now be processed. If "
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,9 +7,9 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-09-10 08:00+0000\n"
|
||||
"PO-Revision-Date: 2019-01-02 08:20+0000\n"
|
||||
"Last-Translator: amefad <fame@libero.it>\n"
|
||||
"POT-Creation-Date: 2019-10-08 13:57+0000\n"
|
||||
"PO-Revision-Date: 2019-09-13 18:00+0000\n"
|
||||
"Last-Translator: Gianmarco Palumbo <pal_gm@hotmail.it>\n"
|
||||
"Language-Team: Italian <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
"js/it/>\n"
|
||||
"Language: it\n"
|
||||
@@ -17,7 +17,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 3.1.1\n"
|
||||
"X-Generator: Weblate 3.5.1\n"
|
||||
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
|
||||
@@ -54,13 +54,11 @@ msgstr "Totale"
|
||||
#: pretix/plugins/stripe/static/pretixplugins/stripe/pretix-stripe.js:146
|
||||
#: pretix/plugins/stripe/static/pretixplugins/stripe/pretix-stripe.js:177
|
||||
msgid "Confirming your payment …"
|
||||
msgstr ""
|
||||
msgstr "Stiamo processando il tuo pagamento ..."
|
||||
|
||||
#: pretix/plugins/stripe/static/pretixplugins/stripe/pretix-stripe.js:153
|
||||
#, fuzzy
|
||||
#| msgid "Contacting Stripe …"
|
||||
msgid "Contacting your bank …"
|
||||
msgstr "Sto contattando Stripe …"
|
||||
msgstr "Sto contattando la tua banca …"
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:39
|
||||
#: pretix/static/pretixbase/js/asynctask.js:105
|
||||
@@ -86,7 +84,7 @@ msgstr ""
|
||||
#: pretix/static/pretixbase/js/asynctask.js:76
|
||||
#: pretix/static/pretixbase/js/asynctask.js:142
|
||||
#: pretix/static/pretixbase/js/asynctask.js:147
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:23
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:24
|
||||
msgid "An error of type {code} occurred."
|
||||
msgstr "Si è verificato un errore {code}."
|
||||
|
||||
@@ -99,12 +97,12 @@ msgstr ""
|
||||
"dell'ultimo errore: {code}"
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:125
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:20
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:21
|
||||
msgid "The request took to long. Please try again."
|
||||
msgstr "La richiesta ha impiegato troppo tempo. Si prega di riprovare."
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:150
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:25
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:26
|
||||
msgid ""
|
||||
"We currently cannot reach the server. Please try again. Error code: {code}"
|
||||
msgstr ""
|
||||
@@ -168,7 +166,7 @@ msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:430
|
||||
msgid "Object"
|
||||
msgstr ""
|
||||
msgstr "Oggetto"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:434
|
||||
msgid "Ticket design"
|
||||
@@ -176,80 +174,80 @@ msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:687
|
||||
msgid "Saving failed."
|
||||
msgstr ""
|
||||
msgstr "Salvataggio fallito."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:736
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:774
|
||||
msgid "Error while uploading your PDF file, please try again."
|
||||
msgstr ""
|
||||
msgstr "Errore durante il caricamento del tuo file PDF, prova di nuovo."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:759
|
||||
msgid "Do you really want to leave the editor without saving your changes?"
|
||||
msgstr ""
|
||||
msgstr "Vuoi davvero abbandonare l'editor senza salvare le modifiche?"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:18
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:19
|
||||
msgid "An error has occurred."
|
||||
msgstr ""
|
||||
msgstr "Abbiamo riscontrato un errore."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:52
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:54
|
||||
msgid "Generating messages …"
|
||||
msgstr ""
|
||||
msgstr "Stiamo generando i messaggi …"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:69
|
||||
msgid "Unknown error."
|
||||
msgstr ""
|
||||
msgstr "Errore sconosciuto."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:231
|
||||
msgid "Your color has great contrast and is very easy to read!"
|
||||
msgstr ""
|
||||
msgstr "Il colore scelto ha un ottimo contrasto ed è molto leggibile!"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:235
|
||||
msgid "Your color has decent contrast and is probably good-enough to read!"
|
||||
msgstr ""
|
||||
"Il colore scelto ha un buon contrasto e probabilmente è abbastanza leggibile!"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:239
|
||||
msgid ""
|
||||
"Your color has bad contrast for text on white background, please choose a "
|
||||
"darker shade."
|
||||
msgstr ""
|
||||
"Il colore scelto non ha un buon contrasto, per favore scegline uno più scuro."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:355
|
||||
msgid "All"
|
||||
msgstr ""
|
||||
msgstr "Tutto"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:356
|
||||
msgid "None"
|
||||
msgstr ""
|
||||
msgstr "Nessuno"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:645
|
||||
msgid "Use a different name internally"
|
||||
msgstr ""
|
||||
msgstr "Utilizza un nome diverso internamente"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:702
|
||||
msgid "Click to close"
|
||||
msgstr ""
|
||||
msgstr "Clicca per chiudere"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/orderchange.js:24
|
||||
#, fuzzy
|
||||
#| msgid "Contacting Stripe …"
|
||||
msgid "Calculating default price…"
|
||||
msgstr "Sto contattando Stripe …"
|
||||
msgstr "Calcolando il prezzo di default…"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:42
|
||||
msgid "Others"
|
||||
msgstr ""
|
||||
msgstr "Altri"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:71
|
||||
msgid "Count"
|
||||
msgstr ""
|
||||
msgstr "Conteggio"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:120
|
||||
msgid "Yes"
|
||||
msgstr ""
|
||||
msgstr "Si"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:121
|
||||
msgid "No"
|
||||
msgstr ""
|
||||
msgstr "No"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/subevent.js:108
|
||||
msgid "(one more date)"
|
||||
@@ -259,11 +257,11 @@ msgstr[1] ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:39
|
||||
msgid "The items in your cart are no longer reserved for you."
|
||||
msgstr ""
|
||||
msgstr "Gli articoli nel tuo carrello non sono più riservati per te."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:41
|
||||
msgid "Cart expired"
|
||||
msgstr ""
|
||||
msgstr "Carrello scaduto"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:46
|
||||
msgid "The items in your cart are reserved for you for one minute."
|
||||
|
||||
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-09-10 08:00+0000\n"
|
||||
"POT-Creation-Date: 2019-10-08 13:57+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"
|
||||
@@ -77,7 +77,7 @@ msgstr ""
|
||||
#: pretix/static/pretixbase/js/asynctask.js:76
|
||||
#: pretix/static/pretixbase/js/asynctask.js:142
|
||||
#: pretix/static/pretixbase/js/asynctask.js:147
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:23
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:24
|
||||
msgid "An error of type {code} occurred."
|
||||
msgstr ""
|
||||
|
||||
@@ -88,12 +88,12 @@ msgid ""
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:125
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:20
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:21
|
||||
msgid "The request took to long. Please try again."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:150
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:25
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:26
|
||||
msgid ""
|
||||
"We currently cannot reach the server. Please try again. Error code: {code}"
|
||||
msgstr ""
|
||||
@@ -171,11 +171,11 @@ msgstr ""
|
||||
msgid "Do you really want to leave the editor without saving your changes?"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:18
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:19
|
||||
msgid "An error has occurred."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:52
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:54
|
||||
msgid "Generating messages …"
|
||||
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-09-10 08:00+0000\n"
|
||||
"POT-Creation-Date: 2019-10-08 13:57+0000\n"
|
||||
"PO-Revision-Date: 2019-08-03 22:00+0000\n"
|
||||
"Last-Translator: Maarten van den Berg <maartenberg1@gmail.com>\n"
|
||||
"Language-Team: Dutch <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
@@ -82,7 +82,7 @@ msgstr ""
|
||||
#: pretix/static/pretixbase/js/asynctask.js:76
|
||||
#: pretix/static/pretixbase/js/asynctask.js:142
|
||||
#: pretix/static/pretixbase/js/asynctask.js:147
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:23
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:24
|
||||
msgid "An error of type {code} occurred."
|
||||
msgstr "Er is een fout opgetreden met code {code}."
|
||||
|
||||
@@ -95,12 +95,12 @@ msgstr ""
|
||||
"opnieuw. Laatste foutcode: {code}"
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:125
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:20
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:21
|
||||
msgid "The request took to long. Please try again."
|
||||
msgstr "De aanvraag duurde te lang, probeer het alstublieft opnieuw."
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:150
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:25
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:26
|
||||
msgid ""
|
||||
"We currently cannot reach the server. Please try again. Error code: {code}"
|
||||
msgstr ""
|
||||
@@ -182,11 +182,11 @@ msgstr "Probleem bij het uploaden van het PDF-bestand, probeer het opnieuw."
|
||||
msgid "Do you really want to leave the editor without saving your changes?"
|
||||
msgstr "Wilt u de editor verlaten zonder uw wijzigingen op te slaan?"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:18
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:19
|
||||
msgid "An error has occurred."
|
||||
msgstr "Er is een fout opgetreden."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:52
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:54
|
||||
msgid "Generating messages …"
|
||||
msgstr "Bezig met het genereren van berichten …"
|
||||
|
||||
|
||||
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-09-10 08:00+0000\n"
|
||||
"POT-Creation-Date: 2019-10-08 13:57+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: Automatically generated\n"
|
||||
"Language-Team: none\n"
|
||||
@@ -76,7 +76,7 @@ msgstr ""
|
||||
#: pretix/static/pretixbase/js/asynctask.js:76
|
||||
#: pretix/static/pretixbase/js/asynctask.js:142
|
||||
#: pretix/static/pretixbase/js/asynctask.js:147
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:23
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:24
|
||||
msgid "An error of type {code} occurred."
|
||||
msgstr ""
|
||||
|
||||
@@ -87,12 +87,12 @@ msgid ""
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:125
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:20
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:21
|
||||
msgid "The request took to long. Please try again."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:150
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:25
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:26
|
||||
msgid ""
|
||||
"We currently cannot reach the server. Please try again. Error code: {code}"
|
||||
msgstr ""
|
||||
@@ -170,11 +170,11 @@ msgstr ""
|
||||
msgid "Do you really want to leave the editor without saving your changes?"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:18
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:19
|
||||
msgid "An error has occurred."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:52
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:54
|
||||
msgid "Generating messages …"
|
||||
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-09-10 08:00+0000\n"
|
||||
"POT-Creation-Date: 2019-10-08 13:57+0000\n"
|
||||
"PO-Revision-Date: 2019-08-03 22:00+0000\n"
|
||||
"Last-Translator: Maarten van den Berg <maartenberg1@gmail.com>\n"
|
||||
"Language-Team: Dutch (informal) <https://translate.pretix.eu/projects/pretix/"
|
||||
@@ -83,7 +83,7 @@ msgstr ""
|
||||
#: pretix/static/pretixbase/js/asynctask.js:76
|
||||
#: pretix/static/pretixbase/js/asynctask.js:142
|
||||
#: pretix/static/pretixbase/js/asynctask.js:147
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:23
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:24
|
||||
msgid "An error of type {code} occurred."
|
||||
msgstr "Er is een fout opgetreden met code {code}."
|
||||
|
||||
@@ -96,12 +96,12 @@ msgstr ""
|
||||
"foutcode: {code}"
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:125
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:20
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:21
|
||||
msgid "The request took to long. Please try again."
|
||||
msgstr "De aanvraag duurde te lang, probeer het alsjeblieft opnieuw."
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:150
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:25
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:26
|
||||
msgid ""
|
||||
"We currently cannot reach the server. Please try again. Error code: {code}"
|
||||
msgstr ""
|
||||
@@ -184,11 +184,11 @@ msgstr "Probleem bij het uploaden van het PDF-bestand, probeer het opnieuw."
|
||||
msgid "Do you really want to leave the editor without saving your changes?"
|
||||
msgstr "Wil je de editor verlaten zonder je wijzigingen op te slaan?"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:18
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:19
|
||||
msgid "An error has occurred."
|
||||
msgstr "Er is iets misgegaan."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:52
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:54
|
||||
msgid "Generating messages …"
|
||||
msgstr "Bezig met het genereren van berichten …"
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user