Compare commits

..
1 Commits
Author SHA1 Message Date
Raphael Michel 173cf751da Add search index model 2019-02-14 18:11:25 +01:00
123 changed files with 17236 additions and 22062 deletions
+1 -1
View File
@@ -129,7 +129,7 @@ Database replica settings
------------------------- -------------------------
If you use a replicated database setup, pretix expects that the default database connection always points to the primary database node. If you use a replicated database setup, pretix expects that the default database connection always points to the primary database node.
Routing read queries to a replica on database layer is **strongly** discouraged since this can lead to inaccurate such as more tickets Routing read queries to a replica on databse layer is **strongly** discouraged since this can lead to inaccurate such as more tickets
being sold than are actually available. being sold than are actually available.
However, pretix can still make use of a database replica to keep some expensive queries with that can tolerate some latency from your However, pretix can still make use of a database replica to keep some expensive queries with that can tolerate some latency from your
-12
View File
@@ -15,7 +15,6 @@ name multi-lingual string The event's ful
slug string A short form of the name, used e.g. in URLs. slug string A short form of the name, used e.g. in URLs.
live boolean If ``true``, the event ticket shop is publicly live boolean If ``true``, the event ticket shop is publicly
available. available.
testmode boolean If ``true``, the ticket shop is in test mode.
currency string The currency this event is handled in. currency string The currency this event is handled in.
date_from datetime The event's start date date_from datetime The event's start date
date_to datetime The event's end date (or ``null``) date_to datetime The event's end date (or ``null``)
@@ -46,10 +45,6 @@ plugins list A list of packa
Filters have been added to the list of events. Filters have been added to the list of events.
.. versionchanged:: 2.5
The ``testmode`` attribute has been added.
Endpoints Endpoints
--------- ---------
@@ -84,7 +79,6 @@ Endpoints
"name": {"en": "Sample Conference"}, "name": {"en": "Sample Conference"},
"slug": "sampleconf", "slug": "sampleconf",
"live": false, "live": false,
"testmode": false,
"currency": "EUR", "currency": "EUR",
"date_from": "2017-12-27T10:00:00Z", "date_from": "2017-12-27T10:00:00Z",
"date_to": null, "date_to": null,
@@ -143,7 +137,6 @@ Endpoints
"name": {"en": "Sample Conference"}, "name": {"en": "Sample Conference"},
"slug": "sampleconf", "slug": "sampleconf",
"live": false, "live": false,
"testmode": false,
"currency": "EUR", "currency": "EUR",
"date_from": "2017-12-27T10:00:00Z", "date_from": "2017-12-27T10:00:00Z",
"date_to": null, "date_to": null,
@@ -190,7 +183,6 @@ Endpoints
"name": {"en": "Sample Conference"}, "name": {"en": "Sample Conference"},
"slug": "sampleconf", "slug": "sampleconf",
"live": false, "live": false,
"testmode": false,
"currency": "EUR", "currency": "EUR",
"date_from": "2017-12-27T10:00:00Z", "date_from": "2017-12-27T10:00:00Z",
"date_to": null, "date_to": null,
@@ -219,7 +211,6 @@ Endpoints
"name": {"en": "Sample Conference"}, "name": {"en": "Sample Conference"},
"slug": "sampleconf", "slug": "sampleconf",
"live": false, "live": false,
"testmode": false,
"currency": "EUR", "currency": "EUR",
"date_from": "2017-12-27T10:00:00Z", "date_from": "2017-12-27T10:00:00Z",
"date_to": null, "date_to": null,
@@ -268,7 +259,6 @@ Endpoints
"name": {"en": "Sample Conference"}, "name": {"en": "Sample Conference"},
"slug": "sampleconf", "slug": "sampleconf",
"live": false, "live": false,
"testmode": false,
"currency": "EUR", "currency": "EUR",
"date_from": "2017-12-27T10:00:00Z", "date_from": "2017-12-27T10:00:00Z",
"date_to": null, "date_to": null,
@@ -297,7 +287,6 @@ Endpoints
"name": {"en": "Sample Conference"}, "name": {"en": "Sample Conference"},
"slug": "sampleconf", "slug": "sampleconf",
"live": false, "live": false,
"testmode": false,
"currency": "EUR", "currency": "EUR",
"date_from": "2017-12-27T10:00:00Z", "date_from": "2017-12-27T10:00:00Z",
"date_to": null, "date_to": null,
@@ -358,7 +347,6 @@ Endpoints
"name": {"en": "Sample Conference"}, "name": {"en": "Sample Conference"},
"slug": "sampleconf", "slug": "sampleconf",
"live": false, "live": false,
"testmode": false,
"currency": "EUR", "currency": "EUR",
"date_from": "2017-12-27T10:00:00Z", "date_from": "2017-12-27T10:00:00Z",
"date_to": null, "date_to": null,
+1 -44
View File
@@ -26,8 +26,6 @@ status string Order status, o
* ``p`` paid * ``p`` paid
* ``e`` expired * ``e`` expired
* ``c`` canceled * ``c`` canceled
testmode boolean If ``True``, this order was created when the event was in
test mode. Only orders in test mode can be deleted.
secret string The secret contained in the link sent to the customer secret string The secret contained in the link sent to the customer
email string The customer email address email string The customer email address
locale string The locale used for communication with this customer locale string The locale used for communication with this customer
@@ -133,10 +131,6 @@ last_modified datetime Last modificati
``order.status`` can no longer be ``r``, ``…/mark_canceled/`` now accepts a ``cancellation_fee`` parameter and ``order.status`` can no longer be ``r``, ``…/mark_canceled/`` now accepts a ``cancellation_fee`` parameter and
``…/mark_refunded/`` has been deprecated. ``…/mark_refunded/`` has been deprecated.
.. versionchanged:: 2.5:
The ``testmode`` attribute has been added and ``DELETE`` has been implemented for orders.
.. _order-position-resource: .. _order-position-resource:
Order position resource Order position resource
@@ -278,7 +272,6 @@ List of all orders
{ {
"code": "ABC12", "code": "ABC12",
"status": "p", "status": "p",
"testmode": false,
"secret": "k24fiuwvu8kxz3y1", "secret": "k24fiuwvu8kxz3y1",
"email": "tester@example.org", "email": "tester@example.org",
"locale": "en", "locale": "en",
@@ -377,14 +370,11 @@ List of all orders
``status``. Default: ``datetime`` ``status``. Default: ``datetime``
:query string code: Only return orders that match the given order code :query string code: Only return orders that match the given order code
:query string status: Only return orders in the given order status (see above) :query string status: Only return orders in the given order status (see above)
:query boolean testmode: Only return orders with ``testmode`` set to ``true`` or ``false``
:query boolean require_approval: If set to ``true`` or ``false``, only categories with this value for the field :query boolean require_approval: If set to ``true`` or ``false``, only categories with this value for the field
``require_approval`` will be returned. ``require_approval`` will be returned.
:query string email: Only return orders created with the given email address :query string email: Only return orders created with the given email address
:query string locale: Only return orders with the given customer locale :query string locale: Only return orders with the given customer locale
:query datetime modified_since: Only return orders that have changed since the given date. Be careful: We only :query datetime modified_since: Only return orders that have changed since the given date
recommend using this in combination with ``testmode=false``, since test mode orders can vanish at any time and
you will not notice it using this method.
:param organizer: The ``slug`` field of the organizer to fetch :param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch :param event: The ``slug`` field of the event to fetch
:resheader X-Page-Generated: The server time at the beginning of the operation. If you're using this API to fetch :resheader X-Page-Generated: The server time at the beginning of the operation. If you're using this API to fetch
@@ -419,7 +409,6 @@ Fetching individual orders
{ {
"code": "ABC12", "code": "ABC12",
"status": "p", "status": "p",
"testmode": false,
"secret": "k24fiuwvu8kxz3y1", "secret": "k24fiuwvu8kxz3y1",
"email": "tester@example.org", "email": "tester@example.org",
"locale": "en", "locale": "en",
@@ -563,37 +552,6 @@ Order ticket download
:statuscode 409: The file is not yet ready and will now be prepared. Retry the request after waiting for a few :statuscode 409: The file is not yet ready and will now be prepared. Retry the request after waiting for a few
seconds. seconds.
Deleting orders
---------------
.. http:delete:: /api/v1/organizers/(organizer)/events/(event)/orders/(code)/
Deletes an order. Works only if the order has ``testmode`` set to ``true``.
**Example request**:
.. sourcecode:: http
DELETE /api/v1/organizers/bigevents/events/sampleconf/orders/ABC12/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 204 No Content
Vary: Accept
Content-Type: application/json
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch
:param code: The ``code`` field of the order to delete
:statuscode 204: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to delete this resource **or** the order may not be deleted.
:statuscode 404: The requested order does not exist.
Creating orders Creating orders
--------------- ---------------
@@ -648,7 +606,6 @@ Creating orders
or in state ``confirmed``, depending on this value. If you create a paid order, the ``order_paid`` signal will or in state ``confirmed``, depending on this value. If you create a paid order, the ``order_paid`` signal will
**not** be sent out to plugins and no email will be sent. If you want that behavior, create an unpaid order and **not** be sent out to plugins and no email will be sent. If you want that behavior, create an unpaid order and
then call the ``mark_paid`` API method. then call the ``mark_paid`` API method.
* ``testmode`` (optional) Defaults to ``false``
* ``consume_carts`` (optional) A list of cart IDs. All cart positions with these IDs will be deleted if the * ``consume_carts`` (optional) A list of cart IDs. All cart positions with these IDs will be deleted if the
order creation is successful. Any quotas that become free by this operation will be credited to your order order creation is successful. Any quotas that become free by this operation will be credited to your order
creation. creation.
-2
View File
@@ -114,8 +114,6 @@ The provider class
.. autoattribute:: is_meta .. autoattribute:: is_meta
.. autoattribute:: test_mode_message
Additional views Additional views
---------------- ----------------
+1 -7
View File
@@ -82,12 +82,6 @@ Orders
^^^^^^ ^^^^^^
If a customer completes the checkout process, an **Order** will be created containing all the entered information. If a customer completes the checkout process, an **Order** will be created containing all the entered information.
An order can be in one of currently four states that are listed in the diagram below: An order can be in one of currently five states that are listed in the diagram below:
.. image:: /images/order_states.png .. image:: /images/order_states.png
There are additional "fake" states that are displayed like states but not represented as states in the system:
* An order is considered **canceled (with paid fee)** if it is in **paid** status but does not include any non-cancelled positions.
* An order is considered **requiring approval** if it is in **pending** status with the ``require_approval`` attribute set to ``True``.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 42 KiB

+2 -2
View File
@@ -4,6 +4,7 @@ Pending: Order is expecting payment\nOrder reduces quotas
Expired: Payment period is over\nOrder does not affect quotas Expired: Payment period is over\nOrder does not affect quotas
Paid: Order was successful\nOrder reduces quotas Paid: Order was successful\nOrder reduces quotas
Canceled: Order has been canceled\nOrder does not affect quotas Canceled: Order has been canceled\nOrder does not affect quotas
Refunded: Order has been refunded\nOrder does not affect quotas
[*] --> Pending: customer\nplaces order [*] --> Pending: customer\nplaces order
Pending --> Paid: successful payment Pending --> Paid: successful payment
@@ -11,9 +12,8 @@ Pending --> Expired: automatically\nor manually\non admin action
Expired --> Paid: if payment is received\nonly if quota left Expired --> Paid: if payment is received\nonly if quota left
Expired --> Canceled Expired --> Canceled
Expired --> Pending: manually\non admin action Expired --> Pending: manually\non admin action
Paid --> Canceled: manually on\nadmin action\nor if an external\npayment provider\nnotifies about a\npayment refund Paid --> Refunded: manually on\nadmin action\nor if an external\npayment provider\nnotifies about a\npayment refund
Pending --> Canceled: on admin or\ncustomer action Pending --> Canceled: on admin or\ncustomer action
Paid -> Pending: manually on admin action Paid -> Pending: manually on admin action
[*] --> Paid: customer\nplaces free order
@enduml @enduml
-1
View File
@@ -111,7 +111,6 @@ submodule
subpath subpath
Symfony Symfony
systemd systemd
testmode
testutils testutils
timestamp timestamp
tuples tuples
+16 -4
View File
@@ -4,10 +4,22 @@ FAQ and Troubleshooting
How can I test my shop before taking it live? How can I test my shop before taking it live?
--------------------------------------------- ---------------------------------------------
On your event dashboard, click on the first tile that shows your shop status. On the lower part of this page, you can There are multiple ways to do this.
place your event into "test mode". In "test mode", everything behaves the same, but orders created during test mode can
later be fully deleted. Be sure to actually delete them when or after you turn off test mode, since test mode orders First, you could just create some orders in your real shop and cancel/refund them later. If you don't want to process
still count toward your quotas and are included in your reports. real payments for the tests, you can either use a "manual" payment method like bank transfer and just mark the orders
as paid with the button in the backend, or if you want to use e.g. Stripe, you can configure pretix to use your keys
for the Stripe test system and use their test credit cars. Read our :ref:`Stripe documentation <stripe>` for more
information.
Second, you could create a separate event, just for testing. In the last step of the :ref:`event creation process <event_create>`,
you can specify that you want to copy all settings from your real event, so you don't have to do all of it twice.
We are planning to add a dedicated test mode in a later version of pretix.
If you are using the hosted service at pretix.eu and want to get rid of the test orders completely, contact us at
support@pretix.eu and we can remove them for you. Please note that we only are able to do that *before* you have
received any real orders (i.e. taken the shop public). We won't charge any fees for test orders or test events.
How do I delete an event? How do I delete an event?
------------------------- -------------------------
+1 -1
View File
@@ -48,7 +48,7 @@ class EventSerializer(I18nAwareModelSerializer):
class Meta: class Meta:
model = Event model = Event
fields = ('name', 'slug', 'live', 'testmode', 'currency', 'date_from', fields = ('name', 'slug', 'live', 'currency', 'date_from',
'date_to', 'date_admission', 'is_public', 'presale_start', 'date_to', 'date_admission', 'is_public', 'presale_start',
'presale_end', 'location', 'has_subevents', 'meta_data', 'plugins') 'presale_end', 'location', 'has_subevents', 'meta_data', 'plugins')
+12 -14
View File
@@ -12,7 +12,6 @@ from rest_framework.reverse import reverse
from pretix.api.serializers.i18n import I18nAwareModelSerializer from pretix.api.serializers.i18n import I18nAwareModelSerializer
from pretix.base.channels import get_all_sales_channels from pretix.base.channels import get_all_sales_channels
from pretix.base.i18n import language
from pretix.base.models import ( from pretix.base.models import (
Checkin, Invoice, InvoiceAddress, InvoiceLine, Order, OrderPosition, Checkin, Invoice, InvoiceAddress, InvoiceLine, Order, OrderPosition,
Question, QuestionAnswer, Question, QuestionAnswer,
@@ -141,21 +140,20 @@ class PdfDataSerializer(serializers.Field):
res = {} res = {}
ev = instance.subevent or instance.order.event ev = instance.subevent or instance.order.event
with language(instance.order.locale): # This needs to have some extra performance improvements to avoid creating hundreds of queries when
# This needs to have some extra performance improvements to avoid creating hundreds of queries when # we serialize a list.
# we serialize a list.
if 'vars' not in self.context: if 'vars' not in self.context:
self.context['vars'] = get_variables(self.context['request'].event) self.context['vars'] = get_variables(self.context['request'].event)
for k, f in self.context['vars'].items(): for k, f in self.context['vars'].items():
res[k] = f['evaluate'](instance, instance.order, ev) res[k] = f['evaluate'](instance, instance.order, ev)
if not hasattr(ev, '_cached_meta_data'): if not hasattr(ev, '_cached_meta_data'):
ev._cached_meta_data = ev.meta_data ev._cached_meta_data = ev.meta_data
for k, v in ev._cached_meta_data.items(): for k, v in ev._cached_meta_data.items():
res['meta:' + k] = v res['meta:' + k] = v
return res return res
@@ -231,7 +229,7 @@ class OrderSerializer(I18nAwareModelSerializer):
class Meta: class Meta:
model = Order model = Order
fields = ('code', 'status', 'testmode', 'secret', 'email', 'locale', 'datetime', 'expires', 'payment_date', fields = ('code', 'status', 'secret', 'email', 'locale', 'datetime', 'expires', 'payment_date',
'payment_provider', 'fees', 'total', 'comment', 'invoice_address', 'positions', 'downloads', 'payment_provider', 'fees', 'total', 'comment', 'invoice_address', 'positions', 'downloads',
'checkin_attention', 'last_modified', 'payments', 'refunds', 'require_approval', 'sales_channel') 'checkin_attention', 'last_modified', 'payments', 'refunds', 'require_approval', 'sales_channel')
@@ -413,7 +411,7 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
class Meta: class Meta:
model = Order model = Order
fields = ('code', 'status', 'testmode', 'email', 'locale', 'payment_provider', 'fees', 'comment', 'sales_channel', fields = ('code', 'status', 'email', 'locale', 'payment_provider', 'fees', 'comment', 'sales_channel',
'invoice_address', 'positions', 'checkin_attention', 'payment_info', 'consume_carts') 'invoice_address', 'positions', 'checkin_attention', 'payment_info', 'consume_carts')
def validate_payment_provider(self, pp): def validate_payment_provider(self, pp):
+3 -10
View File
@@ -16,7 +16,7 @@ from rest_framework.exceptions import (
APIException, NotFound, PermissionDenied, ValidationError, APIException, NotFound, PermissionDenied, ValidationError,
) )
from rest_framework.filters import OrderingFilter from rest_framework.filters import OrderingFilter
from rest_framework.mixins import CreateModelMixin, DestroyModelMixin from rest_framework.mixins import CreateModelMixin
from rest_framework.response import Response from rest_framework.response import Response
from pretix.api.models import OAuthAccessToken from pretix.api.models import OAuthAccessToken
@@ -51,10 +51,10 @@ class OrderFilter(FilterSet):
class Meta: class Meta:
model = Order model = Order
fields = ['code', 'status', 'email', 'locale', 'testmode', 'require_approval'] fields = ['code', 'status', 'email', 'locale', 'require_approval']
class OrderViewSet(DestroyModelMixin, CreateModelMixin, viewsets.ReadOnlyModelViewSet): class OrderViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
serializer_class = OrderSerializer serializer_class = OrderSerializer
queryset = Order.objects.none() queryset = Order.objects.none()
filter_backends = (DjangoFilterBackend, OrderingFilter) filter_backends = (DjangoFilterBackend, OrderingFilter)
@@ -378,13 +378,6 @@ class OrderViewSet(DestroyModelMixin, CreateModelMixin, viewsets.ReadOnlyModelVi
def perform_create(self, serializer): def perform_create(self, serializer):
serializer.save() serializer.save()
def perform_destroy(self, instance):
if not instance.testmode:
raise PermissionDenied('Only test mode orders can be deleted.')
with transaction.atomic():
self.get_object().gracefully_delete(user=self.request.user if self.request.user.is_authenticated else None, auth=self.request.auth)
class OrderPositionFilter(FilterSet): class OrderPositionFilter(FilterSet):
order = django_filters.CharFilter(field_name='order', lookup_expr='code__iexact') order = django_filters.CharFilter(field_name='order', lookup_expr='code__iexact')
+1 -1
View File
@@ -113,7 +113,7 @@ def register_default_webhook_events(sender, **kwargs):
_('New order placed'), _('New order placed'),
), ),
ParametrizedOrderWebhookEvent( ParametrizedOrderWebhookEvent(
'pretix.event.order.placed.require_approval', 'pretix.event.order.placed.required_approval',
_('New order requires approval'), _('New order requires approval'),
), ),
ParametrizedOrderWebhookEvent( ParametrizedOrderWebhookEvent(
+1 -1
View File
@@ -27,7 +27,7 @@ class CustomSMTPBackend(EmailBackend):
if code != 250: if code != 250:
logger.warn('Error testing mail settings, code %d, resp: %s' % (code, resp)) logger.warn('Error testing mail settings, code %d, resp: %s' % (code, resp))
raise SMTPResponseException(code, resp) raise SMTPResponseException(code, resp)
(code, resp) = self.connection.rcpt('testdummy@pretix.eu') (code, resp) = self.connection.rcpt('test@example.com')
if (code != 250) and (code != 251): if (code != 250) and (code != 251):
logger.warn('Error testing mail settings, code %d, resp: %s' % (code, resp)) logger.warn('Error testing mail settings, code %d, resp: %s' % (code, resp))
raise SMTPResponseException(code, resp) raise SMTPResponseException(code, resp)
-28
View File
@@ -1,11 +1,9 @@
import logging import logging
import i18nfield.forms import i18nfield.forms
from django import forms
from django.forms.models import ModelFormMetaclass from django.forms.models import ModelFormMetaclass
from django.utils import six from django.utils import six
from django.utils.crypto import get_random_string from django.utils.crypto import get_random_string
from formtools.wizard.views import SessionWizardView
from hierarkey.forms import HierarkeyForm from hierarkey.forms import HierarkeyForm
from pretix.base.models import Event from pretix.base.models import Event
@@ -73,29 +71,3 @@ class SettingsForm(i18nfield.forms.I18nFormMixin, HierarkeyForm):
fname = '%s/%s.%s.%s' % (self.obj.slug, name, nonce, name.split('.')[-1]) fname = '%s/%s.%s.%s' % (self.obj.slug, name, nonce, name.split('.')[-1])
# TODO: make sure pub is always correct # TODO: make sure pub is always correct
return 'pub/' + fname return 'pub/' + fname
class PrefixForm(forms.Form):
prefix = forms.CharField(widget=forms.HiddenInput)
class SafeSessionWizardView(SessionWizardView):
def get_prefix(self, request, *args, **kwargs):
if hasattr(request, '_session_wizard_prefix'):
return request._session_wizard_prefix
prefix_form = PrefixForm(self.request.POST, prefix=super().get_prefix(request, *args, **kwargs))
if not prefix_form.is_valid():
request._session_wizard_prefix = get_random_string(length=24)
else:
request._session_wizard_prefix = prefix_form.cleaned_data['prefix']
return request._session_wizard_prefix
def get_context_data(self, form, **kwargs):
context = super().get_context_data(form=form, **kwargs)
context['wizard']['prefix_form'] = PrefixForm(
prefix=super().get_prefix(self.request),
initial={
'prefix': self.get_prefix(self.request)
}
)
return context
+1 -8
View File
@@ -9,7 +9,7 @@ import vat_moss.exchange_rates
from django.contrib.staticfiles import finders from django.contrib.staticfiles import finders
from django.dispatch import receiver from django.dispatch import receiver
from django.utils.formats import date_format, localize from django.utils.formats import date_format, localize
from django.utils.translation import pgettext, ugettext from django.utils.translation import pgettext
from PIL.Image import BICUBIC from PIL.Image import BICUBIC
from reportlab.lib import pagesizes from reportlab.lib import pagesizes
from reportlab.lib.enums import TA_LEFT from reportlab.lib.enums import TA_LEFT
@@ -267,13 +267,6 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
canvas.saveState() canvas.saveState()
canvas.setFont(self.font_regular, 8) canvas.setFont(self.font_regular, 8)
if self.invoice.order.testmode:
canvas.saveState()
canvas.setFont('OpenSansBd', 30)
canvas.setFillColorRGB(32, 0, 0)
canvas.drawRightString(self.pagesize[0] - 20 * mm, (297 - 100) * mm, ugettext('TEST MODE'))
canvas.restoreState()
for i, line in enumerate(self.invoice.footer_text.split('\n')[::-1]): for i, line in enumerate(self.invoice.footer_text.split('\n')[::-1]):
canvas.drawCentredString(self.pagesize[0] / 2, 25 + (3.5 * i) * mm, line.strip()) canvas.drawCentredString(self.pagesize[0] / 2, 25 + (3.5 * i) * mm, line.strip())
@@ -0,0 +1,30 @@
from django.core.management.base import BaseCommand
from django.core.paginator import Paginator
from pretix.base.models import Order, OrderSearchIndex
class Command(BaseCommand):
help = "Rebuild search index"
def add_arguments(self, parser):
parser.add_argument(
'--clean', action='store_true', dest='clean',
help="Clear search index before run.",
)
def iter_pages(self, qs):
paginator = Paginator(qs, 500)
for index in range(paginator.num_pages):
yield paginator.get_page(index + 1)
def handle(self, *args, **options):
if options.get('clean'):
OrderSearchIndex.objects.all().delete()
qs = Order.objects.select_related('event', 'event__organizer', 'invoice_address').prefetch_related('all_positions', 'payments')
for page in self.iter_pages(qs):
if options.get('clean'):
OrderSearchIndex.objects.bulk_create([o.index() for o in page])
else:
for o in page:
o.index()
+7 -16
View File
@@ -129,22 +129,13 @@ def get_language_from_request(request: HttpRequest) -> str:
if _supported is None: if _supported is None:
_supported = OrderedDict(settings.LANGUAGES) _supported = OrderedDict(settings.LANGUAGES)
if request.path.startswith(get_script_prefix() + 'control'): return (
return ( get_language_from_user_settings(request)
get_language_from_user_settings(request) or get_language_from_session_or_cookie(request)
or get_language_from_session_or_cookie(request) or get_language_from_browser(request)
or get_language_from_browser(request) or get_language_from_event(request)
or get_language_from_event(request) or get_default_language()
or get_default_language() )
)
else:
return (
get_language_from_session_or_cookie(request)
or get_language_from_user_settings(request)
or get_language_from_browser(request)
or get_language_from_event(request)
or get_default_language()
)
def _parse_csp(header): def _parse_csp(header):
@@ -0,0 +1,49 @@
# Generated by Django 2.1 on 2019-02-14 10:41
import django.db.models.deletion
from django.db import migrations, models
def create_extension(apps, schema_editor):
if 'postgresql' in schema_editor.connection.settings_dict['ENGINE']:
schema_editor.execute("CREATE EXTENSION IF NOT EXISTS pg_trgm;")
def create_trigram_index(apps, schema_editor):
if 'postgresql' in schema_editor.connection.settings_dict['ENGINE']:
schema_editor.execute("CREATE INDEX pretixbase_ordersearchindex_s ON pretixbase_ordersearchindex USING gin (search_body gin_trgm_ops);")
schema_editor.execute("CREATE INDEX pretixbase_ordersearchindex_spp ON pretixbase_ordersearchindex USING gin (payment_providers gin_trgm_ops);")
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0109_auto_20190208_1432'),
]
operations = [
migrations.RunPython(create_extension),
migrations.CreateModel(
name='OrderSearchIndex',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('search_body', models.TextField()),
('payment_providers', models.TextField()),
],
),
migrations.AddField(
model_name='ordersearchindex',
name='event',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pretixbase.Event'),
),
migrations.AddField(
model_name='ordersearchindex',
name='order',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pretixbase.Order', unique=True),
),
migrations.AddField(
model_name='ordersearchindex',
name='organizer',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pretixbase.Organizer'),
),
migrations.RunPython(create_trigram_index)
]
@@ -1,23 +0,0 @@
# Generated by Django 2.1.5 on 2019-02-19 12:45
import django.db.models.deletion
import jsonfallback.fields
from django.db import migrations, models
import pretix.base.models.fields
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0109_auto_20190208_1432'),
]
operations = [
migrations.AlterField(
model_name='event',
name='plugins',
field=models.TextField(blank=True, default='', verbose_name='Plugins'),
preserve_default=False,
),
]
@@ -1,27 +0,0 @@
# Generated by Django 2.1.5 on 2019-02-19 09:49
import django.db.models.deletion
import jsonfallback.fields
from django.db import migrations, models
import pretix.base.models.fields
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0110_auto_20190219_1245'),
]
operations = [
migrations.AddField(
model_name='order',
name='testmode',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='event',
name='testmode',
field=models.BooleanField(default=False),
),
]
+1
View File
@@ -7,6 +7,7 @@ from .event import (
Event, Event_SettingsStore, EventLock, EventMetaProperty, EventMetaValue, Event, Event_SettingsStore, EventLock, EventMetaProperty, EventMetaValue,
RequiredAction, SubEvent, SubEventMetaValue, generate_invite_token, RequiredAction, SubEvent, SubEventMetaValue, generate_invite_token,
) )
from .index import OrderSearchIndex
from .invoices import Invoice, InvoiceLine, invoice_filename from .invoices import Invoice, InvoiceLine, invoice_filename
from .items import ( from .items import (
Item, ItemAddOn, ItemCategory, ItemVariation, Question, QuestionOption, Item, ItemAddOn, ItemCategory, ItemVariation, Question, QuestionOption,
-7
View File
@@ -77,13 +77,6 @@ class LoggingMixin:
logentry = LogEntry(content_object=self, user=user, action_type=action, event=event, **kwargs) logentry = LogEntry(content_object=self, user=user, action_type=action, event=event, **kwargs)
if isinstance(data, dict): if isinstance(data, dict):
sensitivekeys = ['password', 'secret', 'api_key']
for sensitivekey in sensitivekeys:
for k, v in data.items():
if (sensitivekey in k) and v:
data[k] = "********"
logentry.data = json.dumps(data, cls=CustomJSONEncoder) logentry.data = json.dumps(data, cls=CustomJSONEncoder)
elif data: elif data:
raise TypeError("You should only supply dictionaries as log data.") raise TypeError("You should only supply dictionaries as log data.")
+1 -4
View File
@@ -242,8 +242,6 @@ class Event(EventMixin, LoggedModel):
:param organizer: The organizer this event belongs to :param organizer: The organizer this event belongs to
:type organizer: Organizer :type organizer: Organizer
:param testmode: This event is in test mode
:type testmode: bool
:param name: This event's full title :param name: This event's full title
:type name: str :type name: str
:param slug: A short, alphanumeric, all-lowercase name for use in URLs. The slug has to :param slug: A short, alphanumeric, all-lowercase name for use in URLs. The slug has to
@@ -273,7 +271,6 @@ class Event(EventMixin, LoggedModel):
settings_namespace = 'event' settings_namespace = 'event'
CURRENCY_CHOICES = [(c.alpha_3, c.alpha_3 + " - " + c.name) for c in settings.CURRENCIES] CURRENCY_CHOICES = [(c.alpha_3, c.alpha_3 + " - " + c.name) for c in settings.CURRENCIES]
organizer = models.ForeignKey(Organizer, related_name="events", on_delete=models.PROTECT) organizer = models.ForeignKey(Organizer, related_name="events", on_delete=models.PROTECT)
testmode = models.BooleanField(default=False)
name = I18nCharField( name = I18nCharField(
max_length=200, max_length=200,
verbose_name=_("Event name"), verbose_name=_("Event name"),
@@ -324,7 +321,7 @@ class Event(EventMixin, LoggedModel):
verbose_name=_("Location"), verbose_name=_("Location"),
) )
plugins = models.TextField( plugins = models.TextField(
null=False, blank=True, null=True, blank=True,
verbose_name=_("Plugins"), verbose_name=_("Plugins"),
) )
comment = models.TextField( comment = models.TextField(
+11
View File
@@ -0,0 +1,11 @@
from django.db import models
DELIMITER = "\x1F"
class OrderSearchIndex(models.Model):
order = models.ForeignKey('Order', unique=True, null=False, on_delete=models.CASCADE)
event = models.ForeignKey('Event', null=False, on_delete=models.CASCADE)
organizer = models.ForeignKey('Organizer', null=False, on_delete=models.CASCADE)
search_body = models.TextField()
payment_providers = models.TextField()
+2 -7
View File
@@ -118,8 +118,8 @@ class Invoice(models.Model):
self.invoice_from, self.invoice_from,
(self.invoice_from_zipcode or "") + " " + (self.invoice_from_city or ""), (self.invoice_from_zipcode or "") + " " + (self.invoice_from_city or ""),
str(self.invoice_from_country), str(self.invoice_from_country),
pgettext("invoice", "VAT-ID: %s") % self.invoice_from_vat_id if self.invoice_from_vat_id else "", pgettext("invoice", "VAT-ID: %s" % self.invoice_from_vat_id) if self.invoice_from_vat_id else "",
pgettext("invoice", "Tax ID: %s") % self.invoice_from_tax_id if self.invoice_from_tax_id else "", pgettext("invoice", "Tax ID: %s" % self.invoice_from_tax_id) if self.invoice_from_tax_id else "",
] ]
return '\n'.join([p.strip() for p in parts if p and p.strip()]) return '\n'.join([p.strip() for p in parts if p and p.strip()])
@@ -150,8 +150,6 @@ class Invoice(models.Model):
if not self.prefix: if not self.prefix:
self.prefix = self.event.settings.invoice_numbers_prefix or (self.event.slug.upper() + '-') self.prefix = self.event.settings.invoice_numbers_prefix or (self.event.slug.upper() + '-')
if not self.invoice_no: if not self.invoice_no:
if self.order.testmode:
self.prefix += 'TEST-'
for i in range(10): for i in range(10):
if self.event.settings.get('invoice_numbers_consecutive'): if self.event.settings.get('invoice_numbers_consecutive'):
self.invoice_no = self._get_numeric_invoice_number() self.invoice_no = self._get_numeric_invoice_number()
@@ -235,6 +233,3 @@ class InvoiceLine(models.Model):
class Meta: class Meta:
ordering = ('position', 'pk') ordering = ('position', 'pk')
def __str__(self):
return 'Line {} of invoice {}'.format(self.position, self.invoice)
+39 -33
View File
@@ -76,8 +76,6 @@ class Order(LockModel, LoggedModel):
:type event: Event :type event: Event
:param email: The email of the person who ordered this :param email: The email of the person who ordered this
:type email: str :type email: str
:param testmode: Whether this is a test mode order
:type testmode: bool
:param locale: The locale of this order :param locale: The locale of this order
:type locale: str :type locale: str
:param secret: A secret string that is required to modify the order :param secret: A secret string that is required to modify the order
@@ -123,7 +121,6 @@ class Order(LockModel, LoggedModel):
verbose_name=_("Status"), verbose_name=_("Status"),
db_index=True db_index=True
) )
testmode = models.BooleanField(default=False)
event = models.ForeignKey( event = models.ForeignKey(
Event, Event,
verbose_name=_("Event"), verbose_name=_("Event"),
@@ -188,23 +185,6 @@ class Order(LockModel, LoggedModel):
def __str__(self): def __str__(self):
return self.full_code return self.full_code
def gracefully_delete(self, user=None, auth=None):
if not self.testmode:
raise TypeError("Only test mode orders can be deleted.")
self.event.log_action(
'pretix.event.order.deleted', user=user, auth=auth,
data={
'code': self.code,
}
)
OrderPosition.all.filter(order=self, addon_to__isnull=False).delete()
OrderPosition.all.filter(order=self).delete()
OrderFee.all.filter(order=self).delete()
self.refunds.all().delete()
self.payments.all().delete()
self.event.cache.delete('complain_testmode_orders')
self.delete()
@property @property
def fees(self): def fees(self):
""" """
@@ -510,10 +490,6 @@ class Order(LockModel, LoggedModel):
charset = list('ABCDEFGHJKLMNPQRSTUVWXYZ3789') charset = list('ABCDEFGHJKLMNPQRSTUVWXYZ3789')
while True: while True:
code = get_random_string(length=settings.ENTROPY['order_code'], allowed_chars=charset) code = get_random_string(length=settings.ENTROPY['order_code'], allowed_chars=charset)
if self.testmode:
# Subtle way to recognize test orders while debugging: They all contain a 0 at the second place,
# even though zeros are not used outside test mode.
code = code[0] + "0" + code[2:]
if not Order.objects.filter(event__organizer=self.event.organizer, code=code).exists(): if not Order.objects.filter(event__organizer=self.event.organizer, code=code).exists():
self.code = code self.code = code
return return
@@ -685,9 +661,6 @@ class Order(LockModel, LoggedModel):
if not self.email: if not self.email:
return return
for k, v in self.event.meta_data.items():
context['meta_' + k] = v
with language(self.locale): with language(self.locale):
recipient = self.email recipient = self.email
try: try:
@@ -720,6 +693,45 @@ class Order(LockModel, LoggedModel):
continue continue
yield op yield op
def index(self, save=True):
from .index import OrderSearchIndex
indexed_strings = [
self.code,
self.full_code,
self.email,
self.comment,
]
try:
indexed_strings.append(self.invoice_address.name_cached)
indexed_strings.append(self.invoice_address.company)
except InvoiceAddress.DoesNotExist:
pass
for p in self.all_positions.all():
indexed_strings.append(p.attendee_name_cached)
indexed_strings.append(p.attendee_email)
indexed_strings.append(p.secret)
pprovs = set()
for p in self.payments.all():
pprovs.add(p.provider)
if save:
return OrderSearchIndex.objects.update_or_create(
order=self,
defaults={
'event': self.event,
'organizer': self.event.organizer,
'search_body': '\x1E'.join([str(v) for v in indexed_strings if v]),
'payment_providers': '\x1E' + '\x1E'.join([str(v) for v in pprovs if v]) + '\x1E',
}
)[0]
else:
return OrderSearchIndex(
order=self,
event=self.event,
organizer=self.event.organizer,
search_body='\x1E'.join([str(v) for v in indexed_strings if v]),
payment_providers='\x1E' + '\x1E'.join([str(v) for v in pprovs if v]) + '\x1E',
)
def answerfile_name(instance, filename: str) -> str: def answerfile_name(instance, filename: str) -> str:
secret = get_random_string(length=32, allowed_chars=string.ascii_letters + string.digits) secret = get_random_string(length=32, allowed_chars=string.ascii_letters + string.digits)
@@ -1064,9 +1076,6 @@ class OrderPayment(models.Model):
class Meta: class Meta:
ordering = ('local_id',) ordering = ('local_id',)
def __str__(self):
return self.full_id
@property @property
def info_data(self): def info_data(self):
""" """
@@ -1373,9 +1382,6 @@ class OrderRefund(models.Model):
class Meta: class Meta:
ordering = ('local_id',) ordering = ('local_id',)
def __str__(self):
return self.full_id
@property @property
def info_data(self): def info_data(self):
""" """
-1
View File
@@ -177,7 +177,6 @@ class ParametrizedOrderNotificationType(NotificationType):
n.add_attribute(_('Event'), order.event.name) n.add_attribute(_('Event'), order.event.name)
n.add_attribute(_('Order code'), order.code) n.add_attribute(_('Order code'), order.code)
n.add_attribute(_('Order total'), money_filter(order.total, logentry.event.currency)) n.add_attribute(_('Order total'), money_filter(order.total, logentry.event.currency))
n.add_attribute(_('Pending amount'), money_filter(order.pending_sum, logentry.event.currency))
n.add_attribute(_('Order date'), date_format(order.datetime, 'SHORT_DATETIME_FORMAT')) n.add_attribute(_('Order date'), date_format(order.datetime, 'SHORT_DATETIME_FORMAT'))
n.add_attribute(_('Order status'), order.get_status_display()) n.add_attribute(_('Order status'), order.get_status_display())
n.add_attribute(_('Order positions'), str(order.positions.count())) n.add_attribute(_('Order positions'), str(order.positions.count()))
-17
View File
@@ -88,18 +88,6 @@ class BasePaymentProvider:
""" """
return self.settings.get('_enabled', as_type=bool) return self.settings.get('_enabled', as_type=bool)
@property
def test_mode_message(self) -> str:
"""
If this property is set to a string, this will be displayed when this payment provider is selected
while the event is in test mode. You should use it to explain to your user how your plugin behaves,
e.g. if it falls back to a test mode automatically as well or if actual payments will be performed.
If you do not set this (or, return ``None``), pretix will show a default message warning the user
that this plugin does not support test mode payments.
"""
return None
def calculate_fee(self, price: Decimal) -> Decimal: def calculate_fee(self, price: Decimal) -> Decimal:
""" """
Calculate the fee for this payment provider which will be added to Calculate the fee for this payment provider which will be added to
@@ -725,11 +713,6 @@ class ManualPayment(BasePaymentProvider):
identifier = 'manual' identifier = 'manual'
verbose_name = _('Manual payment') verbose_name = _('Manual payment')
@property
def test_mode_message(self):
return _('In test mode, you can just manually mark this order as paid in the backend after it has been '
'created.')
@property @property
def is_implicit(self): def is_implicit(self):
return 'pretix.plugins.manualpayment' not in self.event.plugins return 'pretix.plugins.manualpayment' not in self.event.plugins
+9 -9
View File
@@ -670,15 +670,15 @@ class CartManager:
self._check_max_cart_size() self._check_max_cart_size()
self._calculate_expiry() self._calculate_expiry()
# with self.event.lock() as now_dt: with self.event.lock() as now_dt:
with transaction.atomic(): with transaction.atomic():
self.now_dt = now() self.now_dt = now_dt
self._extend_expiry_of_valid_existing_positions() self._extend_expiry_of_valid_existing_positions()
err = self._delete_out_of_timeframe() err = self._delete_out_of_timeframe()
err = self.extend_expired_positions() or err err = self.extend_expired_positions() or err
err = self._perform_operations() or err err = self._perform_operations() or err
if err: if err:
raise CartError(err) raise CartError(err)
def update_tax_rates(event: Event, cart_id: str, invoice_address: InvoiceAddress): def update_tax_rates(event: Event, cart_id: str, invoice_address: InvoiceAddress):
-6
View File
@@ -79,10 +79,6 @@ def mail(email: str, subject: str, template: Union[str, LazyI18nString],
headers = headers or {} headers = headers or {}
with language(locale): with language(locale):
if isinstance(context, dict) and event:
for k, v in event.meta_data.items():
context['meta_' + k] = v
if isinstance(context, dict) and order: if isinstance(context, dict) and order:
try: try:
context.update({ context.update({
@@ -130,8 +126,6 @@ def mail(email: str, subject: str, template: Union[str, LazyI18nString],
body_plain += "\r\n\r\n-- \r\n" body_plain += "\r\n\r\n-- \r\n"
if order: if order:
if order.testmode:
subject = "[TESTMODE] " + subject
body_plain += _( body_plain += _(
"You are receiving this email because you placed an order for {event}." "You are receiving this email because you placed an order for {event}."
).format(event=event.name) ).format(event=event.name)
+14 -16
View File
@@ -533,7 +533,6 @@ def _create_order(event: Event, email: str, positions: List[CartPosition], now_d
datetime=now_dt, datetime=now_dt,
locale=locale, locale=locale,
total=total, total=total,
testmode=event.testmode,
meta_info=json.dumps(meta_info or {}), meta_info=json.dumps(meta_info or {}),
require_approval=any(p.item.require_approval for p in positions), require_approval=any(p.item.require_approval for p in positions),
sales_channel=sales_channel sales_channel=sales_channel
@@ -597,16 +596,16 @@ def _perform_order(event: str, payment_provider: str, position_ids: List[str],
except InvoiceAddress.DoesNotExist: except InvoiceAddress.DoesNotExist:
pass pass
# with event.lock() as now_dt: with event.lock() as now_dt:
now_dt = now() positions = list(CartPosition.objects.filter(
positions = list(CartPosition.objects.filter(id__in=position_ids).select_related('item', 'variation', 'subevent')) id__in=position_ids).select_related('item', 'variation', 'subevent'))
if len(positions) == 0: if len(positions) == 0:
raise OrderError(error_messages['empty']) raise OrderError(error_messages['empty'])
if len(position_ids) != len(positions): if len(position_ids) != len(positions):
raise OrderError(error_messages['internal']) raise OrderError(error_messages['internal'])
_check_positions(event, now_dt, positions, address=addr) _check_positions(event, now_dt, positions, address=addr)
order = _create_order(event, email, positions, now_dt, pprov, order = _create_order(event, email, positions, now_dt, pprov,
locale=locale, address=addr, meta_info=meta_info, sales_channel=sales_channel) locale=locale, address=addr, meta_info=meta_info, sales_channel=sales_channel)
invoice = order.invoices.last() # Might be generated by plugin already invoice = order.invoices.last() # Might be generated by plugin already
if event.settings.get('invoice_generate') == 'True' and invoice_qualified(order): if event.settings.get('invoice_generate') == 'True' and invoice_qualified(order):
@@ -823,7 +822,7 @@ class OrderChangeManager:
if keep_price: if keep_price:
price = TaxedPrice(gross=position.price, net=position.price - position.tax_value, price = TaxedPrice(gross=position.price, net=position.price - position.tax_value,
tax=position.tax_value, rate=position.tax_rate, tax=position.tax_value, rate=position.tax_rate,
name=position.tax_rule.name if position.tax_rule else None) name=position.tax_rule.name)
else: else:
price = get_price(item, variation, voucher=position.voucher, subevent=position.subevent, price = get_price(item, variation, voucher=position.voucher, subevent=position.subevent,
invoice_address=self._invoice_address) invoice_address=self._invoice_address)
@@ -963,7 +962,7 @@ class OrderChangeManager:
) )
self.order.save() self.order.save()
elif self.order.status in (Order.STATUS_PENDING, Order.STATUS_EXPIRED) and self._totaldiff < 0: elif self.order.status in (Order.STATUS_PENDING, Order.STATUS_EXPIRED) and self._totaldiff < 0:
if self.order.pending_sum <= Decimal('0.00') and not self.order.require_approval: if self.order.pending_sum <= Decimal('0.00'):
self.order.status = Order.STATUS_PAID self.order.status = Order.STATUS_PAID
self.order.save() self.order.save()
elif self.open_payment: elif self.open_payment:
@@ -983,7 +982,7 @@ class OrderChangeManager:
}, user=self.user, auth=self.auth) }, user=self.user, auth=self.auth)
def _check_paid_to_free(self): def _check_paid_to_free(self):
if self.order.total == 0 and (self._totaldiff < 0 or (self.split_order and self.split_order.total > 0)) and not self.order.require_approval: if self.order.total == 0 and (self._totaldiff < 0 or (self.split_order and self.split_order.total > 0)):
# if the order becomes free, mark it paid using the 'free' provider # if the order becomes free, mark it paid using the 'free' provider
# this could happen if positions have been made cheaper or removed (_totaldiff < 0) # this could happen if positions have been made cheaper or removed (_totaldiff < 0)
# or positions got split off to a new order (split_order with positive total) # or positions got split off to a new order (split_order with positive total)
@@ -998,7 +997,7 @@ class OrderChangeManager:
except Quota.QuotaExceededException: except Quota.QuotaExceededException:
raise OrderError(self.error_messages['paid_to_free_exceeded']) raise OrderError(self.error_messages['paid_to_free_exceeded'])
if self.split_order and self.split_order.total == 0 and not self.split_order.require_approval: if self.split_order and self.split_order.total == 0:
p = self.split_order.payments.create( p = self.split_order.payments.create(
state=OrderPayment.PAYMENT_STATE_CREATED, state=OrderPayment.PAYMENT_STATE_CREATED,
provider='free', provider='free',
@@ -1126,7 +1125,6 @@ class OrderChangeManager:
split_order.code = None split_order.code = None
split_order.datetime = now() split_order.datetime = now()
split_order.secret = generate_secret() split_order.secret = generate_secret()
split_order.require_approval = self.order.require_approval and any(p.item.require_approval for p in split_positions)
split_order.save() split_order.save()
split_order.log_action('pretix.event.order.changed.split_from', user=self.user, auth=self.auth, data={ split_order.log_action('pretix.event.order.changed.split_from', user=self.user, auth=self.auth, data={
'original_order': self.order.code 'original_order': self.order.code
+1 -1
View File
@@ -84,7 +84,7 @@ def markdown_compile(source):
source, source,
extensions=[ extensions=[
'markdown.extensions.sane_lists', 'markdown.extensions.sane_lists',
'markdown.extensions.nl2br' # 'markdown.extensions.nl2br', # TODO: Enable, but check backwards-compatibility issues e.g. with mails
] ]
), ),
tags=ALLOWED_TAGS, tags=ALLOWED_TAGS,
-9
View File
@@ -52,15 +52,6 @@ def contextprocessor(request):
ctx['has_domain'] = request.event.organizer.domains.exists() ctx['has_domain'] = request.event.organizer.domains.exists()
if not request.event.testmode:
complain_testmode_orders = request.event.cache.get('complain_testmode_orders')
if complain_testmode_orders is None:
complain_testmode_orders = request.event.orders.filter(testmode=True).exists()
request.event.cache.set('complain_testmode_orders', complain_testmode_orders, 30)
ctx['complain_testmode_orders'] = complain_testmode_orders
else:
ctx['complain_testmode_orders'] = False
if not request.event.live and ctx['has_domain']: if not request.event.live and ctx['has_domain']:
child_sess = request.session.get('child_session_{}'.format(request.event.pk)) child_sess = request.session.get('child_session_{}'.format(request.event.pk))
s = SessionStore() s = SessionStore()
-7
View File
@@ -973,13 +973,6 @@ class MailSettingsForm(SettingsForm):
self.fields['mail_html_renderer'].choices = [ self.fields['mail_html_renderer'].choices = [
(r.identifier, r.verbose_name) for r in event.get_html_mail_renderers().values() (r.identifier, r.verbose_name) for r in event.get_html_mail_renderers().values()
] ]
keys = list(event.meta_data.keys())
for k, v in self.fields.items():
if k.startswith('mail_text_'):
v.help_text = str(v.help_text) + ', ' + ', '.join({
'{meta_' + p + '}' for p in keys
})
v.validators[0].limit_value += ['{meta_' + p + '}' for p in keys]
def clean(self): def clean(self):
data = self.cleaned_data data = self.cleaned_data
-5
View File
@@ -212,7 +212,6 @@ class EventOrderFilterForm(OrderFilterForm):
('overpaid', _('Overpaid')), ('overpaid', _('Overpaid')),
('underpaid', _('Underpaid')), ('underpaid', _('Underpaid')),
('pendingpaid', _('Pending (but fully paid)')), ('pendingpaid', _('Pending (but fully paid)')),
('testmode', _('Test mode')),
), ),
required=False, required=False,
) )
@@ -299,10 +298,6 @@ class EventOrderFilterForm(OrderFilterForm):
status=Order.STATUS_PENDING, status=Order.STATUS_PENDING,
require_approval=True require_approval=True
) )
elif fdata.get('status') == 'testmode':
qs = qs.filter(
testmode=True
)
elif fdata.get('status') == 'cp': elif fdata.get('status') == 'cp':
s = OrderPosition.objects.filter( s = OrderPosition.objects.filter(
order=OuterRef('pk') order=OuterRef('pk')
-9
View File
@@ -160,7 +160,6 @@ class ItemCreateForm(I18nModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.event = kwargs['event'] self.event = kwargs['event']
self.user = kwargs.pop('user')
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.fields['category'].queryset = self.instance.event.categories.all() self.fields['category'].queryset = self.instance.event.categories.all()
@@ -240,9 +239,6 @@ class ItemCreateForm(I18nModelForm):
if self.cleaned_data.get('quota_option') == self.EXISTING and self.cleaned_data.get('quota_add_existing') is not None: if self.cleaned_data.get('quota_option') == self.EXISTING and self.cleaned_data.get('quota_add_existing') is not None:
quota = self.cleaned_data.get('quota_add_existing') quota = self.cleaned_data.get('quota_add_existing')
quota.items.add(self.instance) quota.items.add(self.instance)
quota.log_action('pretix.event.quota.changed', user=self.user, data={
'item_added': self.instance.pk
})
elif self.cleaned_data.get('quota_option') == self.NEW: elif self.cleaned_data.get('quota_option') == self.NEW:
quota_name = self.cleaned_data.get('quota_add_new_name') quota_name = self.cleaned_data.get('quota_add_new_name')
quota_size = self.cleaned_data.get('quota_add_new_size') quota_size = self.cleaned_data.get('quota_add_new_size')
@@ -251,11 +247,6 @@ class ItemCreateForm(I18nModelForm):
event=self.event, name=quota_name, size=quota_size event=self.event, name=quota_name, size=quota_size
) )
quota.items.add(self.instance) quota.items.add(self.instance)
quota.log_action('pretix.event.quota.added', user=self.user, data={
'name': quota_name,
'size': quota_size,
'items': [self.instance.pk]
})
if self.cleaned_data.get('has_variations'): if self.cleaned_data.get('has_variations'):
if self.cleaned_data.get('copy_from') and self.cleaned_data.get('copy_from').has_variations: if self.cleaned_data.get('copy_from') and self.cleaned_data.get('copy_from').has_variations:
-3
View File
@@ -172,7 +172,6 @@ def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
'pretix.event.order.paid': _('The order has been marked as paid.'), 'pretix.event.order.paid': _('The order has been marked as paid.'),
'pretix.event.order.refunded': _('The order has been refunded.'), 'pretix.event.order.refunded': _('The order has been refunded.'),
'pretix.event.order.canceled': _('The order has been canceled.'), 'pretix.event.order.canceled': _('The order has been canceled.'),
'pretix.event.order.deleted': _('The test mode order {code} has been deleted.'),
'pretix.event.order.placed': _('The order has been created.'), 'pretix.event.order.placed': _('The order has been created.'),
'pretix.event.order.placed.require_approval': _('The order requires approval before it can continue to be processed.'), 'pretix.event.order.placed.require_approval': _('The order requires approval before it can continue to be processed.'),
'pretix.event.order.approved': _('The order has been approved.'), 'pretix.event.order.approved': _('The order has been approved.'),
@@ -269,8 +268,6 @@ def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
'pretix.event.plugins.disabled': _('A plugin has been disabled.'), 'pretix.event.plugins.disabled': _('A plugin has been disabled.'),
'pretix.event.live.activated': _('The shop has been taken live.'), 'pretix.event.live.activated': _('The shop has been taken live.'),
'pretix.event.live.deactivated': _('The shop has been taken offline.'), 'pretix.event.live.deactivated': _('The shop has been taken offline.'),
'pretix.event.testmode.activated': _('The shop has been taken into test mode.'),
'pretix.event.testmode.deactivated': _('The test mode has been disabled.'),
'pretix.event.added': _('The event has been created.'), 'pretix.event.added': _('The event has been created.'),
'pretix.event.changed': _('The event settings have been changed.'), 'pretix.event.changed': _('The event settings have been changed.'),
'pretix.event.question.option.added': _('An answer option has been added to the question.'), 'pretix.event.question.option.added': _('An answer option has been added to the question.'),
@@ -235,14 +235,6 @@
</ul> </ul>
</div> </div>
<ul class="nav" id="side-menu"> <ul class="nav" id="side-menu">
{% if request.event and request.event.testmode %}
<li class="testmode">
<a href="{% url "control:event.live" event=request.event.slug organizer=request.organizer.slug %}">
<i class="fa fa-warning fa-fw"></i>
{% trans "TEST MODE" %}
</a>
</li>
{% endif %}
{% block nav %} {% block nav %}
{% for nav in nav_items %} {% for nav in nav_items %}
<li> <li>
@@ -325,20 +317,6 @@
</div> </div>
{% endfor %} {% endfor %}
{% endif %} {% endif %}
{% if complain_testmode_orders %}
<div class="alert alert-warning">
{% blocktrans trimmed %}
Your event contains <strong>test mode orders</strong> even though <strong>test mode has been disabled</strong>.
You should delete those orders to make sure they do not show up in your reports and statistics and block people from
actually buying tickets.
{% endblocktrans %}
<strong>
<a href="{% url "control:event.orders" event=request.event.slug organizer=request.organizer.slug %}?status=testmode">
{% trans "Show all test mode orders" %}
</a>
</strong>
</div>
{% endif %}
{% if warning_update_check_active %} {% if warning_update_check_active %}
<div class="alert alert-info"> <div class="alert alert-info">
<a href="{% url "control:global.update" %}"> <a href="{% url "control:global.update" %}">
@@ -89,9 +89,6 @@
{% if e.order.status == "n" %} {% if e.order.status == "n" %}
<span class="label label-warning">{% trans "unpaid" %}</span> <span class="label label-warning">{% trans "unpaid" %}</span>
{% endif %} {% endif %}
{% if e.order.testmode %}
<span class="label label-warning">{% trans "TEST MODE" %}</span>
{% endif %}
</td> </td>
<td>{{ e.item }}{% if e.variation %} {{ e.variation }}{% endif %}</td> <td>{{ e.item }}{% if e.variation %} {{ e.variation }}{% endif %}</td>
<td>{{ e.order.email }}</td> <td>{{ e.order.email }}</td>
@@ -3,113 +3,49 @@
{% load bootstrap3 %} {% load bootstrap3 %}
{% block content %} {% block content %}
<h1>{% trans "Shop status" %}</h1> <h1>{% trans "Shop status" %}</h1>
<div class="panel panel-default"> {% if request.event.live %}
<div class="panel-heading"> <p>
{% trans "Shop visibility" %} {% trans "Your shop is currently live. If you take it down, it will only be visible to you and your team." %}
</div> </p>
<div class="panel-body"> <form action="" method="post">
{% if request.event.live %} {% csrf_token %}
<p> <input type="hidden" name="live" value="false">
{% trans "Your shop is currently live. If you take it down, it will only be visible to you and your team." %}
</p>
<form action="" method="post" class="text-right">
{% csrf_token %}
<input type="hidden" name="live" value="false">
<button type="submit" class="btn btn-lg btn-danger btn-save">
{% trans "Go offline" %}
</button>
</form>
{% else %}
{% if issues|length > 0 %}
<p>
{% trans "Your ticket shop is currently not live. It is thus only visible to you and your team, not to any visitors." %}
</p>
<div class="alert alert-warning">
<p>
{% trans "To publish your ticket shop, you first need to resolve the following issues:" %}
</p>
<ul>
{% for issue in issues %}
<li>{{ issue|safe }}</li>
{% endfor %}
</ul>
</div>
<div class="test-right">
<button type="submit" class="btn btn-primary btn-lg btn-save" disabled>
{% trans "Go live" %}
</button>
</div>
{% else %}
<p>
{% trans "Your ticket shop is currently not live. It is thus only visible to you and your team, not to any visitors." %}
</p>
<p>
{% trans "If you want to, you can publish your ticket shop now." %}
</p>
<form action="" method="post" class="text-right">
{% csrf_token %}
<input type="hidden" name="live" value="true">
<button type="submit" class="btn btn-primary btn-lg btn-save">
{% trans "Go live" %}
</button>
</form>
{% endif %}
{% endif %}
<div class="clear"></div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
{% trans "Test mode" %}
</div>
<div class="panel-body">
{% if request.event.testmode %}
<form action="" method="post">
{% csrf_token %}
<input type="hidden" name="testmode" value="false">
<p>
{% trans "Your shop is currently in test mode. All orders are not persistant and can be deleted at any point." %}
</p>
<div class="form-inline">
<label class="checkbox">
<input type="checkbox" name="delete" value="yes" />
{% trans "Permanently delete all orders created in test mode" %}
</label>
</div>
<div class="text-right">
<button type="submit" class="btn btn-lg btn-primary btn-save">
{% trans "Disable test mode" %}
</button>
</div>
</form>
{% else %}
<p>
{% trans "Your shop is currently in production mode." %}
</p>
<p>
{% trans "If you want to do some test orders, you can enable test mode for your shop. As long as the shop is in test mode, all orders that are created are marked as test orders and can be deleted again." %}
<strong>
{% trans "Please note that test orders still count into your quotas, actually use vouchers and might perform actual payments. The only difference is that you can delete test orders. Use at your own risk!" %}
</strong>
</p>
<p>
{% trans "Also, test mode only covers the main web shop. Orders created through other sales channels such as the box office or resellers module are still created as production orders." %}
</p>
{% if actual_orders %}
<div class="alert alert-danger">
{% trans "It looks like you already have some real orders in your shop. We do not recommend enabling test mode if your customers already know your shop, as it will confuse them." %}
</div>
{% endif %}
<form action="" method="post" class="text-right">
{% csrf_token %}
<input type="hidden" name="testmode" value="true">
<button type="submit" class="btn btn-danger btn-lg btn-save">
{% trans "Enable test mode" %}
</button>
</form>
{% endif %} <div class="form-group submit-group">
<div class="clear"></div> <button type="submit" class="btn btn-primary btn-save">
</div> {% trans "Go offline" %}
</div> </button>
</div>
</form>
{% else %}
<p>
{% trans "Your ticket shop is currently not live. It is thus only visible to you and your team, not to any visitors." %}
</p>
{% if issues|length > 0 %}
<div class="alert alert-warning">
<p>
{% trans "To publish your ticket shop, you first need to resolve the following issues:" %}
</p>
<ul>
{% for issue in issues %}
<li>{{ issue|safe }}</li>
{% endfor %}
</ul>
</div>
{% else %}
<p>
{% trans "If you want to, you can publish your ticket shop now." %}
</p>
<form action="" method="post">
{% csrf_token %}
<input type="hidden" name="live" value="true">
<div class="form-group submit-group">
<button type="submit" class="btn btn-primary btn-save">
{% trans "Go live" %}
</button>
</div>
</form>
{% endif %}
{% endif %}
{% endblock %} {% endblock %}
@@ -90,11 +90,6 @@
<span class="fa fa-eraser"></span> <span class="fa fa-eraser"></span>
{% trans "Delete personal data" %} {% trans "Delete personal data" %}
</a> </a>
<a href="{% url "control:events.add" %}?clone={{ request.event.pk }}"
class="btn btn-default btn-lg">
<span class="fa fa-copy"></span>
{% trans "Clone event" %}
</a>
</div> </div>
</div> </div>
</form> </form>
@@ -10,7 +10,6 @@
<form action="" method="post" class="form-horizontal"> <form action="" method="post" class="form-horizontal">
{% csrf_token %} {% csrf_token %}
{{ wizard.management_form }} {{ wizard.management_form }}
{{ wizard.prefix_form }}
{% bootstrap_form_errors form %} {% bootstrap_form_errors form %}
{% block form %} {% block form %}
{% endblock %} {% endblock %}
@@ -72,13 +72,11 @@
<a href="?{% url_replace request 'ordering' '-sum_tickets_paid' %}"><i class="fa fa-caret-down"></i></a> <a href="?{% url_replace request 'ordering' '-sum_tickets_paid' %}"><i class="fa fa-caret-down"></i></a>
<a href="?{% url_replace request 'ordering' 'sum_tickets_paid' %}"><i class="fa fa-caret-up"></i></a> <a href="?{% url_replace request 'ordering' 'sum_tickets_paid' %}"><i class="fa fa-caret-up"></i></a>
</th> </th>
<th> <th class="text-right">
{% trans "Status" %} {% trans "Status" %}
<a href="?{% url_replace request 'ordering' '-live' %}"><i class="fa fa-caret-down"></i></a> <a href="?{% url_replace request 'ordering' '-live' %}"><i class="fa fa-caret-down"></i></a>
<a href="?{% url_replace request 'ordering' 'live' %}"><i class="fa fa-caret-up"></i></a> <a href="?{% url_replace request 'ordering' 'live' %}"><i class="fa fa-caret-up"></i></a>
</th> </th>
<th class="text-right">
</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -119,7 +117,7 @@
</a> </a>
{% endif %} {% endif %}
</td> </td>
<td> <td class="text-right">
{% if not e.live %} {% if not e.live %}
<span class="label label-danger">{% trans "Shop disabled" %}</span> <span class="label label-danger">{% trans "Shop disabled" %}</span>
{% elif e.presale_has_ended %} {% elif e.presale_has_ended %}
@@ -130,17 +128,6 @@
<span class="label label-success">{% trans "On sale" %}</span> <span class="label label-success">{% trans "On sale" %}</span>
{% endif %} {% endif %}
</td> </td>
<td class="text-right">
<a href="{% url "control:event.index" organizer=e.organizer.slug event=e.slug %}"
class="btn btn-sm btn-default" title="{% trans "Open event dashboard" %}"
data-toggle="tooltip">
<span class="fa fa-eye"></span>
</a>
<a href="{% url "control:events.add" %}?clone={{ e.pk }}" class="btn btn-sm btn-default"
title="{% trans "Clone event" %}" data-toggle="tooltip">
<span class="fa fa-copy"></span>
</a>
</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
@@ -1,31 +0,0 @@
{% extends "pretixcontrol/event/base.html" %}
{% load i18n %}
{% block title %}
{% trans "Delete order" %}
{% endblock %}
{% block content %}
<h1>
{% trans "Delete order" %}
</h1>
<p>{% blocktrans trimmed %}
Do you really want to delete this order? <strong>You really cannot revert this action and we can't either.</strong>
{% endblocktrans %}</p>
<form method="post" href="">
{% csrf_token %}
<div class="row checkout-button-row">
<div class="col-md-4">
<a class="btn btn-block btn-default btn-lg"
href="{% url "control:event.order" event=request.event.slug organizer=request.event.organizer.slug code=order.code %}">
{% trans "No, take me back" %}
</a>
</div>
<div class="col-md-4 col-md-offset-4">
<button class="btn btn-block btn-danger btn-lg" type="submit">
{% trans "Yes, delete order" %}
</button>
</div>
<div class="clearfix"></div>
</div>
</form>
{% endblock %}
@@ -16,9 +16,6 @@
{% blocktrans trimmed with code=order.code %} {% blocktrans trimmed with code=order.code %}
Order details: {{ code }} Order details: {{ code }}
{% endblocktrans %} {% endblocktrans %}
{% if order.testmode %}
<span class="label label-warning">{% trans "TEST MODE" %}</span>
{% endif %}
{% include "pretixcontrol/orders/fragment_order_status.html" with order=order class="pull-right" %} {% include "pretixcontrol/orders/fragment_order_status.html" with order=order class="pull-right" %}
</h1> </h1>
{% if 'can_change_orders' in request.eventpermset %} {% if 'can_change_orders' in request.eventpermset %}
@@ -27,13 +24,6 @@
{% csrf_token %} {% csrf_token %}
<div class="btn-toolbar" role="toolbar"> <div class="btn-toolbar" role="toolbar">
<div class="btn-group" role="group"> <div class="btn-group" role="group">
{% if order.testmode %}
<a href="{% url "control:event.order.delete" event=request.event.slug organizer=request.event.organizer.slug code=order.code %}"
class="btn btn-danger">
<span class="fa fa-trash"></span>
{% trans "Delete" %}
</a>
{% endif %}
{% if order.require_approval and order.status == 'n' %} {% if order.require_approval and order.status == 'n' %}
<a href="{% url "control:event.order.approve" event=request.event.slug organizer=request.event.organizer.slug code=order.code %}" <a href="{% url "control:event.order.approve" event=request.event.slug organizer=request.event.organizer.slug code=order.code %}"
class="btn btn-primary"> class="btn btn-primary">
@@ -280,10 +270,6 @@
</form> </form>
{% endfor %} {% endfor %}
{% endif %} {% endif %}
<button type="submit" data-toggle="qrcode" data-qrcode="{{ line.secret }}"
class="btn btn-xs btn-default">
<span class="fa fa-qrcode"></span> {% trans "Show ticket code" %}
</button>
{% eventsignal event "pretix.control.signals.order_position_buttons" order=order position=line request=request %} {% eventsignal event "pretix.control.signals.order_position_buttons" order=order position=line request=request %}
</div> </div>
{% endif %} {% endif %}
@@ -114,11 +114,9 @@
<strong> <strong>
<a <a
href="{% url "control:event.order" event=request.event.slug organizer=request.event.organizer.slug code=o.code %}"> href="{% url "control:event.order" event=request.event.slug organizer=request.event.organizer.slug code=o.code %}">
{{ o.code }}</a> {{ o.code }}
</a>
</strong> </strong>
{% if o.testmode %}
<span class="label label-warning">{% trans "TEST MODE" %}</span>
{% endif %}
</td> </td>
<td> <td>
{{ o.email|default_if_none:"" }} {{ o.email|default_if_none:"" }}
@@ -55,9 +55,6 @@
<a href="{% url "control:event.order" event=request.event.slug organizer=request.event.organizer.slug code=r.order.code %}"> <a href="{% url "control:event.order" event=request.event.slug organizer=request.event.organizer.slug code=r.order.code %}">
{{ r.order.code }}</a>-R-{{ r.local_id }} {{ r.order.code }}</a>-R-{{ r.local_id }}
</strong> </strong>
{% if r.order.testmode %}
<span class="label label-warning">{% trans "TEST MODE" %}</span>
{% endif %}
</td> </td>
<td> <td>
{{ r.payment_provider.verbose_name }} {{ r.payment_provider.verbose_name }}
@@ -15,7 +15,6 @@
<tr> <tr>
<th>{% trans "Event name" %}</th> <th>{% trans "Event name" %}</th>
<th>{% trans "Start date" %}</th> <th>{% trans "Start date" %}</th>
<th></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -26,28 +25,13 @@
href="{% url "control:event.index" organizer=e.organizer.slug event=e.slug %}">{{ e.name }}</a></strong> href="{% url "control:event.index" organizer=e.organizer.slug event=e.slug %}">{{ e.name }}</a></strong>
</td> </td>
<td>{{ e.get_date_from_display }}</td> <td>{{ e.get_date_from_display }}</td>
<td class="text-right">
<a href="{% url "control:event.index" organizer=e.organizer.slug event=e.slug %}"
class="btn btn-sm btn-default" title="{% trans "Open event dashboard" %}"
data-toggle="tooltip">
<span class="fa fa-eye"></span>
</a>
{% if "can_create_events" in request.orgapermset %}
<a href="{% url "control:events.add" %}?clone={{ e.pk }}" class="btn btn-sm btn-default"
title="{% trans "Clone event" %}" data-toggle="tooltip">
<span class="fa fa-copy"></span>
</a>
{% endif %}
</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
{% endif %} {% endif %}
{% if "can_create_events" in request.orgapermset %} <a href="{% url "control:events.add" %}" class="btn btn-default">
<a href="{% url "control:events.add" %}" class="btn btn-default"> <span class="fa fa-plus"></span>
<span class="fa fa-plus"></span> {% trans "Create a new event" %}
{% trans "Create a new event" %} </a>
</a>
{% endif %}
{% endblock %} {% endblock %}
@@ -59,11 +59,9 @@
<td> <td>
<strong> <strong>
<a href="{% url "control:event.order" event=o.event.slug organizer=o.event.organizer.slug code=o.code %}"> <a href="{% url "control:event.order" event=o.event.slug organizer=o.event.organizer.slug code=o.code %}">
{{ o.event.slug|upper }}-{{ o.code }}</a> {{ o.event.slug|upper }}-{{ o.code }}
</a>
</strong> </strong>
{% if o.testmode %}
<span class="label label-warning">{% trans "TEST MODE" %}</span>
{% endif %}
</td> </td>
<td>{{ o.event.name }}</td> <td>{{ o.event.name }}</td>
<td> <td>
-2
View File
@@ -222,8 +222,6 @@ urlpatterns = [
name='event.order.approve'), name='event.order.approve'),
url(r'^orders/(?P<code>[0-9A-Z]+)/deny$', orders.OrderDeny.as_view(), url(r'^orders/(?P<code>[0-9A-Z]+)/deny$', orders.OrderDeny.as_view(),
name='event.order.deny'), name='event.order.deny'),
url(r'^orders/(?P<code>[0-9A-Z]+)/delete$', orders.OrderDelete.as_view(),
name='event.order.delete'),
url(r'^orders/(?P<code>[0-9A-Z]+)/info', orders.OrderModifyInformation.as_view(), url(r'^orders/(?P<code>[0-9A-Z]+)/info', orders.OrderModifyInformation.as_view(),
name='event.order.info'), name='event.order.info'),
url(r'^orders/(?P<code>[0-9A-Z]+)/sendmail$', orders.OrderSendMail.as_view(), url(r'^orders/(?P<code>[0-9A-Z]+)/sendmail$', orders.OrderSendMail.as_view(),
+2 -14
View File
@@ -183,20 +183,8 @@ def shop_state_widget(sender, **kwargs):
'priority': 1000, 'priority': 1000,
'content': '<div class="shopstate">{t1}<br><span class="{cls}"><span class="fa {icon}"></span> {state}</span>{t2}</div>'.format( 'content': '<div class="shopstate">{t1}<br><span class="{cls}"><span class="fa {icon}"></span> {state}</span>{t2}</div>'.format(
t1=_('Your ticket shop is'), t2=_('Click here to change'), t1=_('Your ticket shop is'), t2=_('Click here to change'),
state=_('live') if sender.live and not sender.testmode else ( state=_('live') if sender.live else _('not yet public'),
_('live and in test mode') if sender.live else ( icon='fa-check-circle' if sender.live else 'fa-times-circle',
_('not yet public') if not sender.testmode else (
_('in private test mode')
)
)
),
icon='fa-check-circle' if sender.live and not sender.testmode else (
'fa-warning' if sender.live else (
'fa-times-circle' if not sender.testmode else (
'fa-times-circle'
)
)
),
cls='live' if sender.live else 'off' cls='live' if sender.live else 'off'
), ),
'url': reverse('control:event.live', kwargs={ 'url': reverse('control:event.live', kwargs={
+14 -58
View File
@@ -573,13 +573,9 @@ class MailSettingsPreview(EventPermissionRequiredMixin, View):
locales[str(idx)] = val[0] locales[str(idx)] = val[0]
return locales return locales
@cached_property
def meta_properties(self):
return [p.name for p in self.request.organizer.meta_properties.all()]
@cached_property @cached_property
def items(self): def items(self):
kv = { return {
'mail_text_order_placed': ['total', 'currency', 'date', 'invoice_company', 'total_with_currency', 'mail_text_order_placed': ['total', 'currency', 'date', 'invoice_company', 'total_with_currency',
'event', 'payment_info', 'url', 'invoice_name'], 'event', 'payment_info', 'url', 'invoice_name'],
'mail_text_order_paid': ['event', 'url', 'invoice_name', 'invoice_company', 'payment_info'], 'mail_text_order_paid': ['event', 'url', 'invoice_name', 'invoice_company', 'payment_info'],
@@ -600,10 +596,6 @@ class MailSettingsPreview(EventPermissionRequiredMixin, View):
'mail_text_order_denied': ['total', 'currency', 'date', 'invoice_company', 'mail_text_order_denied': ['total', 'currency', 'date', 'invoice_company',
'total_with_currency', 'event', 'url', 'invoice_name'], 'total_with_currency', 'event', 'url', 'invoice_name'],
} }
for v in kv.values():
for p in self.meta_properties:
v.append('meta_' + p)
return kv
@cached_property @cached_property
def base_data(self): def base_data(self):
@@ -615,7 +607,7 @@ class MailSettingsPreview(EventPermissionRequiredMixin, View):
orders = [' - {} - {}'.format(self.generate_order_fullname(self.request.event.slug, order['code']), orders = [' - {} - {}'.format(self.generate_order_fullname(self.request.event.slug, order['code']),
self.generate_order_url(order['code'], order['secret'])) self.generate_order_url(order['code'], order['secret']))
for order in user_orders] for order in user_orders]
d = { return {
'event': self.request.event.name, 'event': self.request.event.name,
'total': 42.23, 'total': 42.23,
'total_with_currency': LazyCurrencyNumber(42.23, self.request.event.currency), 'total_with_currency': LazyCurrencyNumber(42.23, self.request.event.currency),
@@ -628,11 +620,8 @@ class MailSettingsPreview(EventPermissionRequiredMixin, View):
'invoice_name': _('John Doe'), 'invoice_name': _('John Doe'),
'invoice_company': _('Sample Corporation'), 'invoice_company': _('Sample Corporation'),
'common': _('An individial text with a reason can be inserted here.'), 'common': _('An individial text with a reason can be inserted here.'),
'payment_info': _('Please transfer money to this bank account: 9999-9999-9999-9999'), 'payment_info': _('Please transfer money to this bank account: 9999-9999-9999-9999')
} }
for k, v in self.request.event.meta_data.items():
d['meta_' + k] = v
return d
def generate_order_url(self, code, secret): def generate_order_url(self, code, secret):
return build_absolute_uri('presale:event.order', kwargs={ return build_absolute_uri('presale:event.order', kwargs={
@@ -690,8 +679,7 @@ class MailSettingsRendererPreview(MailSettingsPreview):
expires=now(), code="PREVIEW", total=119) expires=now(), code="PREVIEW", total=119)
item = request.event.items.create(name=ugettext("Sample product"), default_price=42.23, item = request.event.items.create(name=ugettext("Sample product"), default_price=42.23,
description=ugettext("Sample product description")) description=ugettext("Sample product description"))
order.positions.create(item=item, attendee_name_parts={'_legacy': ugettext("John Doe")}, order.positions.create(item=item, attendee_name_parts={'full_name': ugettext("John Doe")}, price=item.default_price)
price=item.default_price)
v = renderers[request.GET.get('renderer')].render( v = renderers[request.GET.get('renderer')].render(
v, v,
str(request.event.settings.mail_text_signature), str(request.event.settings.mail_text_signature),
@@ -854,55 +842,23 @@ class EventLive(EventPermissionRequiredMixin, TemplateView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs) ctx = super().get_context_data(**kwargs)
ctx['issues'] = self.request.event.live_issues ctx['issues'] = self.request.event.live_issues
ctx['actual_orders'] = self.request.event.orders.filter(testmode=False).exists()
return ctx return ctx
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
if request.POST.get("live") == "true" and not self.request.event.live_issues: if request.POST.get("live") == "true" and not self.request.event.live_issues:
with transaction.atomic(): request.event.live = True
request.event.live = True request.event.save()
request.event.save() self.request.event.log_action(
self.request.event.log_action( 'pretix.event.live.activated', user=self.request.user, data={}
'pretix.event.live.activated', user=self.request.user, data={} )
)
messages.success(self.request, _('Your shop is live now!')) messages.success(self.request, _('Your shop is live now!'))
elif request.POST.get("live") == "false": elif request.POST.get("live") == "false":
with transaction.atomic(): request.event.live = False
request.event.live = False request.event.save()
request.event.save() self.request.event.log_action(
self.request.event.log_action( 'pretix.event.live.deactivated', user=self.request.user, data={}
'pretix.event.live.deactivated', user=self.request.user, data={} )
)
messages.success(self.request, _('We\'ve taken your shop down. You can re-enable it whenever you want!')) messages.success(self.request, _('We\'ve taken your shop down. You can re-enable it whenever you want!'))
elif request.POST.get("testmode") == "true":
with transaction.atomic():
request.event.testmode = True
request.event.save()
self.request.event.log_action(
'pretix.event.testmode.activated', user=self.request.user, data={}
)
messages.success(self.request, _('Your shop is now in test mode!'))
elif request.POST.get("testmode") == "false":
with transaction.atomic():
request.event.testmode = False
request.event.save()
self.request.event.log_action(
'pretix.event.testmode.deactivated', user=self.request.user, data={
'delete': (request.POST.get("delete") == "yes")
}
)
request.event.cache.delete('complain_testmode_orders')
if request.POST.get("delete") == "yes":
try:
with transaction.atomic():
for order in request.event.orders.filter(testmode=True):
order.gracefully_delete(user=self.request.user)
except ProtectedError:
messages.error(self.request, _('An order could not be deleted as some constraints (e.g. data '
'created by plug-ins) do not allow it.'))
else:
request.event.cache.set('complain_testmode_orders', False, 30)
messages.success(self.request, _('We\'ve disabled test mode for you. Let\'s sell some real tickets!'))
return redirect(self.get_success_url()) return redirect(self.get_success_url())
def get_success_url(self) -> str: def get_success_url(self) -> str:
+1 -1
View File
@@ -817,7 +817,7 @@ class ItemCreate(EventPermissionRequiredMixin, CreateView):
""" """
newinst = Item(event=self.request.event) newinst = Item(event=self.request.event)
kwargs = super().get_form_kwargs() kwargs = super().get_form_kwargs()
kwargs.update({'instance': newinst, 'user': self.request.user}) kwargs.update({'instance': newinst})
return kwargs return kwargs
def form_invalid(self, form): def form_invalid(self, form):
+4 -60
View File
@@ -1,5 +1,4 @@
from django.conf import settings from django.conf import settings
from django.contrib import messages
from django.db import transaction from django.db import transaction
from django.db.models import ( from django.db.models import (
F, IntegerField, Max, Min, OuterRef, Prefetch, Subquery, Sum, F, IntegerField, Max, Min, OuterRef, Prefetch, Subquery, Sum,
@@ -13,9 +12,9 @@ from django.utils.functional import cached_property
from django.utils.translation import ugettext, ugettext_lazy as _ from django.utils.translation import ugettext, ugettext_lazy as _
from django.views import View from django.views import View
from django.views.generic import ListView from django.views.generic import ListView
from formtools.wizard.views import SessionWizardView
from i18nfield.strings import LazyI18nString from i18nfield.strings import LazyI18nString
from pretix.base.forms import SafeSessionWizardView
from pretix.base.i18n import language from pretix.base.i18n import language
from pretix.base.models import Event, Organizer, Quota, Team from pretix.base.models import Event, Organizer, Quota, Team
from pretix.control.forms.event import ( from pretix.control.forms.event import (
@@ -95,13 +94,10 @@ class EventList(PaginationMixin, ListView):
def condition_copy(wizard): def condition_copy(wizard):
return ( return EventWizardCopyForm.copy_from_queryset(wizard.request.user).exists()
not wizard.clone_from and
EventWizardCopyForm.copy_from_queryset(wizard.request.user).exists()
)
class EventWizard(SafeSessionWizardView): class EventWizard(SessionWizardView):
form_list = [ form_list = [
('foundation', EventWizardFoundationForm), ('foundation', EventWizardFoundationForm),
('basics', EventWizardBasicsForm), ('basics', EventWizardBasicsForm),
@@ -116,49 +112,6 @@ class EventWizard(SafeSessionWizardView):
'copy': condition_copy 'copy': condition_copy
} }
def get_form_initial(self, step):
initial = super().get_form_initial(step)
if self.clone_from:
if step == 'foundation':
initial['organizer'] = self.clone_from.organizer
initial['locales'] = self.clone_from.settings.locales
initial['has_subevents'] = self.clone_from.has_subevents
elif step == 'basics':
initial['name'] = self.clone_from.name
initial['slug'] = self.clone_from.slug + '-2'
initial['currency'] = self.clone_from.currency
initial['date_from'] = self.clone_from.date_from
initial['date_to'] = self.clone_from.date_to
initial['presale_start'] = self.clone_from.presale_start
initial['presale_end'] = self.clone_from.presale_end
initial['location'] = self.clone_from.location
initial['timezone'] = self.clone_from.settings.timezone
initial['locale'] = self.clone_from.settings.locale
if self.clone_from.settings.tax_rate_default:
initial['tax_rate'] = self.clone_from.settings.tax_rate_default.rate
return initial
def dispatch(self, request, *args, **kwargs):
self.clone_from = None
if 'clone' in self.request.GET:
try:
clone_from = Event.objects.select_related('organizer').get(pk=self.request.GET.get("clone"))
except Event.DoesNotExist:
allow = False
else:
allow = (
request.user.has_event_permission(clone_from.organizer, clone_from,
'can_change_event_settings', request)
and request.user.has_event_permission(clone_from.organizer, clone_from,
'can_change_items', request)
)
if not allow:
messages.error(self.request, _('You do not have permission to clone this event.'))
else:
self.clone_from = clone_from
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, form, **kwargs): def get_context_data(self, form, **kwargs):
ctx = super().get_context_data(form, **kwargs) ctx = super().get_context_data(form, **kwargs)
ctx['has_organizer'] = self.request.user.teams.filter(can_create_events=True).exists() ctx['has_organizer'] = self.request.user.teams.filter(can_create_events=True).exists()
@@ -180,13 +133,6 @@ class EventWizard(SafeSessionWizardView):
} }
if step != 'foundation': if step != 'foundation':
fdata = self.get_cleaned_data_for_step('foundation') fdata = self.get_cleaned_data_for_step('foundation')
if fdata is None:
fdata = {
'organizer': Organizer(slug='_nonexisting'),
'has_subevents': False,
'locales': ['en']
}
# The show must go on, we catch this error in render()
kwargs.update(fdata) kwargs.update(fdata)
return kwargs return kwargs
@@ -240,8 +186,6 @@ class EventWizard(SafeSessionWizardView):
if copy_data and copy_data['copy_from_event']: if copy_data and copy_data['copy_from_event']:
from_event = copy_data['copy_from_event'] from_event = copy_data['copy_from_event']
event.copy_data_from(from_event) event.copy_data_from(from_event)
elif self.clone_from:
event.copy_data_from(self.clone_from)
elif event.has_subevents: elif event.has_subevents:
event.checkin_lists.create( event.checkin_lists.create(
name=str(se), name=str(se),
@@ -265,7 +209,7 @@ class EventWizard(SafeSessionWizardView):
event.settings.set('locale', basics_data['locale']) event.settings.set('locale', basics_data['locale'])
event.settings.set('locales', foundation_data['locales']) event.settings.set('locales', foundation_data['locales'])
if (copy_data and copy_data['copy_from_event']) or self.clone_from or event.has_subevents: if (copy_data and copy_data['copy_from_event']) or event.has_subevents:
return redirect(reverse('control:event.settings', kwargs={ return redirect(reverse('control:event.settings', kwargs={
'organizer': event.organizer.slug, 'organizer': event.organizer.slug,
'event': event.slug, 'event': event.slug,
+1 -32
View File
@@ -11,9 +11,7 @@ from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.core.files import File from django.core.files import File
from django.db import transaction from django.db import transaction
from django.db.models import ( from django.db.models import Count, IntegerField, OuterRef, Subquery
Count, IntegerField, OuterRef, ProtectedError, Subquery,
)
from django.http import ( from django.http import (
FileResponse, Http404, HttpResponseNotAllowed, JsonResponse, FileResponse, Http404, HttpResponseNotAllowed, JsonResponse,
) )
@@ -400,35 +398,6 @@ class OrderApprove(OrderView):
}) })
class OrderDelete(OrderView):
permission = 'can_change_orders'
def post(self, *args, **kwargs):
if self.order.testmode:
try:
with transaction.atomic():
self.order.gracefully_delete(user=self.request.user)
messages.success(self.request, _('The order has been deleted.'))
return redirect(reverse('control:event.orders', kwargs={
'event': self.request.event.slug,
'organizer': self.request.organizer.slug,
}))
except ProtectedError:
messages.error(self.request, _('The order could not be deleted as some constraints (e.g. data created '
'by plug-ins) do not allow it.'))
return self.get(self.request, *self.args, **self.kwargs)
return redirect(self.get_order_url())
def get(self, *args, **kwargs):
if not self.order.testmode:
messages.error(self.request, _('Only orders created in test mode can be deleted.'))
return redirect(self.get_order_url())
return render(self.request, 'pretixcontrol/order/delete.html', {
'order': self.order,
})
class OrderDeny(OrderView): class OrderDeny(OrderView):
permission = 'can_change_orders' permission = 'can_change_orders'
+3 -41
View File
@@ -46,7 +46,7 @@ class OrderSearch(PaginationMixin, ListView):
return ctx return ctx
def get_queryset(self): def get_queryset(self):
qs = Order.objects.using(settings.DATABASE_REPLICA) qs = Order.objects.select_related('invoice_address').using(settings.DATABASE_REPLICA)
if not self.request.user.has_active_staff_session(self.request.session.session_key): if not self.request.user.has_active_staff_session(self.request.session.session_key):
qs = qs.filter( qs = qs.filter(
@@ -59,47 +59,9 @@ class OrderSearch(PaginationMixin, ListView):
if self.filter_form.is_valid(): if self.filter_form.is_valid():
qs = self.filter_form.filter_qs(qs) qs = self.filter_form.filter_qs(qs)
if self.filter_form.cleaned_data.get('query'):
"""
We need to work around a bug in PostgreSQL's (and likely MySQL's) query plan optimizer here.
The database lacks statistical data to predict how common our search filter is and therefore
assumes that it is cheaper to first ORDER *all* orders in the system (since we got an index on
datetime), then filter out with a full scan until OFFSET/LIMIT condition is fulfilled. If we
look for something rare (such as an email address used once within hundreds of thousands of
orders, this ends up to be pathologically slow.
For some search queries on pretix.eu, we see search times of >30s, just due to the ORDER BY and
LIMIT clause. Without them. the query runs in roughly 0.6s. This heuristical approach tries to
detect these cases and rewrite the query as a nested subquery that strongly suggests sorting
before filtering. However, since even that fails in some cases because PostgreSQL thinks it knows
better, we literally force it by evaluating the subquery explicitly. We only do this for n<=200,
to avoid memory leaks and problems with maximum parameter count on SQLite. In cases where the
search query yields lots of results, this will actually be slower since it requires two queries,
sorry.
Phew.
"""
page = self.kwargs.get(self.page_kwarg) or self.request.GET.get(self.page_kwarg) or 1
limit = self.get_paginate_by(None)
offset = (page - 1) * limit
resultids = list(qs.order_by().values_list('id', flat=True)[:201])
if len(resultids) <= 200 and len(resultids) <= offset + limit:
qs = Order.objects.using(settings.DATABASE_REPLICA).filter(
id__in=resultids
)
"""
We use prefetch_related here instead of select_related for a reason, even though select_related
would be the common choice for a foreign key and doesn't require an additional database query.
The problem is, that if our results contain the same event 25 times, select_related will create
25 Django objects which will all try to pull their ownsettings cache to show the event properly,
leading to lots of unnecessary queries. Due to the way prefetch_related works differently, it
will only create one shared Django object.
"""
return qs.only( return qs.only(
'id', 'invoice_address__name_cached', 'invoice_address__name_parts', 'code', 'event', 'email', 'id', 'invoice_address__name_cached', 'invoice_address__name_parts', 'code', 'event', 'email',
'datetime', 'total', 'status', 'require_approval', 'testmode' 'datetime', 'total', 'status', 'require_approval'
).prefetch_related( ).prefetch_related(
'event', 'event__organizer' 'event', 'event__organizer'
).select_related('invoice_address') )
+1 -1
View File
@@ -32,7 +32,7 @@ def get_sizes(size, imgsize):
(0, int((imgsize[1] * wfactor - imgsize[1] * hfactor) / 2), (0, int((imgsize[1] * wfactor - imgsize[1] * hfactor) / 2),
imgsize[0] * hfactor, int((imgsize[1] * wfactor + imgsize[1] * wfactor) / 2)) imgsize[0] * hfactor, int((imgsize[1] * wfactor + imgsize[1] * wfactor) / 2))
elif wfactor > hfactor: elif wfactor > hfactor:
return (int(size[0]), int(imgsize[1] * wfactor)), \ return (int(size[0]), int(imgsize[1] * hfactor)), \
(0, int((imgsize[1] * wfactor - size[1]) / 2), size[0], int((imgsize[1] * wfactor + size[1]) / 2)) (0, int((imgsize[1] * wfactor - size[1]) / 2), size[0], int((imgsize[1] * wfactor + size[1]) / 2))
else: else:
return (int(imgsize[0] * hfactor), int(size[1])), \ return (int(imgsize[0] * hfactor), int(size[1])), \
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-02-20 16:52+0000\n" "POT-Creation-Date: 2019-02-01 16:27+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Automatically generated\n" "Last-Translator: Automatically generated\n"
"Language-Team: none\n" "Language-Team: none\n"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-02-20 16:52+0000\n" "POT-Creation-Date: 2019-02-01 16:27+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Automatically generated\n" "Last-Translator: Automatically generated\n"
"Language-Team: none\n" "Language-Team: none\n"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -6,7 +6,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-02-20 16:52+0000\n" "POT-Creation-Date: 2019-02-01 16:27+0000\n"
"PO-Revision-Date: 2018-04-24 14:22+0000\n" "PO-Revision-Date: 2018-04-24 14:22+0000\n"
"Last-Translator: Pernille Thorsen <perth@aarhus.dk>\n" "Last-Translator: Pernille Thorsen <perth@aarhus.dk>\n"
"Language-Team: Danish <https://translate.pretix.eu/projects/pretix/pretix-js/" "Language-Team: Danish <https://translate.pretix.eu/projects/pretix/pretix-js/"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-02-20 16:52+0000\n" "POT-Creation-Date: 2019-02-01 16:27+0000\n"
"PO-Revision-Date: 2018-12-02 15:44+0000\n" "PO-Revision-Date: 2018-12-02 15:44+0000\n"
"Last-Translator: Alexander Schwartz <alexander.schwartz@gmx.net>\n" "Last-Translator: Alexander Schwartz <alexander.schwartz@gmx.net>\n"
"Language-Team: German <https://translate.pretix.eu/projects/pretix/pretix-js/" "Language-Team: German <https://translate.pretix.eu/projects/pretix/pretix-js/"
File diff suppressed because it is too large Load Diff
@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-02-20 16:52+0000\n" "POT-Creation-Date: 2019-02-01 16:27+0000\n"
"PO-Revision-Date: 2018-12-02 13:41+0000\n" "PO-Revision-Date: 2018-12-02 13:41+0000\n"
"Last-Translator: Alexander Schwartz <alexander.schwartz@gmx.net>\n" "Last-Translator: Alexander Schwartz <alexander.schwartz@gmx.net>\n"
"Language-Team: German (informal) <https://translate.pretix.eu/projects/" "Language-Team: German (informal) <https://translate.pretix.eu/projects/"
+1040 -1250
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-02-20 16:52+0000\n" "POT-Creation-Date: 2019-02-01 16:27+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-02-20 16:52+0000\n" "POT-Creation-Date: 2019-02-01 16:27+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Automatically generated\n" "Last-Translator: Automatically generated\n"
"Language-Team: none\n" "Language-Team: none\n"
File diff suppressed because it is too large Load Diff
+12 -7
View File
@@ -7,9 +7,9 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-02-20 16:52+0000\n" "POT-Creation-Date: 2019-02-01 16:27+0000\n"
"PO-Revision-Date: 2019-02-20 03:00+0000\n" "PO-Revision-Date: 2018-11-29 09:01+0000\n"
"Last-Translator: oocf <oswaldocerna@gmail.com>\n" "Last-Translator: arabestia <sergioadalbertor@gmail.com>\n"
"Language-Team: Spanish <https://translate.pretix.eu/projects/pretix/pretix-" "Language-Team: Spanish <https://translate.pretix.eu/projects/pretix/pretix-"
"js/es/>\n" "js/es/>\n"
"Language: es\n" "Language: es\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n" "Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 3.4\n" "X-Generator: Weblate 3.1.1\n"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56 #: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62 #: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
@@ -62,14 +62,19 @@ msgstr ""
#: pretix/static/pretixbase/js/asynctask.js:45 #: pretix/static/pretixbase/js/asynctask.js:45
#: pretix/static/pretixbase/js/asynctask.js:101 #: pretix/static/pretixbase/js/asynctask.js:101
#, fuzzy
#| msgid ""
#| "Your request has been queued on the server and will now be processed. If "
#| "this takes longer than two minutes, please contact us or go back in your "
#| "browser and try again."
msgid "" msgid ""
"Your request arrived on the server but we still wait for it to be processed. " "Your request arrived on the server but we still wait for it to be processed. "
"If this takes longer than two minutes, please contact us or go back in your " "If this takes longer than two minutes, please contact us or go back in your "
"browser and try again." "browser and try again."
msgstr "" msgstr ""
"Su solicitud llegó al servidor pero seguimos esperando a que sea procesada. " "Su solicitud ha sido enviada al servidor y se procesada en breve. Si el "
"Si toma más de dos minutos, por favor contáctenos o regrese a la página " "proceso tomara más de dos minutos, por favor, contáctenos o regrese a la "
"anterior en su navegador y pruebe de nuevo." "página anterior en su navegador y pruebe de nuevo."
#: pretix/static/pretixbase/js/asynctask.js:66 #: pretix/static/pretixbase/js/asynctask.js:66
#: pretix/static/pretixbase/js/asynctask.js:124 #: pretix/static/pretixbase/js/asynctask.js:124
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -6,7 +6,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: French\n" "Project-Id-Version: French\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-02-20 16:52+0000\n" "POT-Creation-Date: 2019-02-01 16:27+0000\n"
"PO-Revision-Date: 2018-10-28 10:23+0000\n" "PO-Revision-Date: 2018-10-28 10:23+0000\n"
"Last-Translator: Arnaud Vergnet <keplyx@gmail.com>\n" "Last-Translator: Arnaud Vergnet <keplyx@gmail.com>\n"
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix-js/" "Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix-js/"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-02-20 16:52+0000\n" "POT-Creation-Date: 2019-02-01 16:27+0000\n"
"PO-Revision-Date: 2019-01-02 08:20+0000\n" "PO-Revision-Date: 2019-01-02 08:20+0000\n"
"Last-Translator: amefad <fame@libero.it>\n" "Last-Translator: amefad <fame@libero.it>\n"
"Language-Team: Italian <https://translate.pretix.eu/projects/pretix/pretix-" "Language-Team: Italian <https://translate.pretix.eu/projects/pretix/pretix-"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -6,7 +6,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: 1\n" "Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-02-20 16:52+0000\n" "POT-Creation-Date: 2019-02-01 16:27+0000\n"
"PO-Revision-Date: 2019-01-08 12:30+0000\n" "PO-Revision-Date: 2019-01-08 12:30+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-js/" "Language-Team: Dutch <https://translate.pretix.eu/projects/pretix/pretix-js/"
File diff suppressed because it is too large Load Diff
@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-02-20 16:52+0000\n" "POT-Creation-Date: 2019-02-01 16:27+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Automatically generated\n" "Last-Translator: Automatically generated\n"
"Language-Team: none\n" "Language-Team: none\n"
File diff suppressed because it is too large Load Diff
@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-02-20 16:52+0000\n" "POT-Creation-Date: 2019-02-01 16:27+0000\n"
"PO-Revision-Date: 2019-01-08 21:00+0000\n" "PO-Revision-Date: 2019-01-08 21: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 (informal) <https://translate.pretix.eu/projects/pretix/" "Language-Team: Dutch (informal) <https://translate.pretix.eu/projects/pretix/"
File diff suppressed because it is too large Load Diff
@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-02-20 16:52+0000\n" "POT-Creation-Date: 2019-02-01 16:27+0000\n"
"PO-Revision-Date: 2018-10-22 04:23+0000\n" "PO-Revision-Date: 2018-10-22 04:23+0000\n"
"Last-Translator: Samir C. Costa <samirfor@gmail.com>\n" "Last-Translator: Samir C. Costa <samirfor@gmail.com>\n"
"Language-Team: Portuguese (Brazil) <https://translate.pretix.eu/projects/" "Language-Team: Portuguese (Brazil) <https://translate.pretix.eu/projects/"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-02-20 16:52+0000\n" "POT-Creation-Date: 2019-02-01 16:27+0000\n"
"PO-Revision-Date: 2019-01-02 08:21+0000\n" "PO-Revision-Date: 2019-01-02 08:21+0000\n"
"Last-Translator: Alexey Zh <write2aracon@gmail.com>\n" "Last-Translator: Alexey Zh <write2aracon@gmail.com>\n"
"Language-Team: Russian <https://translate.pretix.eu/projects/pretix/pretix-" "Language-Team: Russian <https://translate.pretix.eu/projects/pretix/pretix-"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-02-20 16:52+0000\n" "POT-Creation-Date: 2019-02-01 16:27+0000\n"
"PO-Revision-Date: 2018-09-03 06:36+0000\n" "PO-Revision-Date: 2018-09-03 06:36+0000\n"
"Last-Translator: Yunus Fırat Pişkin <firat.piskin@idvlabs.com>\n" "Last-Translator: Yunus Fırat Pişkin <firat.piskin@idvlabs.com>\n"
"Language-Team: Turkish <https://translate.pretix.eu/projects/pretix/pretix-" "Language-Team: Turkish <https://translate.pretix.eu/projects/pretix/pretix-"
@@ -101,11 +101,6 @@ class BankTransfer(BasePaymentProvider):
def public_name(self): def public_name(self):
return str(self.settings.get('public_name', as_type=LazyI18nString) or self.verbose_name) return str(self.settings.get('public_name', as_type=LazyI18nString) or self.verbose_name)
@property
def test_mode_message(self):
return _('In test mode, you can just manually mark this order as paid in the backend after it has been '
'created.')
@property @property
def settings_form_fields(self): def settings_form_fields(self):
d = OrderedDict( d = OrderedDict(
@@ -5,6 +5,4 @@ register = template.Library()
@register.filter @register.filter
def ibanformat(value): def ibanformat(value):
if not value:
return ''
return ' '.join(value[i:i + 4] for i in range(0, len(value), 4)) return ' '.join(value[i:i + 4] for i in range(0, len(value), 4))
@@ -320,7 +320,6 @@ class CSVCheckinList(CheckInListMixin, ListExporter):
headers.append(_('Company')) headers.append(_('Company'))
headers.append(_('Voucher code')) headers.append(_('Voucher code'))
headers.append(_('Order date'))
yield headers yield headers
for op in qs: for op in qs:
@@ -373,7 +372,6 @@ class CSVCheckinList(CheckInListMixin, ListExporter):
row.append(ia.company) row.append(ia.company)
row.append(op.voucher.code if op.voucher else "") row.append(op.voucher.code if op.voucher else "")
row.append(op.order.datetime.astimezone(self.event.timezone).strftime('%Y-%m-%d'))
yield row yield row
def get_filename(self): def get_filename(self):
-12
View File
@@ -36,18 +36,6 @@ class Paypal(BasePaymentProvider):
super().__init__(event) super().__init__(event)
self.settings = SettingsSandbox('payment', 'paypal', event) self.settings = SettingsSandbox('payment', 'paypal', event)
@property
def test_mode_message(self):
if self.settings.connect_client_id and not self.settings.secret:
# in OAuth mode, sandbox mode needs to be set global
is_sandbox = self.settings.connect_endpoint == 'sandbox'
else:
is_sandbox = self.settings.get('endpoint') == 'sandbox'
if is_sandbox:
return _('The PayPal sandbox is being used, you can test without actually sending money but you will need a '
'PayPal sandbox user to log in.')
return None
@property @property
def settings_form_fields(self): def settings_form_fields(self):
if self.settings.connect_client_id and not self.settings.secret: if self.settings.connect_client_id and not self.settings.secret:
@@ -63,11 +63,6 @@
</p> </p>
<h2>{% trans "3. Start scanning tickets" %}</h2> <h2>{% trans "3. Start scanning tickets" %}</h2>
<script type="text/javascript" src="{% static "pretixplugins/pretixdroid/pretixdroid.js" %}"></script> <script type="text/javascript" src="{% static "pretixplugins/pretixdroid/pretixdroid.js" %}"></script>
{% if request.event.testmode %}
<div class="alert-info">
{% trans "Test mode orders will only be scanned if you scan online. If you scan in asynchronous mode, test mode orders won't be there." %}
</div>
{% endif %}
{% endif %} {% endif %}
{% endblock %} {% endblock %}
-1
View File
@@ -332,7 +332,6 @@ class ApiDownloadView(ApiView):
order__event=self.event, order__event=self.event,
order__status__in=[Order.STATUS_PAID] + ([Order.STATUS_PENDING] if self.config.list.include_pending else order__status__in=[Order.STATUS_PAID] + ([Order.STATUS_PENDING] if self.config.list.include_pending else
[]), []),
order__testmode=False,
subevent=self.config.list.subevent subevent=self.config.list.subevent
).annotate( ).annotate(
last_checked_in=Subquery(cqs) last_checked_in=Subquery(cqs)
+1 -2
View File
@@ -118,8 +118,7 @@ class ReportlabExportMixin:
canvas.setFont('OpenSans', 10) canvas.setFont('OpenSans', 10)
canvas.drawString(15 * mm, self.pagesize[1] - 15 * mm, canvas.drawString(15 * mm, self.pagesize[1] - 15 * mm,
"%s %s %s" % (self.event.organizer.name, self.event.name, "%s %s" % (self.event.organizer.name, self.event.name))
self.event.get_date_range_display()))
canvas.drawRightString(self.pagesize[0] - 15 * mm, self.pagesize[1] - 15 * mm, canvas.drawRightString(self.pagesize[0] - 15 * mm, self.pagesize[1] - 15 * mm,
settings.PRETIX_INSTANCE_NAME) settings.PRETIX_INSTANCE_NAME)
canvas.setStrokeColorRGB(0, 0, 0) canvas.setStrokeColorRGB(0, 0, 0)
+1 -19
View File
@@ -14,7 +14,6 @@ from django.template.loader import get_template
from django.urls import reverse from django.urls import reverse
from django.utils.crypto import get_random_string from django.utils.crypto import get_random_string
from django.utils.http import urlquote from django.utils.http import urlquote
from django.utils.safestring import mark_safe
from django.utils.timezone import now from django.utils.timezone import now
from django.utils.translation import pgettext, ugettext, ugettext_lazy as _ from django.utils.translation import pgettext, ugettext, ugettext_lazy as _
from django_countries import countries from django_countries import countries
@@ -112,8 +111,6 @@ class StripeSettingsHolder(BasePaymentProvider):
('live', pgettext('stripe', 'Live')), ('live', pgettext('stripe', 'Live')),
('test', pgettext('stripe', 'Testing')), ('test', pgettext('stripe', 'Testing')),
), ),
help_text=_('If your event is in test mode, we will always use Stripe\'s test API, '
'regardless of this setting.')
)), )),
] ]
else: else:
@@ -233,21 +230,6 @@ class StripeMethod(BasePaymentProvider):
super().__init__(event) super().__init__(event)
self.settings = SettingsSandbox('payment', 'stripe', event) self.settings = SettingsSandbox('payment', 'stripe', event)
@property
def test_mode_message(self):
if self.settings.connect_client_id and not self.settings.secret_key:
is_testmode = True
else:
is_testmode = '_test_' in self.settings.secret_key
if is_testmode:
return mark_safe(
_('The Stripe plugin is operating in test mode. You can use one of <a {args}>many test '
'cards</a> to perform a transaction. No money will actually be transferred.').format(
args='href="https://stripe.com/docs/testing#cards" target="_blank"'
)
)
return None
@property @property
def settings_form_fields(self): def settings_form_fields(self):
return {} return {}
@@ -280,7 +262,7 @@ class StripeMethod(BasePaymentProvider):
@property @property
def api_kwargs(self): def api_kwargs(self):
if self.settings.connect_client_id and self.settings.connect_user_id: if self.settings.connect_client_id and self.settings.connect_user_id:
if self.settings.get('endpoint', 'live') == 'live' and not self.event.testmode: if self.settings.get('endpoint', 'live') == 'live':
kwargs = { kwargs = {
'api_key': self.settings.connect_secret_key, 'api_key': self.settings.connect_secret_key,
'stripe_account': self.settings.connect_user_id 'stripe_account': self.settings.connect_user_id
+1 -8
View File
@@ -34,14 +34,7 @@ def html_head_presale(sender, request=None, **kwargs):
url = resolve(request.path_info) url = resolve(request.path_info)
if provider.settings.get('_enabled', as_type=bool) and ("checkout" in url.url_name or "order.pay" in url.url_name): if provider.settings.get('_enabled', as_type=bool) and ("checkout" in url.url_name or "order.pay" in url.url_name):
template = get_template('pretixplugins/stripe/presale_head.html') template = get_template('pretixplugins/stripe/presale_head.html')
ctx = { ctx = {'event': sender, 'settings': provider.settings}
'event': sender,
'settings': provider.settings,
'testmode': (
(provider.settings.get('endpoint', 'live') == 'test' or sender.testmode)
and provider.settings.publishable_test_key
)
}
return template.render(ctx) return template.render(ctx)
else: else:
return "" return ""
@@ -8,7 +8,7 @@
{% compress css %} {% compress css %}
<link type="text/css" rel="stylesheet" href="{% static "pretixplugins/stripe/pretix-stripe.css" %}"> <link type="text/css" rel="stylesheet" href="{% static "pretixplugins/stripe/pretix-stripe.css" %}">
{% endcompress %} {% endcompress %}
{% if testmode %} {% if settings.endpoint == "test" and settings.publishable_test_key %}
<script type="text/plain" id="stripe_pubkey">{{ settings.publishable_test_key }}</script> <script type="text/plain" id="stripe_pubkey">{{ settings.publishable_test_key }}</script>
{% else %} {% else %}
<script type="text/plain" id="stripe_pubkey">{{ settings.publishable_key }}</script> <script type="text/plain" id="stripe_pubkey">{{ settings.publishable_key }}</script>

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