Compare commits

...
13 Commits
Author SHA1 Message Date
Raphael Michel fe5a2286a4 Update cache handling 2021-01-01 20:13:09 +01:00
Raphael MichelandRaphael Michel 3d66bfee7f Update checkout.py 2021-01-01 20:13:09 +01:00
Raphael Michel 0b495a4070 Validate email addresses fo valid DNS names 2021-01-01 20:13:09 +01:00
Martin Gross 23aba9b5ef Move Mapquest-Geocoding to HTTPS 2020-12-30 13:11:10 +01:00
Raphael Michel 454f0f6fc8 Create log entry upon order email confirmation 2020-12-23 17:52:20 +01:00
Raphael Michel 002ff38fba Fix crash in add-on form (PRETIXEU-3GV) 2020-12-23 17:46:52 +01:00
lutoandGitHub dc8bd59715 Add convenience redirect …/event/(org)/ => …/organizer/(org)/ (#1893) 2020-12-23 16:50:41 +01:00
Raphael MichelandGitHub 56a2da08df Merge pull request #1891 from pretix-translations/weblate-pretix-pretix
Translations update from Weblate
2020-12-23 16:31:41 +01:00
Maarten van den Bergandpretix Translation Platform 4762d6818f Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (3909 of 3909 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/nl/

powered by weblate
2020-12-23 16:31:29 +01:00
Richard SchreiberandGitHub e99e91d20f Show event’s date and location in widget if event is subevent (#1892) 2020-12-23 16:31:24 +01:00
0xflotusandGitHub 9fee2d0fbc Docs: Enabling Syntax Highlighting (#1890) 2020-12-23 10:24:39 +01:00
Raphael Michel 3f30ddc9ab Fix #1888 -- UnknownLocaleError if locale is set 2020-12-22 13:14:56 +01:00
Raphael Michel 641a848f30 Bump to 3.15.0.dev0 2020-12-22 12:40:04 +01:00
29 changed files with 236 additions and 107 deletions
+1 -1
View File
@@ -135,7 +135,7 @@ Fill the configuration file ``/etc/pretix/pretix.cfg`` with the following conten
user=pretix user=pretix
; Replace with the password you chose above ; Replace with the password you chose above
password=********* password=*********
; In most docker setups, 172.17.0.1 is the address of the docker host. Adjuts ; In most docker setups, 172.17.0.1 is the address of the docker host. Adjust
; this to wherever your database is running, e.g. the name of a linked container ; this to wherever your database is running, e.g. the name of a linked container
; or of a mounted MySQL socket. ; or of a mounted MySQL socket.
host=172.17.0.1 host=172.17.0.1
+24 -9
View File
@@ -14,7 +14,9 @@ Control panel views
------------------- -------------------
If you want to add a custom view to the control area of an event, just register an URL in your If you want to add a custom view to the control area of an event, just register an URL in your
``urls.py`` that lives in the ``/control/`` subpath:: ``urls.py`` that lives in the ``/control/`` subpath:
.. code-block:: python
from django.conf.urls import url from django.conf.urls import url
@@ -44,7 +46,9 @@ If only the ``organizer`` parameter is present, it will be ensured that:
* The user has permission to access view the current organizer * The user has permission to access view the current organizer
If you want to require specific permission types, we provide you with a decorator or a mixin for If you want to require specific permission types, we provide you with a decorator or a mixin for
your views:: your views:
.. code-block:: python
from pretix.control.permissions import ( from pretix.control.permissions import (
event_permission_required, EventPermissionRequiredMixin event_permission_required, EventPermissionRequiredMixin
@@ -61,8 +65,9 @@ your views::
... ...
Similarly, there is ``organizer_permission_required`` and ``OrganizerPermissionRequiredMixin``. In case of Similarly, there is ``organizer_permission_required`` and ``OrganizerPermissionRequiredMixin``. In case of
event-related views, there is also a signal that allows you to add the view to the event navigation like this:: event-related views, there is also a signal that allows you to add the view to the event navigation like this:
.. code-block:: python
from django.urls import resolve, reverse from django.urls import resolve, reverse
from django.dispatch import receiver from django.dispatch import receiver
@@ -90,7 +95,9 @@ Event settings view
------------------- -------------------
A special case of a control panel view is a view hooked into the event settings page. For this case, there is a A special case of a control panel view is a view hooked into the event settings page. For this case, there is a
special navigation signal:: special navigation signal:
.. code-block:: python
@receiver(nav_event_settings, dispatch_uid='friends_tickets_nav_settings') @receiver(nav_event_settings, dispatch_uid='friends_tickets_nav_settings')
def navbar_settings(sender, request, **kwargs): def navbar_settings(sender, request, **kwargs):
@@ -105,7 +112,9 @@ special navigation signal::
}] }]
Also, your view should inherit from ``EventSettingsViewMixin`` and your template from ``pretixcontrol/event/settings_base.html`` Also, your view should inherit from ``EventSettingsViewMixin`` and your template from ``pretixcontrol/event/settings_base.html``
for good integration. If you just want to display a form, you could do it like the following:: for good integration. If you just want to display a form, you could do it like the following:
.. code-block:: python
class MySettingsView(EventSettingsViewMixin, EventSettingsFormView): class MySettingsView(EventSettingsViewMixin, EventSettingsFormView):
model = Event model = Event
@@ -147,7 +156,9 @@ Including a custom view into the participant-facing frontend is a little bit dif
no path prefix like ``control/``. no path prefix like ``control/``.
First, define your URL in your ``urls.py``, but this time in the ``event_patterns`` section and wrapped by First, define your URL in your ``urls.py``, but this time in the ``event_patterns`` section and wrapped by
``event_url``:: ``event_url``:
.. code-block:: python
from pretix.multidomain import event_url from pretix.multidomain import event_url
@@ -182,8 +193,9 @@ standard Django request handling: There are `ViewSets`_ to group related views i
automatically build URL configurations from them. automatically build URL configurations from them.
To integrate a custom viewset with pretix' REST API, you can just register with one of our routers within the To integrate a custom viewset with pretix' REST API, you can just register with one of our routers within the
``urls.py`` module of your plugin:: ``urls.py`` module of your plugin:
.. code-block:: python
from pretix.api.urls import event_router, router, orga_router from pretix.api.urls import event_router, router, orga_router
@@ -200,7 +212,9 @@ in the control panel. However, you need to make sure on your own only to return
.event`` and ``request.organizer`` are available as usual. .event`` and ``request.organizer`` are available as usual.
To require a special permission like ``can_view_orders``, you do not need to inherit from a special ViewSet base To require a special permission like ``can_view_orders``, you do not need to inherit from a special ViewSet base
class, you can just set the ``permission`` attribute on your viewset:: class, you can just set the ``permission`` attribute on your viewset:
.. code-block:: python
class MyViewSet(ModelViewSet): class MyViewSet(ModelViewSet):
permission = 'can_view_orders' permission = 'can_view_orders'
@@ -208,8 +222,9 @@ class, you can just set the ``permission`` attribute on your viewset::
If you want to check the permission only for some methods of your viewset, you have to do it yourself. Note here that If you want to check the permission only for some methods of your viewset, you have to do it yourself. Note here that
API authentications can be done via user sessions or API tokens and you should therefore check something like the API authentications can be done via user sessions or API tokens and you should therefore check something like the
following:: following:
.. code-block:: python
perm_holder = (request.auth if isinstance(request.auth, TeamAPIToken) else request.user) perm_holder = (request.auth if isinstance(request.auth, TeamAPIToken) else request.user)
if perm_holder.has_event_permission(request.event.organizer, request.event, 'can_view_orders'): if perm_holder.has_event_permission(request.event.organizer, request.event, 'can_view_orders'):
+6 -2
View File
@@ -15,7 +15,9 @@ Output registration
The email HTML renderer API does not make a lot of usage from signals, however, it The email HTML renderer API does not make a lot of usage from signals, however, it
does use a signal to get a list of all available email renderers. Your plugin does use a signal to get a list of all available email renderers. Your plugin
should listen for this signal and return the subclass of ``pretix.base.email.BaseHTMLMailRenderer`` should listen for this signal and return the subclass of ``pretix.base.email.BaseHTMLMailRenderer``
that we'll provide in this plugin:: that we'll provide in this plugin:
.. code-block:: python
from django.dispatch import receiver from django.dispatch import receiver
@@ -72,7 +74,9 @@ class ``TemplateBasedMailRenderer`` that you can re-use to perform the following
* Call `inlinestyler`_ to convert all ``<style>`` style sheets to inline ``style=""`` * Call `inlinestyler`_ to convert all ``<style>`` style sheets to inline ``style=""``
attributes for better compatibility attributes for better compatibility
To use it, you just need to implement some variables:: To use it, you just need to implement some variables:
.. code-block:: python
class ClassicMailRenderer(TemplateBasedMailRenderer): class ClassicMailRenderer(TemplateBasedMailRenderer):
verbose_name = _('pretix default') verbose_name = _('pretix default')
+6 -2
View File
@@ -17,7 +17,9 @@ Exporter registration
The exporter API does not make a lot of usage from signals, however, it does use a signal to get a list of The exporter API does not make a lot of usage from signals, however, it does use a signal to get a list of
all available exporters. Your plugin should listen for this signal and return the subclass of all available exporters. Your plugin should listen for this signal and return the subclass of
``pretix.base.exporter.BaseExporter`` ``pretix.base.exporter.BaseExporter``
that we'll provide in this plugin:: that we'll provide in this plugin:
.. code-block:: python
from django.dispatch import receiver from django.dispatch import receiver
@@ -31,7 +33,9 @@ that we'll provide in this plugin::
Some exporters might also prove to be useful, when provided on an organizer-level. In order to declare your Some exporters might also prove to be useful, when provided on an organizer-level. In order to declare your
exporter as capable of providing exports spanning multiple events, your plugin should listen for this signal exporter as capable of providing exports spanning multiple events, your plugin should listen for this signal
and return the subclass of ``pretix.base.exporter.BaseExporter`` that we'll provide in this plugin:: and return the subclass of ``pretix.base.exporter.BaseExporter`` that we'll provide in this plugin:
.. code-block:: python
from django.dispatch import receiver from django.dispatch import receiver
+3 -1
View File
@@ -15,7 +15,9 @@ Output registration
The invoice renderer API does not make a lot of usage from signals, however, it The invoice renderer API does not make a lot of usage from signals, however, it
does use a signal to get a list of all available invoice renderers. Your plugin does use a signal to get a list of all available invoice renderers. Your plugin
should listen for this signal and return the subclass of ``pretix.base.invoice.BaseInvoiceRenderer`` should listen for this signal and return the subclass of ``pretix.base.invoice.BaseInvoiceRenderer``
that we'll provide in this plugin:: that we'll provide in this plugin:
.. code-block:: python
from django.dispatch import receiver from django.dispatch import receiver
+6 -2
View File
@@ -19,7 +19,9 @@ Provider registration
The payment provider API does not make a lot of usage from signals, however, it The payment provider API does not make a lot of usage from signals, however, it
does use a signal to get a list of all available payment providers. Your plugin does use a signal to get a list of all available payment providers. Your plugin
should listen for this signal and return the subclass of ``pretix.base.payment.BasePaymentProvider`` should listen for this signal and return the subclass of ``pretix.base.payment.BasePaymentProvider``
that the plugin will provide:: that the plugin will provide:
.. code-block:: python
from django.dispatch import receiver from django.dispatch import receiver
@@ -140,7 +142,9 @@ it is necessary to introduce additional views. One example is the PayPal
provider. It redirects the user to a PayPal website in the provider. It redirects the user to a PayPal website in the
:py:meth:`BasePaymentProvider.checkout_prepare` step of the checkout process :py:meth:`BasePaymentProvider.checkout_prepare` step of the checkout process
and provides PayPal with a URL to redirect back to. This URL points to a and provides PayPal with a URL to redirect back to. This URL points to a
view which looks roughly like this:: view which looks roughly like this:
.. code-block:: python
@login_required @login_required
def success(request): def success(request):
+6 -2
View File
@@ -13,7 +13,9 @@ Placeholder registration
The placeholder API does not make a lot of usage from signals, however, it 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 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``:: should listen for this signal and return an instance of a subclass of ``pretix.base.email.BaseMailTextPlaceholder``:
.. code-block:: python
from django.dispatch import receiver from django.dispatch import receiver
@@ -71,7 +73,9 @@ Helper class for simple placeholders
------------------------------------ ------------------------------------
pretix ships with a helper class that makes it easy to provide placeholders based on simple pretix ships with a helper class that makes it easy to provide placeholders based on simple
functions:: functions:
.. code-block:: python
placeholder = SimpleFunctionalMailTextPlaceholder( placeholder = SimpleFunctionalMailTextPlaceholder(
'code', ['order'], lambda order: order.code, sample='F8VVL' 'code', ['order'], lambda order: order.code, sample='F8VVL'
+13 -5
View File
@@ -55,7 +55,9 @@ restricted boolean (optional) ``False`` by default, restricts a plugin
compatibility string Specifier for compatible pretix versions. compatibility string Specifier for compatible pretix versions.
================== ==================== =========================================================== ================== ==================== ===========================================================
A working example would be:: A working example would be:
.. code-block:: python
try: try:
from pretix.base.plugins import PluginConfig from pretix.base.plugins import PluginConfig
@@ -81,7 +83,7 @@ A working example would be::
default_app_config = 'pretix_paypal.PaypalApp' default_app_config = 'pretix_paypal.PaypalApp'
The ``AppConfig`` class may implement a property ``compatiblity_errors``, that checks The ``AppConfig`` class may implement a property ``compatibility_errors``, that checks
whether the pretix installation meets all requirements of the plugin. If so, whether the pretix installation meets all requirements of the plugin. If so,
it should contain ``None`` or an empty list, otherwise a list of strings containing it should contain ``None`` or an empty list, otherwise a list of strings containing
human-readable error messages. We recommend using the ``django.utils.functional.cached_property`` human-readable error messages. We recommend using the ``django.utils.functional.cached_property``
@@ -96,7 +98,9 @@ Plugin registration
Somehow, pretix needs to know that your plugin exists at all. For this purpose, we Somehow, pretix needs to know that your plugin exists at all. For this purpose, we
make use of the `entry point`_ feature of setuptools. To register a plugin that lives make use of the `entry point`_ feature of setuptools. To register a plugin that lives
in a separate python package, your ``setup.py`` should contain something like this:: in a separate python package, your ``setup.py`` should contain something like this:
.. code-block:: python
setup( setup(
args..., args...,
@@ -118,7 +122,9 @@ The various components of pretix define a number of signals which your plugin ca
listen for. We will go into the details of the different signals in the following listen for. We will go into the details of the different signals in the following
pages. We suggest that you put your signal receivers into a ``signals`` submodule pages. We suggest that you put your signal receivers into a ``signals`` submodule
of your plugin. You should extend your ``AppConfig`` (see above) by the following of your plugin. You should extend your ``AppConfig`` (see above) by the following
method to make your receivers available:: method to make your receivers available:
.. code-block:: python
class PaypalApp(AppConfig): class PaypalApp(AppConfig):
@@ -127,7 +133,9 @@ method to make your receivers available::
from . import signals # NOQA from . import signals # NOQA
You can optionally specify code that is executed when your plugin is activated for an event You can optionally specify code that is executed when your plugin is activated for an event
in the ``installed`` method:: in the ``installed`` method:
.. code-block:: python
class PaypalApp(AppConfig): class PaypalApp(AppConfig):
+1 -1
View File
@@ -74,7 +74,7 @@ looks like this:
def generate_files(self) -> List[Tuple[str, str, str]]: def generate_files(self) -> List[Tuple[str, str, str]]:
yield 'invoice-addresses.json', 'application/json', json.dumps({ yield 'invoice-addresses.json', 'application/json', json.dumps({
ia.order.code: InvoiceAdddressSerializer(ia).data ia.order.code: InvoiceAddressSerializer(ia).data
for ia in InvoiceAddress.objects.filter(order__event=self.event) for ia in InvoiceAddress.objects.filter(order__event=self.event)
}, indent=4) }, indent=4)
+3 -1
View File
@@ -17,7 +17,9 @@ Output registration
The ticket output API does not make a lot of usage from signals, however, it The ticket output API does not make a lot of usage from signals, however, it
does use a signal to get a list of all available ticket outputs. Your plugin does use a signal to get a list of all available ticket outputs. Your plugin
should listen for this signal and return the subclass of ``pretix.base.ticketoutput.BaseTicketOutput`` should listen for this signal and return the subclass of ``pretix.base.ticketoutput.BaseTicketOutput``
that we'll provide in this plugin:: that we'll provide in this plugin:
.. code-block:: python
from django.dispatch import receiver from django.dispatch import receiver
+10 -4
View File
@@ -12,7 +12,9 @@ Implementing a task
------------------- -------------------
A common pattern for implementing asynchronous tasks can be seen a lot in ``pretix.base.services`` A common pattern for implementing asynchronous tasks can be seen a lot in ``pretix.base.services``
and looks like this:: and looks like this:
.. code-block:: python
from pretix.celery_app import app from pretix.celery_app import app
@@ -34,13 +36,15 @@ If your user needs to wait for the response of the asynchronous task, there are
that will probably move to ``pretix.base`` at some point. They consist of the view mixin ``AsyncAction`` that allows that will probably move to ``pretix.base`` at some point. They consist of the view mixin ``AsyncAction`` that allows
you to easily write a view that kicks off and waits for an asynchronous task. ``AsyncAction`` will determine whether you to easily write a view that kicks off and waits for an asynchronous task. ``AsyncAction`` will determine whether
to run the task asynchronously or not and will do some magic to look nice for users with and without JavaScript support. to run the task asynchronously or not and will do some magic to look nice for users with and without JavaScript support.
A usage example taken directly from the code is:: A usage example taken directly from the code is:
.. code-block:: python
class OrderCancelDo(EventViewMixin, OrderDetailMixin, AsyncAction, View): class OrderCancelDo(EventViewMixin, OrderDetailMixin, AsyncAction, View):
""" """
A view that executes a task asynchronously. A POST request will kick off the A view that executes a task asynchronously. A POST request will kick off the
task into the background or run it in the foreground if celery is not installed. task into the background or run it in the foreground if celery is not installed.
In the former case, subsequent GET calls can be used to determinine the current In the former case, subsequent GET calls can be used to determine the current
status of the task. status of the task.
""" """
@@ -79,7 +83,9 @@ A usage example taken directly from the code is::
return super().get_error_message(exception) return super().get_error_message(exception)
On the client side, this can be used by simply adding a ``data-asynctask`` attribute to an HTML form. This will enable On the client side, this can be used by simply adding a ``data-asynctask`` attribute to an HTML form. This will enable
AJAX sending of the form and display a loading indicator:: AJAX sending of the form and display a loading indicator:
.. code-block:: html
<form method="post" data-asynctask <form method="post" data-asynctask
action="{% eventurl request.event "presale:event.order.cancel.do" %}"> action="{% eventurl request.event "presale:event.order.cancel.do" %}">
+3 -1
View File
@@ -27,7 +27,9 @@ numbers and dates, ``LazyDate`` and ``LazyNumber``. There also is a ``LazyLocale
exceptions with gettext-localized exception messages. exceptions with gettext-localized exception messages.
Last, but definitely not least, we have the ``language`` context manager (``pretix.base.i18n.language``) that allows Last, but definitely not least, we have the ``language`` context manager (``pretix.base.i18n.language``) that allows
you to execute a piece of code with a different locale:: you to execute a piece of code with a different locale:
.. code-block:: python
with language('de'): with language('de'):
render_mail_template() render_mail_template()
+24 -8
View File
@@ -16,7 +16,9 @@ We recommend all relevant models to inherit from ``LoggedModel`` as it simplifie
.. autoclass:: pretix.base.models.LoggedModel .. autoclass:: pretix.base.models.LoggedModel
:members: log_action, all_logentries :members: log_action, all_logentries
To actually log an action, you can just call the ``log_action`` method on your object:: To actually log an action, you can just call the ``log_action`` method on your object:
.. code-block:: python
order.log_action('pretix.event.order.canceled', user=user, data={}) order.log_action('pretix.event.order.canceled', user=user, data={})
@@ -29,7 +31,9 @@ Logging form actions
"""""""""""""""""""" """"""""""""""""""""
A very common use case is to log the changes to a model that have been done in a ``ModelForm``. In this case, A very common use case is to log the changes to a model that have been done in a ``ModelForm``. In this case,
we generally use a custom ``form_valid`` method on our ``FormView`` that looks like this:: we generally use a custom ``form_valid`` method on our ``FormView`` that looks like this:
.. code-block:: python
@transaction.atomic @transaction.atomic
def form_valid(self, form): def form_valid(self, form):
@@ -40,7 +44,9 @@ we generally use a custom ``form_valid`` method on our ``FormView`` that looks l
messages.success(self.request, _('Your changes have been saved.')) messages.success(self.request, _('Your changes have been saved.'))
return super().form_valid(form) return super().form_valid(form)
It gets a little bit more complicated if your form allows file uploads:: It gets a little bit more complicated if your form allows file uploads:
.. code-block:: python
@transaction.atomic @transaction.atomic
def form_valid(self, form): def form_valid(self, form):
@@ -67,7 +73,9 @@ following ready-to-include template::
We now need a way to translate the action codes like ``pretix.event.changed`` into human-readable We now need a way to translate the action codes like ``pretix.event.changed`` into human-readable
strings. The :py:attr:`pretix.base.signals.logentry_display` signals allows you to do so. A simple strings. The :py:attr:`pretix.base.signals.logentry_display` signals allows you to do so. A simple
implementation could look like:: implementation could look like:
.. code-block:: python
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from pretix.base.signals import logentry_display from pretix.base.signals import logentry_display
@@ -88,7 +96,9 @@ Sending notifications
If you think that the logged information might be important or urgent enough to send out a notification to interested If you think that the logged information might be important or urgent enough to send out a notification to interested
organizers. In this case, you should listen for the :py:attr:`pretix.base.signals.register_notification_types` signal organizers. In this case, you should listen for the :py:attr:`pretix.base.signals.register_notification_types` signal
to register a notification type:: to register a notification type:
.. code-block:: python
@receiver(register_notification_types) @receiver(register_notification_types)
def register_my_notification_types(sender, **kwargs): def register_my_notification_types(sender, **kwargs):
@@ -103,7 +113,9 @@ You should subclass the base ``NotificationType`` class and implement all its me
.. autoclass:: pretix.base.notifications.NotificationType .. autoclass:: pretix.base.notifications.NotificationType
:members: action_type, verbose_name, required_permission, build_notification :members: action_type, verbose_name, required_permission, build_notification
A simple implementation could look like this:: A simple implementation could look like this:
.. code-block:: python
class MyNotificationType(NotificationType): class MyNotificationType(NotificationType):
required_permission = "can_view_orders" required_permission = "can_view_orders"
@@ -143,7 +155,9 @@ Logging technical information
----------------------------- -----------------------------
If you just want to log technical information to a log file on disk that does not need to be parsed If you just want to log technical information to a log file on disk that does not need to be parsed
and displayed later, you can just use Python's ``logging`` module:: and displayed later, you can just use Python's ``logging`` module:
.. code-block:: python
import logging import logging
@@ -151,7 +165,9 @@ and displayed later, you can just use Python's ``logging`` module::
logger.info('Startup complete.') logger.info('Startup complete.')
This is also very useful to provide debugging information when an exception occurs:: This is also very useful to provide debugging information when an exception occurs:
.. code-block:: python
try: try:
foo() foo()
+12 -4
View File
@@ -15,7 +15,9 @@ Requiring permissions for a view
-------------------------------- --------------------------------
pretix provides a number of useful mixins and decorators that allow you to specify that a user needs a certain pretix provides a number of useful mixins and decorators that allow you to specify that a user needs a certain
permission level to access a view:: permission level to access a view:
.. code-block:: python
from pretix.control.permissions import ( from pretix.control.permissions import (
OrganizerPermissionRequiredMixin, organizer_permission_required OrganizerPermissionRequiredMixin, organizer_permission_required
@@ -44,7 +46,9 @@ permission level to access a view::
# Only users with *any* permission on this organizer can access this # Only users with *any* permission on this organizer can access this
Of course, the same is available on event level:: Of course, the same is available on event level:
.. code-block:: python
from pretix.control.permissions import ( from pretix.control.permissions import (
EventPermissionRequiredMixin, event_permission_required EventPermissionRequiredMixin, event_permission_required
@@ -73,7 +77,9 @@ Of course, the same is available on event level::
# Only users with *any* permission on this event can access this # Only users with *any* permission on this event can access this
You can also require that this view is only accessible by system administrators with an active "admin session" You can also require that this view is only accessible by system administrators with an active "admin session"
(see below for what this means):: (see below for what this means):
.. code-block:: python
from pretix.control.permissions import ( from pretix.control.permissions import (
AdministratorPermissionRequiredMixin, administrator_permission_required AdministratorPermissionRequiredMixin, administrator_permission_required
@@ -89,7 +95,9 @@ You can also require that this view is only accessible by system administrators
# ... # ...
In rare cases it might also be useful to expose a feature only to people who have a staff account but do not In rare cases it might also be useful to expose a feature only to people who have a staff account but do not
necessarily have an active admin session:: necessarily have an active admin session:
.. code-block:: python
from pretix.control.permissions import ( from pretix.control.permissions import (
StaffMemberRequiredMixin, staff_member_required StaffMemberRequiredMixin, staff_member_required
+6 -2
View File
@@ -39,7 +39,9 @@ subclass that also adds support for internationalized fields:
.. autoclass:: pretix.base.forms.SettingsForm .. autoclass:: pretix.base.forms.SettingsForm
You can simply use it like this:: You can simply use it like this:
.. code-block:: python
class EventSettingsForm(SettingsForm): class EventSettingsForm(SettingsForm):
show_date_to = forms.BooleanField( show_date_to = forms.BooleanField(
@@ -56,7 +58,9 @@ You can simply use it like this::
Defaults in plugins Defaults in plugins
------------------- -------------------
Plugins can add custom hardcoded defaults in the following way:: Plugins can add custom hardcoded defaults in the following way:
.. code-block:: python
from pretix.base.settings import settings_hierarkey from pretix.base.settings import settings_hierarkey
+1 -1
View File
@@ -1 +1 @@
__version__ = "3.14.0" __version__ = "3.15.0.dev0"
+19 -13
View File
@@ -9,7 +9,6 @@ import pycountry
import pytz import pytz
import vat_moss.errors import vat_moss.errors
import vat_moss.id import vat_moss.id
from babel import localedata
from django import forms from django import forms
from django.contrib import messages from django.contrib import messages
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
@@ -20,14 +19,14 @@ from django.utils.formats import date_format
from django.utils.html import escape from django.utils.html import escape
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.timezone import get_current_timezone from django.utils.timezone import get_current_timezone
from django.utils.translation import ( from django.utils.translation import gettext_lazy as _, pgettext_lazy
get_language, gettext_lazy as _, pgettext_lazy,
)
from django_countries import countries from django_countries import countries
from django_countries.fields import Country, CountryField from django_countries.fields import Country, CountryField
from phonenumber_field.formfields import PhoneNumberField from phonenumber_field.formfields import PhoneNumberField
from phonenumber_field.phonenumber import PhoneNumber from phonenumber_field.phonenumber import PhoneNumber
from phonenumber_field.widgets import PhoneNumberPrefixWidget from phonenumber_field.widgets import (
PhoneNumberPrefixWidget, PhonePrefixSelect,
)
from phonenumbers import NumberParseException, national_significant_number from phonenumbers import NumberParseException, national_significant_number
from phonenumbers.data import _COUNTRY_CODE_TO_REGION_CODE from phonenumbers.data import _COUNTRY_CODE_TO_REGION_CODE
@@ -35,7 +34,9 @@ from pretix.base.forms.widgets import (
BusinessBooleanRadio, DatePickerWidget, SplitDateTimePickerWidget, BusinessBooleanRadio, DatePickerWidget, SplitDateTimePickerWidget,
TimePickerWidget, UploadedFileWidget, TimePickerWidget, UploadedFileWidget,
) )
from pretix.base.i18n import get_language_without_region, language from pretix.base.i18n import (
get_babel_locale, get_language_without_region, language,
)
from pretix.base.models import InvoiceAddress, Question, QuestionOption from pretix.base.models import InvoiceAddress, Question, QuestionOption
from pretix.base.models.tax import ( from pretix.base.models.tax import (
EU_COUNTRIES, cc_to_vat_prefix, is_eu_country, EU_COUNTRIES, cc_to_vat_prefix, is_eu_country,
@@ -204,7 +205,18 @@ class NamePartsFormField(forms.MultiValueField):
return value return value
class WrappedPhonePrefixSelect(PhonePrefixSelect):
def __init__(self, *args, **kwargs):
with language(get_babel_locale()):
super().__init__(*args, **kwargs)
class WrappedPhoneNumberPrefixWidget(PhoneNumberPrefixWidget): class WrappedPhoneNumberPrefixWidget(PhoneNumberPrefixWidget):
def __init__(self, attrs=None, initial=None):
widgets = (WrappedPhonePrefixSelect(initial), forms.TextInput())
super(PhoneNumberPrefixWidget, self).__init__(widgets, attrs)
def render(self, name, value, attrs=None, renderer=None): def render(self, name, value, attrs=None, renderer=None):
output = super().render(name, value, attrs, renderer) output = super().render(name, value, attrs, renderer)
return mark_safe(self.format_output(output)) return mark_safe(self.format_output(output))
@@ -564,13 +576,7 @@ class BaseQuestionsForm(forms.Form):
if q.valid_datetime_max: if q.valid_datetime_max:
field.validators.append(MaxDateTimeValidator(q.valid_datetime_max)) field.validators.append(MaxDateTimeValidator(q.valid_datetime_max))
elif q.type == Question.TYPE_PHONENUMBER: elif q.type == Question.TYPE_PHONENUMBER:
babel_locale = 'en' with language(get_babel_locale()):
# Babel, and therefore django-phonenumberfield, do not support our custom locales such das de_Informal
if localedata.exists(get_language()):
babel_locale = get_language()
elif localedata.exists(get_language()[:2]):
babel_locale = get_language()[:2]
with language(babel_locale):
default_country = guess_country(event) default_country = guess_country(event)
default_prefix = None default_prefix = None
for prefix, values in _COUNTRY_CODE_TO_REGION_CODE.items(): for prefix, values in _COUNTRY_CODE_TO_REGION_CODE.items():
+11
View File
@@ -1,5 +1,6 @@
from contextlib import contextmanager from contextlib import contextmanager
from babel import localedata
from django.conf import settings from django.conf import settings
from django.utils import translation from django.utils import translation
from django.utils.formats import date_format, number_format from django.utils.formats import date_format, number_format
@@ -69,6 +70,16 @@ class LazyNumber:
ALLOWED_LANGUAGES = dict(settings.LANGUAGES) ALLOWED_LANGUAGES = dict(settings.LANGUAGES)
def get_babel_locale():
babel_locale = 'en'
# Babel, and therefore django-phonenumberfield, do not support our custom locales such das de_Informal
if localedata.exists(translation.get_language()):
babel_locale = translation.get_language()
elif localedata.exists(translation.get_language()[:2]):
babel_locale = translation.get_language()[:2]
return babel_locale
def get_language_without_region(lng=None): def get_language_without_region(lng=None):
""" """
Returns the currently active language, but strips what pretix calls a ``region``. For example, Returns the currently active language, but strips what pretix calls a ``region``. For example,
+2
View File
@@ -292,6 +292,8 @@ def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
'pretix.event.order.denied': _('The order has been denied.'), 'pretix.event.order.denied': _('The order has been denied.'),
'pretix.event.order.contact.changed': _('The email address has been changed from "{old_email}" ' 'pretix.event.order.contact.changed': _('The email address has been changed from "{old_email}" '
'to "{new_email}".'), 'to "{new_email}".'),
'pretix.event.order.contact.confirmed': _('The email address has been confirmed to be working (the user clicked on a link '
'in the email for the first time).'),
'pretix.event.order.phone.changed': _('The phone number has been changed from "{old_phone}" ' 'pretix.event.order.phone.changed': _('The phone number has been changed from "{old_phone}" '
'to "{new_phone}".'), 'to "{new_phone}".'),
'pretix.event.order.locale.changed': _('The order locale has been changed.'), 'pretix.event.order.locale.changed': _('The order locale has been changed.'),
+2
View File
@@ -1,4 +1,5 @@
from django.conf.urls import include, url from django.conf.urls import include, url
from django.views.generic.base import RedirectView
from pretix.control.views import ( from pretix.control.views import (
auth, checkin, dashboards, event, geo, global_settings, item, main, oauth, auth, checkin, dashboards, event, geo, global_settings, item, main, oauth,
@@ -303,4 +304,5 @@ urlpatterns = [
url(r'^checkinlists/(?P<list>\d+)/delete$', checkin.CheckinListDelete.as_view(), url(r'^checkinlists/(?P<list>\d+)/delete$', checkin.CheckinListDelete.as_view(),
name='event.orders.checkinlists.delete'), name='event.orders.checkinlists.delete'),
])), ])),
url(r'^event/(?P<organizer>[^/]+)/$', RedirectView.as_view(pattern_name='control:organizer'), name='event.organizerredirect'),
] ]
+1 -1
View File
@@ -71,7 +71,7 @@ class GeoCodeView(LoginRequiredMixin, View):
try: try:
r = requests.get( r = requests.get(
'http://www.mapquestapi.com/geocoding/v1/address?location={}&key={}'.format( 'https://www.mapquestapi.com/geocoding/v1/address?location={}&key={}'.format(
quote(q), gs.settings.mapquest_apikey quote(q), gs.settings.mapquest_apikey
) )
) )
+15 -29
View File
@@ -7,10 +7,10 @@ msgstr ""
"Project-Id-Version: 1\n" "Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-12-22 11:05+0000\n" "POT-Creation-Date: 2020-12-22 11:05+0000\n"
"PO-Revision-Date: 2020-12-22 06:00+0000\n" "PO-Revision-Date: 2020-12-23 04:00+0000\n"
"Last-Translator: Maarten van den Berg <maartenberg1@gmail.com>\n" "Last-Translator: Maarten van den Berg <maartenberg1@gmail.com>\n"
"Language-Team: Dutch <https://translate.pretix.eu/projects/pretix/pretix/nl/" "Language-Team: Dutch <https://translate.pretix.eu/projects/pretix/pretix/nl/>"
">\n" "\n"
"Language: nl\n" "Language: nl\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
@@ -1484,6 +1484,8 @@ msgstr ""
#: pretix/base/forms/auth.py:26 pretix/base/forms/user.py:23 #: pretix/base/forms/auth.py:26 pretix/base/forms/user.py:23
msgid "For security reasons, please wait 5 minutes before you try again." msgid "For security reasons, please wait 5 minutes before you try again."
msgstr "" msgstr ""
"Om veiligheidsredenen moet u vijf minuten wachten voor u dit opnieuw kunt "
"proberen."
#: pretix/base/forms/auth.py:27 pretix/base/forms/auth.py:223 #: pretix/base/forms/auth.py:27 pretix/base/forms/auth.py:223
msgid "This account is inactive." msgid "This account is inactive."
@@ -8155,18 +8157,16 @@ msgid "Free text input"
msgstr "Vrije tekstinvoer" msgstr "Vrije tekstinvoer"
#: pretix/control/forms/event.py:575 #: pretix/control/forms/event.py:575
#, fuzzy
#| msgid "Do not copy"
msgid "Do not ask" msgid "Do not ask"
msgstr "Niet kopiëren" msgstr "Niet vragen"
#: pretix/control/forms/event.py:576 #: pretix/control/forms/event.py:576
msgid "Ask, but do not require input" msgid "Ask, but do not require input"
msgstr "" msgstr "Vraag, maar maak invoer niet verplicht"
#: pretix/control/forms/event.py:577 #: pretix/control/forms/event.py:577
msgid "Ask and require input" msgid "Ask and require input"
msgstr "" msgstr "Vraag, invullen is niet verplicht"
#: pretix/control/forms/event.py:612 #: pretix/control/forms/event.py:612
msgid "" msgid ""
@@ -8616,16 +8616,12 @@ msgid "Cancellations"
msgstr "Annuleringen" msgstr "Annuleringen"
#: pretix/control/forms/filter.py:153 #: pretix/control/forms/filter.py:153
#, fuzzy
#| msgid "Canceled (paid fee)"
msgid "Canceled (fully)" msgid "Canceled (fully)"
msgstr "Geannuleerd (betaalde kosten)" msgstr "Geannuleerd (hele bestelling)"
#: pretix/control/forms/filter.py:154 #: pretix/control/forms/filter.py:154
#, fuzzy
#| msgid "Canceled (or with paid fee)"
msgid "Canceled (fully or with paid fee)" msgid "Canceled (fully or with paid fee)"
msgstr "Geannuleerd (of met betaalde kosten)" msgstr "Geannuleerd (hele bestelling of met betaalde kosten)"
#: pretix/control/forms/filter.py:155 #: pretix/control/forms/filter.py:155
msgid "Cancellation requested" msgid "Cancellation requested"
@@ -8661,17 +8657,12 @@ msgid "Overpaid"
msgstr "Overbetaald" msgstr "Overbetaald"
#: pretix/control/forms/filter.py:162 #: pretix/control/forms/filter.py:162
#, fuzzy
#| msgid "Mark as paid"
msgid "Partially paid" msgid "Partially paid"
msgstr "Markeren als betaald" msgstr "Gedeeltelijk betaald"
#: pretix/control/forms/filter.py:163 #: pretix/control/forms/filter.py:163
#, fuzzy
#| msgctxt "checkoutflow"
#| msgid "Order confirmed"
msgid "Underpaid (but confirmed)" msgid "Underpaid (but confirmed)"
msgstr "Bestelling bevestigd" msgstr "Onderbetaald (maar bevestigd)"
#: pretix/control/forms/filter.py:164 #: pretix/control/forms/filter.py:164
msgid "Pending (but fully paid)" msgid "Pending (but fully paid)"
@@ -15141,10 +15132,8 @@ msgid "Remove filter"
msgstr "Verwijder filter" msgstr "Verwijder filter"
#: pretix/control/templates/pretixcontrol/orders/index.html:116 #: pretix/control/templates/pretixcontrol/orders/index.html:116
#, fuzzy
#| msgid "Order total"
msgid "Order paid / total" msgid "Order paid / total"
msgstr "Totaalbedrag van bestelling" msgstr "Bestelling betaald / totaal"
#: pretix/control/templates/pretixcontrol/orders/index.html:151 #: pretix/control/templates/pretixcontrol/orders/index.html:151
#: pretix/control/templates/pretixcontrol/search/orders.html:83 #: pretix/control/templates/pretixcontrol/search/orders.html:83
@@ -18798,7 +18787,7 @@ msgstr "De bestelling is al geannuleerd."
#: pretix/plugins/banktransfer/tasks.py:117 #: pretix/plugins/banktransfer/tasks.py:117
msgid "Automatic split to multiple orders not possible." msgid "Automatic split to multiple orders not possible."
msgstr "" msgstr "Automatisch uitsplitsen naar meerdere bestellingen niet mogelijk."
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/checkout_payment_form.html:4 #: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/checkout_payment_form.html:4
msgid "" msgid ""
@@ -18827,11 +18816,8 @@ msgid "Account"
msgstr "Rekening" msgstr "Rekening"
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/control.html:22 #: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/control.html:22
#, fuzzy
#| msgctxt "amount_label"
#| msgid "Transfer"
msgid "Transfer amount" msgid "Transfer amount"
msgstr "Overdragen" msgstr "Overschrijvingsbedrag"
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/control.html:26 #: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/control.html:26
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/import_assign.html:33 #: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/import_assign.html:33
+1 -1
View File
@@ -273,7 +273,7 @@ class AddOnsStep(CartMixin, AsyncAction, TemplateFlowStep):
net=a.price - a.tax_value, net=a.price - a.tax_value,
gross=a.price, gross=a.price,
tax=a.tax_value, tax=a.tax_value,
name=a.item.tax_rule.name, name=a.item.tax_rule.name if a.item.tax_rule else "",
rate=a.tax_rate, rate=a.tax_rate,
) )
else: else:
+49 -14
View File
@@ -1,10 +1,11 @@
from itertools import chain from itertools import chain
from babel import localedata import dns.resolver
from django import forms from django import forms
from django.core.cache import cache
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.utils.encoding import force_str from django.utils.encoding import force_str
from django.utils.translation import get_language, gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from phonenumber_field.formfields import PhoneNumberField from phonenumber_field.formfields import PhoneNumberField
from phonenumber_field.phonenumber import PhoneNumber from phonenumber_field.phonenumber import PhoneNumber
from phonenumbers import NumberParseException from phonenumbers import NumberParseException
@@ -14,17 +15,57 @@ from pretix.base.forms.questions import (
BaseInvoiceAddressForm, BaseQuestionsForm, WrappedPhoneNumberPrefixWidget, BaseInvoiceAddressForm, BaseQuestionsForm, WrappedPhoneNumberPrefixWidget,
guess_country, guess_country,
) )
from pretix.base.i18n import language from pretix.base.i18n import get_babel_locale, language
from pretix.base.validators import EmailBanlistValidator from pretix.base.validators import EmailBanlistValidator
from pretix.presale.signals import contact_form_fields from pretix.presale.signals import contact_form_fields
class EmailDNSValidator:
msg = _('Please check your email domain, it does not look like "%(value)s" is able to receive emails.')
def __call__(self, value):
domain = value.split('@')[-1]
works = cache.get(f"mail_domain_exists_{domain}")
if works == "true":
return value
if works == "false":
raise ValidationError(self.msg, code='dns', params={'value': domain})
resolver = dns.resolver.Resolver()
resolver.lifetime = 0.5
resolver.timeout = 0.5
record_types = ('MX', 'AAAA', 'A')
for record_type in record_types:
try:
if len(resolver.query(domain, record_type)):
cache.set(f"mail_domain_exists_{domain}", "true", 3600 * 24 * 7)
return value
except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN, dns.resolver.YXDOMAIN):
# These errors represent a non-existing domain or entry, we want to continue with the
# other types of records and if all fail, we'll report an error.
continue
except:
# Timeout, IO errors, etc.: We don't know what's happening! Let's not stop the user
# from buying and just not validate this. Let's cache this result for a short while
# to prevent issues during peak load
cache.set(f"mail_domain_exists_{domain}", "false", 60)
return value
# No valid record found, even though the requests did not fail. This domain will not receive email.
# Cache this result for a short time.
cache.set(f"mail_domain_exists_{domain}", "false", 300)
raise ValidationError(self.msg, code='dns', params={'value': domain})
class ContactForm(forms.Form): class ContactForm(forms.Form):
required_css_class = 'required' required_css_class = 'required'
email = forms.EmailField(label=_('E-mail'), email = forms.EmailField(
validators=[EmailBanlistValidator()], label=_('E-mail'),
widget=forms.EmailInput(attrs={'autocomplete': 'section-contact email'}) validators=[
) EmailBanlistValidator(),
EmailDNSValidator(),
],
widget=forms.EmailInput(attrs={'autocomplete': 'section-contact email'})
)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.event = kwargs.pop('event') self.event = kwargs.pop('event')
@@ -39,13 +80,7 @@ class ContactForm(forms.Form):
) )
if self.event.settings.order_phone_asked: if self.event.settings.order_phone_asked:
babel_locale = 'en' with language(get_babel_locale()):
# Babel, and therefore django-phonenumberfield, do not support our custom locales such as de_Informal
if localedata.exists(get_language()):
babel_locale = get_language()
elif localedata.exists(get_language()[:2]):
babel_locale = get_language()[:2]
with language(babel_locale):
default_country = guess_country(self.event) default_country = guess_country(self.event)
default_prefix = None default_prefix = None
for prefix, values in _COUNTRY_CODE_TO_REGION_CODE.items(): for prefix, values in _COUNTRY_CODE_TO_REGION_CODE.items():
@@ -183,7 +183,7 @@
{% endif %} {% endif %}
</div> </div>
{% endif %} {% endif %}
{% if not cart_namespace %} {% if not cart_namespace or subevent %}
<div> <div>
{% if ev.location %} {% if ev.location %}
<div class="info-row"> <div class="info-row">
+4 -2
View File
@@ -120,8 +120,10 @@ class OrderOpen(EventViewMixin, OrderDetailMixin, View):
if not self.order: if not self.order:
raise Http404(_('Unknown order code or not authorized to access this order.')) raise Http404(_('Unknown order code or not authorized to access this order.'))
if kwargs.get('hash') == self.order.email_confirm_hash(): if kwargs.get('hash') == self.order.email_confirm_hash():
self.order.email_known_to_work = True if not self.order.email_known_to_work:
self.order.save(update_fields=['email_known_to_work']) self.order.log_action('pretix.event.order.contact.confirmed')
self.order.email_known_to_work = True
self.order.save(update_fields=['email_known_to_work'])
return redirect(self.get_order_url()) return redirect(self.get_order_url())
+1
View File
@@ -44,6 +44,7 @@ oauthlib==3.1.*
django-jsonfallback>=2.1.2 django-jsonfallback>=2.1.2
psycopg2-binary psycopg2-binary
tqdm==4.* tqdm==4.*
dnspython==1.16.*
# Stripe # Stripe
stripe==2.42.* stripe==2.42.*
# PayPal # PayPal
+1
View File
@@ -134,6 +134,7 @@ setup(
'django-i18nfield>=1.7.0', 'django-i18nfield>=1.7.0',
'django-jsonfallback>=2.1.2', 'django-jsonfallback>=2.1.2',
'psycopg2-binary', 'psycopg2-binary',
'dnspython==1.16.*',
'tqdm==4.*', 'tqdm==4.*',
'vobject==0.9.*', 'vobject==0.9.*',
'pycountry', 'pycountry',
+4
View File
@@ -55,6 +55,10 @@ class EventsTest(SoupTest):
self.assertNotIn("31C3", tabletext) self.assertNotIn("31C3", tabletext)
self.assertNotIn("MRMCD14", tabletext) self.assertNotIn("MRMCD14", tabletext)
def test_convenience_organizer_redirect(self):
resp = self.client.get('/control/event/%s/' % (self.orga1.slug))
self.assertRedirects(resp, '/control/organizer/%s/' % (self.orga1.slug))
def test_quick_setup_later(self): def test_quick_setup_later(self):
with scopes_disabled(): with scopes_disabled():
self.event1.quotas.create(name='foo', size=2) self.event1.quotas.create(name='foo', size=2)