Compare commits

..

1 Commits

Author SHA1 Message Date
Raphael Michel
173cf751da Add search index model 2019-02-14 18:11:25 +01:00
151 changed files with 20108 additions and 41636 deletions

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.
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.
However, pretix can still make use of a database replica to keep some expensive queries with that can tolerate some latency from your

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.
live boolean If ``true``, the event ticket shop is publicly
available.
testmode boolean If ``true``, the ticket shop is in test mode.
currency string The currency this event is handled in.
date_from datetime The event's start date
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.
.. versionchanged:: 2.5
The ``testmode`` attribute has been added.
Endpoints
---------
@@ -84,7 +79,6 @@ Endpoints
"name": {"en": "Sample Conference"},
"slug": "sampleconf",
"live": false,
"testmode": false,
"currency": "EUR",
"date_from": "2017-12-27T10:00:00Z",
"date_to": null,
@@ -143,7 +137,6 @@ Endpoints
"name": {"en": "Sample Conference"},
"slug": "sampleconf",
"live": false,
"testmode": false,
"currency": "EUR",
"date_from": "2017-12-27T10:00:00Z",
"date_to": null,
@@ -190,7 +183,6 @@ Endpoints
"name": {"en": "Sample Conference"},
"slug": "sampleconf",
"live": false,
"testmode": false,
"currency": "EUR",
"date_from": "2017-12-27T10:00:00Z",
"date_to": null,
@@ -219,7 +211,6 @@ Endpoints
"name": {"en": "Sample Conference"},
"slug": "sampleconf",
"live": false,
"testmode": false,
"currency": "EUR",
"date_from": "2017-12-27T10:00:00Z",
"date_to": null,
@@ -268,7 +259,6 @@ Endpoints
"name": {"en": "Sample Conference"},
"slug": "sampleconf",
"live": false,
"testmode": false,
"currency": "EUR",
"date_from": "2017-12-27T10:00:00Z",
"date_to": null,
@@ -297,7 +287,6 @@ Endpoints
"name": {"en": "Sample Conference"},
"slug": "sampleconf",
"live": false,
"testmode": false,
"currency": "EUR",
"date_from": "2017-12-27T10:00:00Z",
"date_to": null,
@@ -358,7 +347,6 @@ Endpoints
"name": {"en": "Sample Conference"},
"slug": "sampleconf",
"live": false,
"testmode": false,
"currency": "EUR",
"date_from": "2017-12-27T10:00:00Z",
"date_to": null,

View File

@@ -26,8 +26,6 @@ status string Order status, o
* ``p`` paid
* ``e`` expired
* ``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
email string The customer email address
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
``…/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
@@ -278,7 +272,6 @@ List of all orders
{
"code": "ABC12",
"status": "p",
"testmode": false,
"secret": "k24fiuwvu8kxz3y1",
"email": "tester@example.org",
"locale": "en",
@@ -377,14 +370,11 @@ List of all orders
``status``. Default: ``datetime``
: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 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
``require_approval`` will be returned.
:query string email: Only return orders created with the given email address
: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
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.
:query datetime modified_since: Only return orders that have changed since the given date
:param organizer: The ``slug`` field of the organizer 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
@@ -419,7 +409,6 @@ Fetching individual orders
{
"code": "ABC12",
"status": "p",
"testmode": false,
"secret": "k24fiuwvu8kxz3y1",
"email": "tester@example.org",
"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
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
---------------
@@ -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
**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.
* ``testmode`` (optional) Defaults to ``false``
* ``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
creation.

View File

@@ -114,8 +114,6 @@ The provider class
.. autoattribute:: is_meta
.. autoattribute:: test_mode_message
Additional views
----------------

View File

@@ -82,12 +82,6 @@ Orders
^^^^^^
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
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

View File

@@ -4,6 +4,7 @@ Pending: Order is expecting payment\nOrder reduces quotas
Expired: Payment period is over\nOrder does not affect quotas
Paid: Order was successful\nOrder reduces 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 --> 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 --> Canceled
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
Paid -> Pending: manually on admin action
[*] --> Paid: customer\nplaces free order
@enduml

View File

@@ -108,43 +108,3 @@ Endpoints
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view it.
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/badgeitems/
Returns a list of all assignments of items to layouts
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/democon/badgeitems/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: text/javascript
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 1,
"layout": 2,
"item": 3,
}
]
}
:query page: The page number in case of a multi-page result set, default is 1
:param organizer: The ``slug`` field of a valid organizer
:param event: The ``slug`` field of a valid event
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to view it.

View File

@@ -114,44 +114,3 @@ Endpoints
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view it.
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/ticketlayoutitems/
Returns a list of all assignments of items to layouts
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/events/democon/ticketlayoutitems/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: text/javascript
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 1,
"layout": 2,
"item": 3,
"sales_channel": web
}
]
}
:query page: The page number in case of a multi-page result set, default is 1
:param organizer: The ``slug`` field of a valid organizer
:param event: The ``slug`` field of a valid event
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to view it.

View File

@@ -111,7 +111,6 @@ submodule
subpath
Symfony
systemd
testmode
testutils
timestamp
tuples

View File

@@ -4,10 +4,22 @@ FAQ and Troubleshooting
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
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
still count toward your quotas and are included in your reports.
There are multiple ways to do this.
First, you could just create some orders in your real shop and cancel/refund them later. If you don't want to process
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?
-------------------------

View File

@@ -1 +1 @@
__version__ = "2.5.0"
__version__ = "2.5.0.dev0"

View File

@@ -48,7 +48,7 @@ class EventSerializer(I18nAwareModelSerializer):
class Meta:
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',
'presale_end', 'location', 'has_subevents', 'meta_data', 'plugins')

View File

@@ -12,7 +12,6 @@ from rest_framework.reverse import reverse
from pretix.api.serializers.i18n import I18nAwareModelSerializer
from pretix.base.channels import get_all_sales_channels
from pretix.base.i18n import language
from pretix.base.models import (
Checkin, Invoice, InvoiceAddress, InvoiceLine, Order, OrderPosition,
Question, QuestionAnswer,
@@ -141,21 +140,20 @@ class PdfDataSerializer(serializers.Field):
res = {}
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
# we serialize a list.
# This needs to have some extra performance improvements to avoid creating hundreds of queries when
# we serialize a list.
if 'vars' not in self.context:
self.context['vars'] = get_variables(self.context['request'].event)
if 'vars' not in self.context:
self.context['vars'] = get_variables(self.context['request'].event)
for k, f in self.context['vars'].items():
res[k] = f['evaluate'](instance, instance.order, ev)
for k, f in self.context['vars'].items():
res[k] = f['evaluate'](instance, instance.order, ev)
if not hasattr(ev, '_cached_meta_data'):
ev._cached_meta_data = ev.meta_data
if not hasattr(ev, '_cached_meta_data'):
ev._cached_meta_data = ev.meta_data
for k, v in ev._cached_meta_data.items():
res['meta:' + k] = v
for k, v in ev._cached_meta_data.items():
res['meta:' + k] = v
return res
@@ -231,7 +229,7 @@ class OrderSerializer(I18nAwareModelSerializer):
class Meta:
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',
'checkin_attention', 'last_modified', 'payments', 'refunds', 'require_approval', 'sales_channel')
@@ -413,7 +411,7 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
class Meta:
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')
def validate_payment_provider(self, pp):

View File

@@ -16,7 +16,7 @@ from rest_framework.exceptions import (
APIException, NotFound, PermissionDenied, ValidationError,
)
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 pretix.api.models import OAuthAccessToken
@@ -51,10 +51,10 @@ class OrderFilter(FilterSet):
class Meta:
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
queryset = Order.objects.none()
filter_backends = (DjangoFilterBackend, OrderingFilter)
@@ -378,13 +378,6 @@ class OrderViewSet(DestroyModelMixin, CreateModelMixin, viewsets.ReadOnlyModelVi
def perform_create(self, serializer):
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):
order = django_filters.CharFilter(field_name='order', lookup_expr='code__iexact')
@@ -397,8 +390,6 @@ class OrderPositionFilter(FilterSet):
Q(secret__istartswith=value)
| Q(attendee_name_cached__icontains=value)
| Q(addon_to__attendee_name_cached__icontains=value)
| Q(attendee_email__icontains=value)
| Q(addon_to__attendee_email__icontains=value)
| Q(order__code__istartswith=value)
| Q(order__invoice_address__name_cached__icontains=value)
| Q(order__email__icontains=value)

View File

@@ -113,7 +113,7 @@ def register_default_webhook_events(sender, **kwargs):
_('New order placed'),
),
ParametrizedOrderWebhookEvent(
'pretix.event.order.placed.require_approval',
'pretix.event.order.placed.required_approval',
_('New order requires approval'),
),
ParametrizedOrderWebhookEvent(

View File

@@ -1,6 +1,8 @@
import logging
from smtplib import SMTPResponseException
import bleach
import markdown
from django.conf import settings
from django.core.mail.backends.smtp import EmailBackend
from django.dispatch import receiver
@@ -10,7 +12,7 @@ from inlinestyler.utils import inline_css
from pretix.base.models import Event, Order
from pretix.base.signals import register_html_mail_renderers
from pretix.base.templatetags.rich_text import markdown_compile_email
from pretix.base.templatetags.rich_text import markdown_compile
logger = logging.getLogger('pretix.base.email')
@@ -25,7 +27,7 @@ class CustomSMTPBackend(EmailBackend):
if code != 250:
logger.warn('Error testing mail settings, code %d, resp: %s' % (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):
logger.warn('Error testing mail settings, code %d, resp: %s' % (code, resp))
raise SMTPResponseException(code, resp)
@@ -96,7 +98,7 @@ class TemplateBasedMailRenderer(BaseHTMLMailRenderer):
raise NotImplementedError()
def render(self, plain_body: str, plain_signature: str, subject: str, order: Order) -> str:
body_md = markdown_compile_email(plain_body)
body_md = bleach.linkify(markdown_compile(plain_body))
htmlctx = {
'site': settings.PRETIX_INSTANCE_NAME,
'site_url': settings.SITE_URL,
@@ -110,7 +112,7 @@ class TemplateBasedMailRenderer(BaseHTMLMailRenderer):
if plain_signature:
signature_md = plain_signature.replace('\n', '<br>\n')
signature_md = markdown_compile_email(signature_md)
signature_md = bleach.linkify(bleach.clean(markdown.markdown(signature_md), tags=bleach.ALLOWED_TAGS + ['p', 'br']))
htmlctx['signature'] = signature_md
if order:

View File

@@ -92,7 +92,7 @@ class ListExporter(BaseExporter):
choices=(
('xlsx', _('Excel (.xlsx)')),
('default', _('CSV (with commas)')),
('csv-excel', _('CSV (Excel-style)')),
('excel', _('CSV (Excel-style)')),
('semicolon', _('CSV (with semicolons)')),
),
)),

View File

@@ -1,11 +1,9 @@
import logging
import i18nfield.forms
from django import forms
from django.forms.models import ModelFormMetaclass
from django.utils import six
from django.utils.crypto import get_random_string
from formtools.wizard.views import SessionWizardView
from hierarkey.forms import HierarkeyForm
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])
# TODO: make sure pub is always correct
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

View File

@@ -9,7 +9,7 @@ import vat_moss.exchange_rates
from django.contrib.staticfiles import finders
from django.dispatch import receiver
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 reportlab.lib import pagesizes
from reportlab.lib.enums import TA_LEFT
@@ -267,13 +267,6 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
canvas.saveState()
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]):
canvas.drawCentredString(self.pagesize[0] / 2, 25 + (3.5 * i) * mm, line.strip())

View File

@@ -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()

View File

@@ -129,22 +129,13 @@ def get_language_from_request(request: HttpRequest) -> str:
if _supported is None:
_supported = OrderedDict(settings.LANGUAGES)
if request.path.startswith(get_script_prefix() + 'control'):
return (
get_language_from_user_settings(request)
or get_language_from_session_or_cookie(request)
or get_language_from_browser(request)
or get_language_from_event(request)
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()
)
return (
get_language_from_user_settings(request)
or get_language_from_session_or_cookie(request)
or get_language_from_browser(request)
or get_language_from_event(request)
or get_default_language()
)
def _parse_csp(header):

View File

@@ -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)
]

View File

@@ -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,
),
]

View File

@@ -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),
),
]

View File

@@ -1,27 +0,0 @@
# Generated by Django 2.1.5 on 2019-03-04 17:26
from django.db import migrations
from django.db.models import Count
def make_checkins_unique(apps, se):
Checkin = apps.get_model('pretixbase', 'Checkin')
for d in Checkin.objects.order_by().values('list_id', 'position_id').annotate(c=Count('id')).filter(c__gt=1):
for c in Checkin.objects.filter(list_id=d['list_id'], position_id=d['position_id'])[:d['c'] - 1]:
c.delete()
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0111_auto_20190219_0949'),
]
operations = [
migrations.RunPython(
make_checkins_unique, migrations.RunPython.noop,
),
migrations.AlterUniqueTogether(
name='checkin',
unique_together={('list', 'position')},
),
]

View File

@@ -7,6 +7,7 @@ from .event import (
Event, Event_SettingsStore, EventLock, EventMetaProperty, EventMetaValue,
RequiredAction, SubEvent, SubEventMetaValue, generate_invite_token,
)
from .index import OrderSearchIndex
from .invoices import Invoice, InvoiceLine, invoice_filename
from .items import (
Item, ItemAddOn, ItemCategory, ItemVariation, Question, QuestionOption,

View File

@@ -77,13 +77,6 @@ class LoggingMixin:
logentry = LogEntry(content_object=self, user=user, action_type=action, event=event, **kwargs)
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)
elif data:
raise TypeError("You should only supply dictionaries as log data.")

View File

@@ -167,9 +167,6 @@ class Checkin(models.Model):
'pretixbase.CheckinList', related_name='checkins', on_delete=models.PROTECT,
)
class Meta:
unique_together = (('list', 'position'),)
def __repr__(self):
return "<Checkin: pos {} on list '{}' at {}>".format(
self.position, self.list, self.datetime

View File

@@ -242,8 +242,6 @@ class Event(EventMixin, LoggedModel):
:param organizer: The organizer this event belongs to
:type organizer: Organizer
:param testmode: This event is in test mode
:type testmode: bool
:param name: This event's full title
:type name: str
: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'
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)
testmode = models.BooleanField(default=False)
name = I18nCharField(
max_length=200,
verbose_name=_("Event name"),
@@ -324,7 +321,7 @@ class Event(EventMixin, LoggedModel):
verbose_name=_("Location"),
)
plugins = models.TextField(
null=False, blank=True,
null=True, blank=True,
verbose_name=_("Plugins"),
)
comment = models.TextField(

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()

View File

@@ -118,8 +118,8 @@ class Invoice(models.Model):
self.invoice_from,
(self.invoice_from_zipcode or "") + " " + (self.invoice_from_city or ""),
str(self.invoice_from_country),
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", "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 "",
]
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:
self.prefix = self.event.settings.invoice_numbers_prefix or (self.event.slug.upper() + '-')
if not self.invoice_no:
if self.order.testmode:
self.prefix += 'TEST-'
for i in range(10):
if self.event.settings.get('invoice_numbers_consecutive'):
self.invoice_no = self._get_numeric_invoice_number()
@@ -235,6 +233,3 @@ class InvoiceLine(models.Model):
class Meta:
ordering = ('position', 'pk')
def __str__(self):
return 'Line {} of invoice {}'.format(self.position, self.invoice)

View File

@@ -76,8 +76,6 @@ class Order(LockModel, LoggedModel):
:type event: Event
:param email: The email of the person who ordered this
:type email: str
:param testmode: Whether this is a test mode order
:type testmode: bool
:param locale: The locale of this order
:type locale: str
:param secret: A secret string that is required to modify the order
@@ -123,7 +121,6 @@ class Order(LockModel, LoggedModel):
verbose_name=_("Status"),
db_index=True
)
testmode = models.BooleanField(default=False)
event = models.ForeignKey(
Event,
verbose_name=_("Event"),
@@ -188,23 +185,6 @@ class Order(LockModel, LoggedModel):
def __str__(self):
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
def fees(self):
"""
@@ -229,8 +209,6 @@ class Order(LockModel, LoggedModel):
@cached_property
def meta_info_data(self):
if not self.meta_info:
return {}
try:
return json.loads(self.meta_info)
except TypeError:
@@ -512,10 +490,6 @@ class Order(LockModel, LoggedModel):
charset = list('ABCDEFGHJKLMNPQRSTUVWXYZ3789')
while True:
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():
self.code = code
return
@@ -687,9 +661,6 @@ class Order(LockModel, LoggedModel):
if not self.email:
return
for k, v in self.event.meta_data.items():
context['meta_' + k] = v
with language(self.locale):
recipient = self.email
try:
@@ -722,6 +693,45 @@ class Order(LockModel, LoggedModel):
continue
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:
secret = get_random_string(length=32, allowed_chars=string.ascii_letters + string.digits)
@@ -1066,9 +1076,6 @@ class OrderPayment(models.Model):
class Meta:
ordering = ('local_id',)
def __str__(self):
return self.full_id
@property
def info_data(self):
"""
@@ -1088,7 +1095,7 @@ class OrderPayment(models.Model):
"""
return self.order.event.get_payment_providers().get(self.provider)
def _mark_paid(self, force, count_waitinglist, user, auth, overpaid=False):
def _mark_paid(self, force, count_waitinglist, user, auth):
from pretix.base.signals import order_paid
can_be_paid = self.order._can_be_paid(count_waitinglist=count_waitinglist)
if not force and can_be_paid is not True:
@@ -1105,9 +1112,6 @@ class OrderPayment(models.Model):
'date': self.payment_date,
'force': force
}, user=user, auth=auth)
if overpaid:
self.order.log_action('pretix.event.order.overpaid', {}, user=user, auth=auth)
order_paid.send(self.order.event, order=self.order)
def confirm(self, count_waitinglist=True, send_mail=True, force=False, user=None, auth=None, mail_text=''):
@@ -1173,10 +1177,10 @@ class OrderPayment(models.Model):
# Performance optimization. In this case, there's really no reason to lock everything and an atomic
# database transaction is more than enough.
with transaction.atomic():
self._mark_paid(force, count_waitinglist, user, auth, overpaid=payment_sum - refund_sum > self.order.total)
self._mark_paid(force, count_waitinglist, user, auth)
else:
with self.order.event.lock():
self._mark_paid(force, count_waitinglist, user, auth, overpaid=payment_sum - refund_sum > self.order.total)
self._mark_paid(force, count_waitinglist, user, auth)
invoice = None
if invoice_qualified(self.order):
@@ -1378,9 +1382,6 @@ class OrderRefund(models.Model):
class Meta:
ordering = ('local_id',)
def __str__(self):
return self.full_id
@property
def info_data(self):
"""

View File

@@ -177,7 +177,6 @@ class ParametrizedOrderNotificationType(NotificationType):
n.add_attribute(_('Event'), order.event.name)
n.add_attribute(_('Order code'), order.code)
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 status'), order.get_status_display())
n.add_attribute(_('Order positions'), str(order.positions.count()))
@@ -236,12 +235,6 @@ def register_default_notification_types(sender, **kwargs):
_('Order changed'),
_('Order {order.code} has been changed.')
),
ParametrizedOrderNotificationType(
sender,
'pretix.event.order.overpaid',
_('Order has been overpaid'),
_('Order {order.code} has been overpaid.')
),
ParametrizedOrderNotificationType(
sender,
'pretix.event.order.refund.created.externally',

View File

@@ -88,18 +88,6 @@ class BasePaymentProvider:
"""
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:
"""
Calculate the fee for this payment provider which will be added to
@@ -725,11 +713,6 @@ class ManualPayment(BasePaymentProvider):
identifier = 'manual'
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
def is_implicit(self):
return 'pretix.plugins.manualpayment' not in self.event.plugins

View File

@@ -110,18 +110,7 @@ DEFAULT_VARIABLES = OrderedDict((
("event_begin", {
"label": _("Event begin date and time"),
"editor_sample": _("2017-05-31 20:00"),
"evaluate": lambda op, order, ev: date_format(
ev.date_from.astimezone(timezone(ev.settings.timezone)),
"SHORT_DATETIME_FORMAT"
) if ev.date_from else ""
}),
("event_begin_date", {
"label": _("Event begin date"),
"editor_sample": _("2017-05-31"),
"evaluate": lambda op, order, ev: date_format(
ev.date_from.astimezone(timezone(ev.settings.timezone)),
"SHORT_DATE_FORMAT"
) if ev.date_from else ""
"evaluate": lambda op, order, ev: ev.get_date_from_display(show_times=True)
}),
("event_begin_time", {
"label": _("Event begin time"),

View File

@@ -79,10 +79,6 @@ def mail(email: str, subject: str, template: Union[str, LazyI18nString],
headers = headers or {}
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:
try:
context.update({
@@ -130,8 +126,6 @@ def mail(email: str, subject: str, template: Union[str, LazyI18nString],
body_plain += "\r\n\r\n-- \r\n"
if order:
if order.testmode:
subject = "[TESTMODE] " + subject
body_plain += _(
"You are receiving this email because you placed an order for {event}."
).format(event=event.name)

View File

@@ -533,7 +533,6 @@ def _create_order(event: Event, email: str, positions: List[CartPosition], now_d
datetime=now_dt,
locale=locale,
total=total,
testmode=event.testmode,
meta_info=json.dumps(meta_info or {}),
require_approval=any(p.item.require_approval for p in positions),
sales_channel=sales_channel
@@ -823,7 +822,7 @@ class OrderChangeManager:
if keep_price:
price = TaxedPrice(gross=position.price, net=position.price - position.tax_value,
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:
price = get_price(item, variation, voucher=position.voucher, subevent=position.subevent,
invoice_address=self._invoice_address)
@@ -963,7 +962,7 @@ class OrderChangeManager:
)
self.order.save()
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.save()
elif self.open_payment:
@@ -983,7 +982,7 @@ class OrderChangeManager:
}, user=self.user, auth=self.auth)
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
# 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)
@@ -998,7 +997,7 @@ class OrderChangeManager:
except Quota.QuotaExceededException:
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(
state=OrderPayment.PAYMENT_STATE_CREATED,
provider='free',
@@ -1126,7 +1125,6 @@ class OrderChangeManager:
split_order.code = None
split_order.datetime = now()
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.log_action('pretix.event.order.changed.split_from', user=self.user, auth=self.auth, data={
'original_order': self.order.code

View File

@@ -31,13 +31,12 @@ def run_update_check(sender, **kwargs):
@app.task
def update_check():
gs = GlobalSettingsObject()
if not gs.settings.update_check_perform:
return
if not gs.settings.update_check_id:
gs.settings.set('update_check_id', uuid.uuid4().hex)
if not gs.settings.update_check_perform:
return
if 'runserver' in sys.argv:
gs.settings.set('update_check_last', now())
gs.settings.set('update_check_result', {

View File

@@ -78,28 +78,13 @@ def abslink_callback(attrs, new=False):
return attrs
def markdown_compile_email(source):
return bleach.linkify(bleach.clean(
markdown.markdown(
source,
extensions=[
'markdown.extensions.sane_lists',
# 'markdown.extensions.nl2br' # disabled for backwards-compatibility
]
),
tags=ALLOWED_TAGS,
attributes=ALLOWED_ATTRIBUTES,
protocols=ALLOWED_PROTOCOLS,
))
def markdown_compile(source):
return bleach.clean(
markdown.markdown(
source,
extensions=[
'markdown.extensions.sane_lists',
'markdown.extensions.nl2br'
# 'markdown.extensions.nl2br', # TODO: Enable, but check backwards-compatibility issues e.g. with mails
]
),
tags=ALLOWED_TAGS,

View File

@@ -52,15 +52,6 @@ def contextprocessor(request):
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']:
child_sess = request.session.get('child_session_{}'.format(request.event.pk))
s = SessionStore()

View File

@@ -973,13 +973,6 @@ class MailSettingsForm(SettingsForm):
self.fields['mail_html_renderer'].choices = [
(r.identifier, r.verbose_name) for r in event.get_html_mail_renderers().values()
]
keys = list(event.meta_data.keys())
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):
data = self.cleaned_data

View File

@@ -212,7 +212,6 @@ class EventOrderFilterForm(OrderFilterForm):
('overpaid', _('Overpaid')),
('underpaid', _('Underpaid')),
('pendingpaid', _('Pending (but fully paid)')),
('testmode', _('Test mode')),
),
required=False,
)
@@ -299,10 +298,6 @@ class EventOrderFilterForm(OrderFilterForm):
status=Order.STATUS_PENDING,
require_approval=True
)
elif fdata.get('status') == 'testmode':
qs = qs.filter(
testmode=True
)
elif fdata.get('status') == 'cp':
s = OrderPosition.objects.filter(
order=OuterRef('pk')

View File

@@ -160,7 +160,6 @@ class ItemCreateForm(I18nModelForm):
def __init__(self, *args, **kwargs):
self.event = kwargs['event']
self.user = kwargs.pop('user')
super().__init__(*args, **kwargs)
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:
quota = self.cleaned_data.get('quota_add_existing')
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:
quota_name = self.cleaned_data.get('quota_add_new_name')
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
)
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('copy_from') and self.cleaned_data.get('copy_from').has_variations:

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.refunded': _('The order has been refunded.'),
'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.require_approval': _('The order requires approval before it can continue to be processed.'),
'pretix.event.order.approved': _('The order has been approved.'),
@@ -211,7 +210,6 @@ def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
'pretix.event.order.payment.started': _('Payment {local_id} has been started.'),
'pretix.event.order.payment.failed': _('Payment {local_id} has failed.'),
'pretix.event.order.quotaexceeded': _('The order could not be marked as paid: {message}'),
'pretix.event.order.overpaid': _('The order has been overpaid.'),
'pretix.event.order.refund.created': _('Refund {local_id} has been created.'),
'pretix.event.order.refund.created.externally': _('Refund {local_id} has been created by an external entity.'),
'pretix.event.order.refund.requested': _('The customer requested you to issue a refund.'),
@@ -270,8 +268,6 @@ def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
'pretix.event.plugins.disabled': _('A plugin has been disabled.'),
'pretix.event.live.activated': _('The shop has been taken live.'),
'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.changed': _('The event settings have been changed.'),
'pretix.event.question.option.added': _('An answer option has been added to the question.'),

View File

@@ -235,14 +235,6 @@
</ul>
</div>
<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 %}
{% for nav in nav_items %}
<li>
@@ -325,20 +317,6 @@
</div>
{% endfor %}
{% 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 %}
<div class="alert alert-info">
<a href="{% url "control:global.update" %}">

View File

@@ -70,7 +70,6 @@
<a href="?{% url_replace request 'ordering' 'email'%}"><i class="fa fa-caret-up"></i></a></th>
<th>{% trans "Name" %} <a href="?{% url_replace request 'ordering' '-name'%}"><i class="fa fa-caret-down"></i></a>
<a href="?{% url_replace request 'ordering' 'name'%}"><i class="fa fa-caret-up"></i></a></th>
<th>{% trans "Ticket code" %}</th>
<th>{% trans "Status" %} <a href="?{% url_replace request 'ordering' '-status'%}"><i class="fa fa-caret-down"></i></a>
<a href="?{% url_replace request 'ordering' 'status'%}"><i class="fa fa-caret-up"></i></a></th>
<th>{% trans "Timestamp" %} <a href="?{% url_replace request 'ordering' '-timestamp'%}"><i class="fa fa-caret-down"></i></a>
@@ -90,9 +89,6 @@
{% if e.order.status == "n" %}
<span class="label label-warning">{% trans "unpaid" %}</span>
{% endif %}
{% if e.order.testmode %}
<span class="label label-warning">{% trans "TEST MODE" %}</span>
{% endif %}
</td>
<td>{{ e.item }}{% if e.variation %} {{ e.variation }}{% endif %}</td>
<td>{{ e.order.email }}</td>
@@ -103,9 +99,6 @@
{{ e.attendee_name }}
{% endif %}
</td>
<td>
{{ e.secret|slice:":10" }}…
</td>
<td>
{% if not e.last_checked_in %}
<span class="label label-danger">{% trans "Not checked in" %}</span>

View File

@@ -3,113 +3,49 @@
{% load bootstrap3 %}
{% block content %}
<h1>{% trans "Shop status" %}</h1>
<div class="panel panel-default">
<div class="panel-heading">
{% trans "Shop visibility" %}
</div>
<div class="panel-body">
{% if request.event.live %}
<p>
{% 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>
{% if request.event.live %}
<p>
{% 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">
{% csrf_token %}
<input type="hidden" name="live" value="false">
{% endif %}
<div class="clear"></div>
</div>
</div>
<div class="form-group submit-group">
<button type="submit" class="btn btn-primary btn-save">
{% trans "Go offline" %}
</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 %}

View File

@@ -90,11 +90,6 @@
<span class="fa fa-eraser"></span>
{% trans "Delete personal data" %}
</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>
</form>

View File

@@ -10,7 +10,6 @@
<form action="" method="post" class="form-horizontal">
{% csrf_token %}
{{ wizard.management_form }}
{{ wizard.prefix_form }}
{% bootstrap_form_errors form %}
{% block form %}
{% endblock %}

View File

@@ -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-up"></i></a>
</th>
<th>
<th class="text-right">
{% 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-up"></i></a>
</th>
<th class="text-right">
</th>
</tr>
</thead>
<tbody>
@@ -119,7 +117,7 @@
</a>
{% endif %}
</td>
<td>
<td class="text-right">
{% if not e.live %}
<span class="label label-danger">{% trans "Shop disabled" %}</span>
{% elif e.presale_has_ended %}
@@ -130,17 +128,6 @@
<span class="label label-success">{% trans "On sale" %}</span>
{% endif %}
</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>
{% endfor %}
</tbody>

View File

@@ -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 %}

View File

@@ -16,9 +16,6 @@
{% blocktrans trimmed with code=order.code %}
Order details: {{ code }}
{% 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" %}
</h1>
{% if 'can_change_orders' in request.eventpermset %}
@@ -27,13 +24,6 @@
{% csrf_token %}
<div class="btn-toolbar" role="toolbar">
<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' %}
<a href="{% url "control:event.order.approve" event=request.event.slug organizer=request.event.organizer.slug code=order.code %}"
class="btn btn-primary">
@@ -280,10 +270,6 @@
</form>
{% endfor %}
{% 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 %}
</div>
{% endif %}

View File

@@ -22,10 +22,13 @@
<fieldset>
<legend>{% trans "E-mail preview" %}</legend>
<div class="tab-pane mail-preview-group">
<div lang="{{ order.locale }}" class="mail-preview">
<strong>{{ preview_output.subject }}</strong><br><br>
{{ preview_output.html|safe }}
</div>
<pre lang="" class="mail-preview">
{% for segment in preview_output %}
{% spaceless %}
{{ segment|linebreaksbr }}
{% endspaceless %}
{% endfor %}
</pre>
</div>
</fieldset>
{% endif %}

View File

@@ -85,7 +85,7 @@
</p>
{% endif %}
<div class="table-responsive">
<table class="table table-condensed table-hover table-orders">
<table class="table table-condensed table-hover">
<thead>
<tr>
<th>{% trans "Order code" %}
@@ -114,11 +114,9 @@
<strong>
<a
href="{% url "control:event.order" event=request.event.slug organizer=request.event.organizer.slug code=o.code %}">
{{ o.code }}</a>
{{ o.code }}
</a>
</strong>
{% if o.testmode %}
<span class="label label-warning">{% trans "TEST MODE" %}</span>
{% endif %}
</td>
<td>
{{ o.email|default_if_none:"" }}
@@ -147,32 +145,6 @@
</tr>
{% endfor %}
</tbody>
{% if sums %}
<tfoot>
<tr>
<th>{% trans "Sum over all pages" %}</th>
<th></th>
<th>
{% blocktrans trimmed count s=sums.c %}
1 order
{% plural %}
{{ s }} orders
{% endblocktrans %}
</th>
<th class="text-right">
{% if sums.s|default_if_none:"none" != "none" %}
{{ sums.s|money:request.event.currency }}
{% endif %}
</th>
<th class="text-right">
{% if sums.pc %}
{{ sums.pc }}
{% endif %}
</th>
<th></th>
</tr>
</tfoot>
{% endif %}
</table>
</div>
{% include "pretixcontrol/pagination.html" %}

View File

@@ -55,9 +55,6 @@
<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 }}
</strong>
{% if r.order.testmode %}
<span class="label label-warning">{% trans "TEST MODE" %}</span>
{% endif %}
</td>
<td>
{{ r.payment_provider.verbose_name }}

View File

@@ -15,7 +15,6 @@
<tr>
<th>{% trans "Event name" %}</th>
<th>{% trans "Start date" %}</th>
<th></th>
</tr>
</thead>
<tbody>
@@ -26,28 +25,13 @@
href="{% url "control:event.index" organizer=e.organizer.slug event=e.slug %}">{{ e.name }}</a></strong>
</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>
{% endfor %}
</tbody>
</table>
{% endif %}
{% if "can_create_events" in request.orgapermset %}
<a href="{% url "control:events.add" %}" class="btn btn-default">
<span class="fa fa-plus"></span>
{% trans "Create a new event" %}
</a>
{% endif %}
<a href="{% url "control:events.add" %}" class="btn btn-default">
<span class="fa fa-plus"></span>
{% trans "Create a new event" %}
</a>
{% endblock %}

View File

@@ -59,11 +59,9 @@
<td>
<strong>
<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>
{% if o.testmode %}
<span class="label label-warning">{% trans "TEST MODE" %}</span>
{% endif %}
</td>
<td>{{ o.event.name }}</td>
<td>

View File

@@ -222,8 +222,6 @@ urlpatterns = [
name='event.order.approve'),
url(r'^orders/(?P<code>[0-9A-Z]+)/deny$', orders.OrderDeny.as_view(),
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(),
name='event.order.info'),
url(r'^orders/(?P<code>[0-9A-Z]+)/sendmail$', orders.OrderSendMail.as_view(),

View File

@@ -158,7 +158,7 @@ class CheckinListList(EventPermissionRequiredMixin, PaginationMixin, ListView):
cl.subevent.event = self.request.event # re-use same event object to make sure settings are cached
cl.checkin_count = annotations.get(cl.pk, {}).get('checkin_count', 0)
cl.position_count = annotations.get(cl.pk, {}).get('position_count', 0)
cl.percent = annotations.get(cl.pk, {}).get('percent', 0)
cl.percent_count = annotations.get(cl.pk, {}).get('percent_count', 0)
ctx['checkinlists'] = clists
return ctx

View File

@@ -183,20 +183,8 @@ def shop_state_widget(sender, **kwargs):
'priority': 1000,
'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'),
state=_('live') if sender.live and not sender.testmode else (
_('live and in test mode') if sender.live else (
_('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'
)
)
),
state=_('live') if sender.live else _('not yet public'),
icon='fa-check-circle' if sender.live else 'fa-times-circle',
cls='live' if sender.live else 'off'
),
'url': reverse('control:event.live', kwargs={

View File

@@ -5,6 +5,7 @@ from datetime import timedelta
from decimal import Decimal
from urllib.parse import urlsplit
import bleach
from django.conf import settings
from django.contrib import messages
from django.contrib.contenttypes.models import ContentType
@@ -38,7 +39,7 @@ from pretix.base.services import tickets
from pretix.base.services.invoices import build_preview_invoice_pdf
from pretix.base.signals import register_ticket_outputs
from pretix.base.templatetags.money import money_filter
from pretix.base.templatetags.rich_text import markdown_compile_email
from pretix.base.templatetags.rich_text import markdown_compile
from pretix.control.forms.event import (
CancelSettingsForm, CommentForm, DisplaySettingsForm, EventDeleteForm,
EventMetaValueForm, EventSettingsForm, EventUpdateForm,
@@ -53,8 +54,8 @@ from pretix.multidomain.urlreverse import get_domain
from pretix.plugins.stripe.payment import StripeSettingsHolder
from pretix.presale.style import regenerate_css
from ..logdisplay import OVERVIEW_BLACKLIST
from . import CreateView, PaginationMixin, UpdateView
from ..logdisplay import OVERVIEW_BLACKLIST
class EventSettingsViewMixin:
@@ -572,13 +573,9 @@ class MailSettingsPreview(EventPermissionRequiredMixin, View):
locales[str(idx)] = val[0]
return locales
@cached_property
def meta_properties(self):
return [p.name for p in self.request.organizer.meta_properties.all()]
@cached_property
def items(self):
kv = {
return {
'mail_text_order_placed': ['total', 'currency', 'date', 'invoice_company', 'total_with_currency',
'event', 'payment_info', 'url', 'invoice_name'],
'mail_text_order_paid': ['event', 'url', 'invoice_name', 'invoice_company', 'payment_info'],
@@ -599,10 +596,6 @@ class MailSettingsPreview(EventPermissionRequiredMixin, View):
'mail_text_order_denied': ['total', 'currency', 'date', 'invoice_company',
'total_with_currency', 'event', 'url', 'invoice_name'],
}
for v in kv.values():
for p in self.meta_properties:
v.append('meta_' + p)
return kv
@cached_property
def base_data(self):
@@ -614,7 +607,7 @@ class MailSettingsPreview(EventPermissionRequiredMixin, View):
orders = [' - {} - {}'.format(self.generate_order_fullname(self.request.event.slug, order['code']),
self.generate_order_url(order['code'], order['secret']))
for order in user_orders]
d = {
return {
'event': self.request.event.name,
'total': 42.23,
'total_with_currency': LazyCurrencyNumber(42.23, self.request.event.currency),
@@ -627,11 +620,8 @@ class MailSettingsPreview(EventPermissionRequiredMixin, View):
'invoice_name': _('John Doe'),
'invoice_company': _('Sample Corporation'),
'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):
return build_absolute_uri('presale:event.order', kwargs={
@@ -663,9 +653,9 @@ class MailSettingsPreview(EventPermissionRequiredMixin, View):
idx = matched.group('idx')
if idx in self.supported_locale:
with translation.override(self.supported_locale[idx]):
msgs[self.supported_locale[idx]] = markdown_compile_email(
msgs[self.supported_locale[idx]] = bleach.linkify(markdown_compile(
v.format_map(self.placeholders(preview_item))
)
))
return JsonResponse({
'item': preview_item,
@@ -689,8 +679,7 @@ class MailSettingsRendererPreview(MailSettingsPreview):
expires=now(), code="PREVIEW", total=119)
item = request.event.items.create(name=ugettext("Sample product"), default_price=42.23,
description=ugettext("Sample product description"))
order.positions.create(item=item, attendee_name_parts={'_legacy': ugettext("John Doe")},
price=item.default_price)
order.positions.create(item=item, attendee_name_parts={'full_name': ugettext("John Doe")}, price=item.default_price)
v = renderers[request.GET.get('renderer')].render(
v,
str(request.event.settings.mail_text_signature),
@@ -853,55 +842,23 @@ class EventLive(EventPermissionRequiredMixin, TemplateView):
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['issues'] = self.request.event.live_issues
ctx['actual_orders'] = self.request.event.orders.filter(testmode=False).exists()
return ctx
def post(self, request, *args, **kwargs):
if request.POST.get("live") == "true" and not self.request.event.live_issues:
with transaction.atomic():
request.event.live = True
request.event.save()
self.request.event.log_action(
'pretix.event.live.activated', user=self.request.user, data={}
)
request.event.live = True
request.event.save()
self.request.event.log_action(
'pretix.event.live.activated', user=self.request.user, data={}
)
messages.success(self.request, _('Your shop is live now!'))
elif request.POST.get("live") == "false":
with transaction.atomic():
request.event.live = False
request.event.save()
self.request.event.log_action(
'pretix.event.live.deactivated', user=self.request.user, data={}
)
request.event.live = False
request.event.save()
self.request.event.log_action(
'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!'))
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())
def get_success_url(self) -> str:

View File

@@ -817,7 +817,7 @@ class ItemCreate(EventPermissionRequiredMixin, CreateView):
"""
newinst = Item(event=self.request.event)
kwargs = super().get_form_kwargs()
kwargs.update({'instance': newinst, 'user': self.request.user})
kwargs.update({'instance': newinst})
return kwargs
def form_invalid(self, form):

View File

@@ -1,5 +1,4 @@
from django.conf import settings
from django.contrib import messages
from django.db import transaction
from django.db.models import (
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.views import View
from django.views.generic import ListView
from formtools.wizard.views import SessionWizardView
from i18nfield.strings import LazyI18nString
from pretix.base.forms import SafeSessionWizardView
from pretix.base.i18n import language
from pretix.base.models import Event, Organizer, Quota, Team
from pretix.control.forms.event import (
@@ -95,13 +94,10 @@ class EventList(PaginationMixin, ListView):
def condition_copy(wizard):
return (
not wizard.clone_from and
EventWizardCopyForm.copy_from_queryset(wizard.request.user).exists()
)
return EventWizardCopyForm.copy_from_queryset(wizard.request.user).exists()
class EventWizard(SafeSessionWizardView):
class EventWizard(SessionWizardView):
form_list = [
('foundation', EventWizardFoundationForm),
('basics', EventWizardBasicsForm),
@@ -116,49 +112,6 @@ class EventWizard(SafeSessionWizardView):
'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):
ctx = super().get_context_data(form, **kwargs)
ctx['has_organizer'] = self.request.user.teams.filter(can_create_events=True).exists()
@@ -180,13 +133,6 @@ class EventWizard(SafeSessionWizardView):
}
if 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)
return kwargs
@@ -240,8 +186,6 @@ class EventWizard(SafeSessionWizardView):
if copy_data and copy_data['copy_from_event']:
from_event = copy_data['copy_from_event']
event.copy_data_from(from_event)
elif self.clone_from:
event.copy_data_from(self.clone_from)
elif event.has_subevents:
event.checkin_lists.create(
name=str(se),
@@ -265,7 +209,7 @@ class EventWizard(SafeSessionWizardView):
event.settings.set('locale', basics_data['locale'])
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={
'organizer': event.organizer.slug,
'event': event.slug,

View File

@@ -11,9 +11,7 @@ from django.conf import settings
from django.contrib import messages
from django.core.files import File
from django.db import transaction
from django.db.models import (
Count, IntegerField, OuterRef, ProtectedError, Q, Subquery, Sum,
)
from django.db.models import Count, IntegerField, OuterRef, Subquery
from django.http import (
FileResponse, Http404, HttpResponseNotAllowed, JsonResponse,
)
@@ -60,7 +58,6 @@ from pretix.base.signals import (
register_data_exporters, register_ticket_outputs,
)
from pretix.base.templatetags.money import money_filter
from pretix.base.templatetags.rich_text import markdown_compile_email
from pretix.base.views.mixins import OrderQuestionsViewMixin
from pretix.base.views.tasks import AsyncAction
from pretix.control.forms.filter import EventOrderFilterForm, RefundFilterForm
@@ -126,15 +123,6 @@ class OrderList(EventPermissionRequiredMixin, PaginationMixin, ListView):
o.has_external_refund = annotated.get(o.pk)['has_external_refund']
o.has_pending_refund = annotated.get(o.pk)['has_pending_refund']
if ctx['page_obj'].paginator.count < 1000:
# Performance safeguard: Only count positions if the data set is small
ctx['sums'] = self.get_queryset().annotate(
pcnt=Subquery(s, output_field=IntegerField())
).aggregate(
s=Sum('total'), pc=Sum('pcnt'), c=Count('id')
)
else:
ctx['sums'] = self.get_queryset().aggregate(s=Sum('total'), c=Count('id'))
return ctx
@cached_property
@@ -410,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):
permission = 'can_change_orders'
@@ -1493,10 +1452,10 @@ class OrderSendMail(EventPermissionRequiredMixin, OrderViewMixin, FormView):
email_template = LazyI18nString(form.cleaned_data['message'])
email_content = render_mail(email_template, email_context)
if self.request.POST.get('action') == 'preview':
self.preview_output = {
'subject': _('Subject: {subject}').format(subject=form.cleaned_data['subject']),
'html': markdown_compile_email(email_content)
}
self.preview_output = []
self.preview_output.append(
_('Subject: {subject}').format(subject=form.cleaned_data['subject']))
self.preview_output.append(email_content)
return self.get(self.request, *self.args, **self.kwargs)
else:
try:
@@ -1611,13 +1570,6 @@ class OrderGo(EventPermissionRequiredMixin, View):
return redirect('control:event.order', event=request.event.slug, organizer=request.event.organizer.slug,
code=order.code)
except Order.DoesNotExist:
try:
i = self.request.event.invoices.get(Q(invoice_no=code) | Q(full_invoice_no=code))
return redirect('control:event.order', event=request.event.slug, organizer=request.event.organizer.slug,
code=i.order.code)
except Invoice.DoesNotExist:
pass
messages.error(request, _('There is no order with the given order code.'))
return redirect('control:event.orders', event=request.event.slug, organizer=request.event.organizer.slug)

View File

@@ -46,7 +46,7 @@ class OrderSearch(PaginationMixin, ListView):
return ctx
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):
qs = qs.filter(
@@ -59,47 +59,9 @@ class OrderSearch(PaginationMixin, ListView):
if self.filter_form.is_valid():
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(
'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(
'event', 'event__organizer'
).select_related('invoice_address')
)

View File

@@ -117,7 +117,6 @@ class SubEventDelete(EventPermissionRequiredMixin, DeleteView):
return HttpResponseRedirect(self.get_success_url())
else:
self.object.log_action('pretix.subevent.deleted', user=self.request.user)
self.object.cartposition_set.all().delete()
self.object.delete()
messages.success(request, pgettext_lazy('subevent', 'The selected date has been deleted.'))
return HttpResponseRedirect(success_url)

View File

@@ -32,7 +32,7 @@ def get_sizes(size, imgsize):
(0, int((imgsize[1] * wfactor - imgsize[1] * hfactor) / 2),
imgsize[0] * hfactor, int((imgsize[1] * wfactor + imgsize[1] * wfactor) / 2))
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))
else:
return (int(imgsize[0] * hfactor), int(size[1])), \

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-03-11 15:02+0000\n"
"POT-Creation-Date: 2019-02-01 16:27+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
@@ -198,10 +198,6 @@ msgstr ""
msgid "Use a different name internally"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:639
msgid "Click to close"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/question.js:41
msgid "Others"
msgstr ""

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-03-11 15:02+0000\n"
"POT-Creation-Date: 2019-02-01 16:27+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
@@ -197,10 +197,6 @@ msgstr ""
msgid "Use a different name internally"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:639
msgid "Click to close"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/question.js:41
msgid "Others"
msgstr ""

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-03-11 15:02+0000\n"
"POT-Creation-Date: 2019-02-01 16:27+0000\n"
"PO-Revision-Date: 2018-04-24 14:22+0000\n"
"Last-Translator: Pernille Thorsen <perth@aarhus.dk>\n"
"Language-Team: Danish <https://translate.pretix.eu/projects/pretix/pretix-js/"
@@ -214,10 +214,6 @@ msgstr "Ingen"
msgid "Use a different name internally"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:639
msgid "Click to close"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/question.js:41
msgid "Others"
msgstr "Andre"

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-03-11 15:02+0000\n"
"POT-Creation-Date: 2019-02-01 16:27+0000\n"
"PO-Revision-Date: 2018-12-02 15:44+0000\n"
"Last-Translator: Alexander Schwartz <alexander.schwartz@gmx.net>\n"
"Language-Team: German <https://translate.pretix.eu/projects/pretix/pretix-js/"
@@ -217,10 +217,6 @@ msgstr "Keine"
msgid "Use a different name internally"
msgstr "Intern einen anderen Namen verwenden"
#: pretix/static/pretixcontrol/js/ui/main.js:639
msgid "Click to close"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/question.js:41
msgid "Others"
msgstr "Sonstige"

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-03-11 15:02+0000\n"
"POT-Creation-Date: 2019-02-01 16:27+0000\n"
"PO-Revision-Date: 2018-12-02 13:41+0000\n"
"Last-Translator: Alexander Schwartz <alexander.schwartz@gmx.net>\n"
"Language-Team: German (informal) <https://translate.pretix.eu/projects/"
@@ -216,10 +216,6 @@ msgstr "Keine"
msgid "Use a different name internally"
msgstr "Intern einen anderen Namen verwenden"
#: pretix/static/pretixcontrol/js/ui/main.js:639
msgid "Click to close"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/question.js:41
msgid "Others"
msgstr "Sonstige"

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-03-11 15:02+0000\n"
"POT-Creation-Date: 2019-02-01 16:27+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -198,10 +198,6 @@ msgstr ""
msgid "Use a different name internally"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:639
msgid "Click to close"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/question.js:41
msgid "Others"
msgstr ""

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-03-11 15:02+0000\n"
"POT-Creation-Date: 2019-02-01 16:27+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
@@ -197,10 +197,6 @@ msgstr ""
msgid "Use a different name internally"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:639
msgid "Click to close"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/question.js:41
msgid "Others"
msgstr ""

File diff suppressed because it is too large Load Diff

View File

@@ -7,9 +7,9 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-03-11 15:02+0000\n"
"PO-Revision-Date: 2019-02-20 03:00+0000\n"
"Last-Translator: oocf <oswaldocerna@gmail.com>\n"
"POT-Creation-Date: 2019-02-01 16:27+0000\n"
"PO-Revision-Date: 2018-11-29 09:01+0000\n"
"Last-Translator: arabestia <sergioadalbertor@gmail.com>\n"
"Language-Team: Spanish <https://translate.pretix.eu/projects/pretix/pretix-"
"js/es/>\n"
"Language: es\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 3.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:62
@@ -62,14 +62,19 @@ msgstr ""
#: pretix/static/pretixbase/js/asynctask.js:45
#: 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 ""
"Your request arrived on the server but we still wait for it to be processed. "
"If this takes longer than two minutes, please contact us or go back in your "
"browser and try again."
msgstr ""
"Su solicitud llegó al servidor pero seguimos esperando a que sea procesada. "
"Si toma más de dos minutos, por favor contáctenos o regrese a la página "
"anterior en su navegador y pruebe de nuevo."
"Su solicitud ha sido enviada al servidor y se procesada en breve. Si el "
"proceso tomara más de dos minutos, por favor, contáctenos o regrese a la "
"página anterior en su navegador y pruebe de nuevo."
#: pretix/static/pretixbase/js/asynctask.js:66
#: pretix/static/pretixbase/js/asynctask.js:124
@@ -218,10 +223,6 @@ msgstr "Ninguno"
msgid "Use a different name internally"
msgstr "Usar un nombre diferente internamente"
#: pretix/static/pretixcontrol/js/ui/main.js:639
msgid "Click to close"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/question.js:41
msgid "Others"
msgstr "Otros"

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: French\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-03-11 15:02+0000\n"
"POT-Creation-Date: 2019-02-01 16:27+0000\n"
"PO-Revision-Date: 2018-10-28 10:23+0000\n"
"Last-Translator: Arnaud Vergnet <keplyx@gmail.com>\n"
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix-js/"
@@ -219,10 +219,6 @@ msgstr "Aucun"
msgid "Use a different name internally"
msgstr "Utiliser un nom différent en interne"
#: pretix/static/pretixcontrol/js/ui/main.js:639
msgid "Click to close"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/question.js:41
msgid "Others"
msgstr "Autres"

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-03-11 15:02+0000\n"
"POT-Creation-Date: 2019-02-01 16:27+0000\n"
"PO-Revision-Date: 2019-01-02 08:20+0000\n"
"Last-Translator: amefad <fame@libero.it>\n"
"Language-Team: Italian <https://translate.pretix.eu/projects/pretix/pretix-"
@@ -212,10 +212,6 @@ msgstr ""
msgid "Use a different name internally"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:639
msgid "Click to close"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/question.js:41
msgid "Others"
msgstr ""

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-03-11 15:02+0000\n"
"POT-Creation-Date: 2019-02-01 16:27+0000\n"
"PO-Revision-Date: 2019-01-08 12:30+0000\n"
"Last-Translator: Maarten van den Berg <maartenberg1@gmail.com>\n"
"Language-Team: Dutch <https://translate.pretix.eu/projects/pretix/pretix-js/"
@@ -212,10 +212,6 @@ msgstr "Geen"
msgid "Use a different name internally"
msgstr "Gebruik intern een andere naam"
#: pretix/static/pretixcontrol/js/ui/main.js:639
msgid "Click to close"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/question.js:41
msgid "Others"
msgstr "Andere"

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-03-11 15:02+0000\n"
"POT-Creation-Date: 2019-02-01 16:27+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
@@ -197,10 +197,6 @@ msgstr ""
msgid "Use a different name internally"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:639
msgid "Click to close"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/question.js:41
msgid "Others"
msgstr ""

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-03-11 15:02+0000\n"
"POT-Creation-Date: 2019-02-01 16:27+0000\n"
"PO-Revision-Date: 2019-01-08 21:00+0000\n"
"Last-Translator: Maarten van den Berg <maartenberg1@gmail.com>\n"
"Language-Team: Dutch (informal) <https://translate.pretix.eu/projects/pretix/"
@@ -214,10 +214,6 @@ msgstr "Geen"
msgid "Use a different name internally"
msgstr "Gebruik intern een andere naam"
#: pretix/static/pretixcontrol/js/ui/main.js:639
msgid "Click to close"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/question.js:41
msgid "Others"
msgstr "Andere"

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-03-11 15:02+0000\n"
"POT-Creation-Date: 2019-02-01 16:27+0000\n"
"PO-Revision-Date: 2018-10-22 04:23+0000\n"
"Last-Translator: Samir C. Costa <samirfor@gmail.com>\n"
"Language-Team: Portuguese (Brazil) <https://translate.pretix.eu/projects/"
@@ -216,10 +216,6 @@ msgstr "Nenhum"
msgid "Use a different name internally"
msgstr "Use um nome diferente internamente"
#: pretix/static/pretixcontrol/js/ui/main.js:639
msgid "Click to close"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/question.js:41
msgid "Others"
msgstr "Outros"

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-03-11 15:02+0000\n"
"POT-Creation-Date: 2019-02-01 16:27+0000\n"
"PO-Revision-Date: 2019-01-02 08:21+0000\n"
"Last-Translator: Alexey Zh <write2aracon@gmail.com>\n"
"Language-Team: Russian <https://translate.pretix.eu/projects/pretix/pretix-"
@@ -200,10 +200,6 @@ msgstr ""
msgid "Use a different name internally"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:639
msgid "Click to close"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/question.js:41
msgid "Others"
msgstr ""

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-03-11 15:02+0000\n"
"POT-Creation-Date: 2019-02-01 16:27+0000\n"
"PO-Revision-Date: 2018-09-03 06:36+0000\n"
"Last-Translator: Yunus Fırat Pişkin <firat.piskin@idvlabs.com>\n"
"Language-Team: Turkish <https://translate.pretix.eu/projects/pretix/pretix-"
@@ -216,10 +216,6 @@ msgstr "Hiçbiri"
msgid "Use a different name internally"
msgstr "Dahili olarak farklı bir ad kullan"
#: pretix/static/pretixcontrol/js/ui/main.js:639
msgid "Click to close"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/question.js:41
msgid "Others"
msgstr "Diğerleri"

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