mirror of
https://github.com/pretix/pretix.git
synced 2026-08-10 10:47:00 +00:00
Compare commits
13
Commits
v3.14.2
...
validate-dns
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe5a2286a4 | ||
|
|
3d66bfee7f | ||
|
|
0b495a4070 | ||
|
|
23aba9b5ef | ||
|
|
454f0f6fc8 | ||
|
|
002ff38fba | ||
|
|
dc8bd59715 | ||
|
|
56a2da08df | ||
|
|
4762d6818f | ||
|
|
e99e91d20f | ||
|
|
9fee2d0fbc | ||
|
|
3f30ddc9ab | ||
|
|
641a848f30 |
@@ -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
|
||||||
|
|||||||
@@ -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'):
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
@@ -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):
|
||||||
…
|
…
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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" … %}">
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 @@
|
|||||||
__version__ = "3.14.0"
|
__version__ = "3.15.0.dev0"
|
||||||
|
|||||||
@@ -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():
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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.'),
|
||||||
|
|||||||
@@ -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'),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user