Compare commits

..

1 Commits

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

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

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

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

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

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

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

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

@@ -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):
"""
@@ -510,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
@@ -685,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:
@@ -720,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)
@@ -1064,9 +1076,6 @@ class OrderPayment(models.Model):
class Meta:
ordering = ('local_id',)
def __str__(self):
return self.full_id
@property
def info_data(self):
"""
@@ -1373,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()))

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

@@ -670,15 +670,15 @@ class CartManager:
self._check_max_cart_size()
self._calculate_expiry()
# with self.event.lock() as now_dt:
with transaction.atomic():
self.now_dt = now()
self._extend_expiry_of_valid_existing_positions()
err = self._delete_out_of_timeframe()
err = self.extend_expired_positions() or err
err = self._perform_operations() or err
if err:
raise CartError(err)
with self.event.lock() as now_dt:
with transaction.atomic():
self.now_dt = now_dt
self._extend_expiry_of_valid_existing_positions()
err = self._delete_out_of_timeframe()
err = self.extend_expired_positions() or err
err = self._perform_operations() or err
if err:
raise CartError(err)
def update_tax_rates(event: Event, cart_id: str, invoice_address: InvoiceAddress):

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
@@ -597,16 +596,16 @@ def _perform_order(event: str, payment_provider: str, position_ids: List[str],
except InvoiceAddress.DoesNotExist:
pass
# with event.lock() as now_dt:
now_dt = now()
positions = list(CartPosition.objects.filter(id__in=position_ids).select_related('item', 'variation', 'subevent'))
if len(positions) == 0:
raise OrderError(error_messages['empty'])
if len(position_ids) != len(positions):
raise OrderError(error_messages['internal'])
_check_positions(event, now_dt, positions, address=addr)
order = _create_order(event, email, positions, now_dt, pprov,
locale=locale, address=addr, meta_info=meta_info, sales_channel=sales_channel)
with event.lock() as now_dt:
positions = list(CartPosition.objects.filter(
id__in=position_ids).select_related('item', 'variation', 'subevent'))
if len(positions) == 0:
raise OrderError(error_messages['empty'])
if len(position_ids) != len(positions):
raise OrderError(error_messages['internal'])
_check_positions(event, now_dt, positions, address=addr)
order = _create_order(event, email, positions, now_dt, pprov,
locale=locale, address=addr, meta_info=meta_info, sales_channel=sales_channel)
invoice = order.invoices.last() # Might be generated by plugin already
if event.settings.get('invoice_generate') == 'True' and invoice_qualified(order):
@@ -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

@@ -84,7 +84,7 @@ def markdown_compile(source):
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.'),
@@ -269,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

@@ -89,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>

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

@@ -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:"" }}

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

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

@@ -573,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'],
@@ -600,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):
@@ -615,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),
@@ -628,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={
@@ -690,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),
@@ -854,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, Subquery,
)
from django.db.models import Count, IntegerField, OuterRef, Subquery
from django.http import (
FileResponse, Http404, HttpResponseNotAllowed, JsonResponse,
)
@@ -400,35 +398,6 @@ class OrderApprove(OrderView):
})
class OrderDelete(OrderView):
permission = 'can_change_orders'
def post(self, *args, **kwargs):
if self.order.testmode:
try:
with transaction.atomic():
self.order.gracefully_delete(user=self.request.user)
messages.success(self.request, _('The order has been deleted.'))
return redirect(reverse('control:event.orders', kwargs={
'event': self.request.event.slug,
'organizer': self.request.organizer.slug,
}))
except ProtectedError:
messages.error(self.request, _('The order could not be deleted as some constraints (e.g. data created '
'by plug-ins) do not allow it.'))
return self.get(self.request, *self.args, **self.kwargs)
return redirect(self.get_order_url())
def get(self, *args, **kwargs):
if not self.order.testmode:
messages.error(self.request, _('Only orders created in test mode can be deleted.'))
return redirect(self.get_order_url())
return render(self.request, 'pretixcontrol/order/delete.html', {
'order': self.order,
})
class OrderDeny(OrderView):
permission = 'can_change_orders'

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

@@ -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-02-20 16:52+0000\n"
"POT-Creation-Date: 2019-02-01 16:27+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"

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-02-20 16:52+0000\n"
"POT-Creation-Date: 2019-02-01 16:27+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"

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-02-20 16:52+0000\n"
"POT-Creation-Date: 2019-02-01 16:27+0000\n"
"PO-Revision-Date: 2018-04-24 14:22+0000\n"
"Last-Translator: Pernille Thorsen <perth@aarhus.dk>\n"
"Language-Team: Danish <https://translate.pretix.eu/projects/pretix/pretix-js/"

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-02-20 16:52+0000\n"
"POT-Creation-Date: 2019-02-01 16:27+0000\n"
"PO-Revision-Date: 2018-12-02 15:44+0000\n"
"Last-Translator: Alexander Schwartz <alexander.schwartz@gmx.net>\n"
"Language-Team: German <https://translate.pretix.eu/projects/pretix/pretix-js/"

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-02-20 16:52+0000\n"
"POT-Creation-Date: 2019-02-01 16:27+0000\n"
"PO-Revision-Date: 2018-12-02 13:41+0000\n"
"Last-Translator: Alexander Schwartz <alexander.schwartz@gmx.net>\n"
"Language-Team: German (informal) <https://translate.pretix.eu/projects/"

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-02-20 16:52+0000\n"
"POT-Creation-Date: 2019-02-01 16:27+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"

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-02-20 16:52+0000\n"
"POT-Creation-Date: 2019-02-01 16:27+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"

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-02-20 16:52+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

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-02-20 16:52+0000\n"
"POT-Creation-Date: 2019-02-01 16:27+0000\n"
"PO-Revision-Date: 2018-10-28 10:23+0000\n"
"Last-Translator: Arnaud Vergnet <keplyx@gmail.com>\n"
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix-js/"

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-02-20 16:52+0000\n"
"POT-Creation-Date: 2019-02-01 16:27+0000\n"
"PO-Revision-Date: 2019-01-02 08:20+0000\n"
"Last-Translator: amefad <fame@libero.it>\n"
"Language-Team: Italian <https://translate.pretix.eu/projects/pretix/pretix-"

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-02-20 16:52+0000\n"
"POT-Creation-Date: 2019-02-01 16:27+0000\n"
"PO-Revision-Date: 2019-01-08 12:30+0000\n"
"Last-Translator: Maarten van den Berg <maartenberg1@gmail.com>\n"
"Language-Team: Dutch <https://translate.pretix.eu/projects/pretix/pretix-js/"

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-02-20 16:52+0000\n"
"POT-Creation-Date: 2019-02-01 16:27+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"

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-02-20 16:52+0000\n"
"POT-Creation-Date: 2019-02-01 16:27+0000\n"
"PO-Revision-Date: 2019-01-08 21:00+0000\n"
"Last-Translator: Maarten van den Berg <maartenberg1@gmail.com>\n"
"Language-Team: Dutch (informal) <https://translate.pretix.eu/projects/pretix/"

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-02-20 16:52+0000\n"
"POT-Creation-Date: 2019-02-01 16:27+0000\n"
"PO-Revision-Date: 2018-10-22 04:23+0000\n"
"Last-Translator: Samir C. Costa <samirfor@gmail.com>\n"
"Language-Team: Portuguese (Brazil) <https://translate.pretix.eu/projects/"

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-02-20 16:52+0000\n"
"POT-Creation-Date: 2019-02-01 16:27+0000\n"
"PO-Revision-Date: 2019-01-02 08:21+0000\n"
"Last-Translator: Alexey Zh <write2aracon@gmail.com>\n"
"Language-Team: Russian <https://translate.pretix.eu/projects/pretix/pretix-"

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-02-20 16:52+0000\n"
"POT-Creation-Date: 2019-02-01 16:27+0000\n"
"PO-Revision-Date: 2018-09-03 06:36+0000\n"
"Last-Translator: Yunus Fırat Pişkin <firat.piskin@idvlabs.com>\n"
"Language-Team: Turkish <https://translate.pretix.eu/projects/pretix/pretix-"

View File

@@ -101,11 +101,6 @@ class BankTransfer(BasePaymentProvider):
def public_name(self):
return str(self.settings.get('public_name', as_type=LazyI18nString) or self.verbose_name)
@property
def test_mode_message(self):
return _('In test mode, you can just manually mark this order as paid in the backend after it has been '
'created.')
@property
def settings_form_fields(self):
d = OrderedDict(

View File

@@ -5,6 +5,4 @@ register = template.Library()
@register.filter
def ibanformat(value):
if not value:
return ''
return ' '.join(value[i:i + 4] for i in range(0, len(value), 4))

View File

@@ -320,7 +320,6 @@ class CSVCheckinList(CheckInListMixin, ListExporter):
headers.append(_('Company'))
headers.append(_('Voucher code'))
headers.append(_('Order date'))
yield headers
for op in qs:
@@ -373,7 +372,6 @@ class CSVCheckinList(CheckInListMixin, ListExporter):
row.append(ia.company)
row.append(op.voucher.code if op.voucher else "")
row.append(op.order.datetime.astimezone(self.event.timezone).strftime('%Y-%m-%d'))
yield row
def get_filename(self):

View File

@@ -36,18 +36,6 @@ class Paypal(BasePaymentProvider):
super().__init__(event)
self.settings = SettingsSandbox('payment', 'paypal', event)
@property
def test_mode_message(self):
if self.settings.connect_client_id and not self.settings.secret:
# in OAuth mode, sandbox mode needs to be set global
is_sandbox = self.settings.connect_endpoint == 'sandbox'
else:
is_sandbox = self.settings.get('endpoint') == 'sandbox'
if is_sandbox:
return _('The PayPal sandbox is being used, you can test without actually sending money but you will need a '
'PayPal sandbox user to log in.')
return None
@property
def settings_form_fields(self):
if self.settings.connect_client_id and not self.settings.secret:

View File

@@ -63,11 +63,6 @@
</p>
<h2>{% trans "3. Start scanning tickets" %}</h2>
<script type="text/javascript" src="{% static "pretixplugins/pretixdroid/pretixdroid.js" %}"></script>
{% if request.event.testmode %}
<div class="alert-info">
{% trans "Test mode orders will only be scanned if you scan online. If you scan in asynchronous mode, test mode orders won't be there." %}
</div>
{% endif %}
{% endif %}
{% endblock %}

View File

@@ -332,7 +332,6 @@ class ApiDownloadView(ApiView):
order__event=self.event,
order__status__in=[Order.STATUS_PAID] + ([Order.STATUS_PENDING] if self.config.list.include_pending else
[]),
order__testmode=False,
subevent=self.config.list.subevent
).annotate(
last_checked_in=Subquery(cqs)

View File

@@ -118,8 +118,7 @@ class ReportlabExportMixin:
canvas.setFont('OpenSans', 10)
canvas.drawString(15 * mm, self.pagesize[1] - 15 * mm,
"%s %s %s" % (self.event.organizer.name, self.event.name,
self.event.get_date_range_display()))
"%s %s" % (self.event.organizer.name, self.event.name))
canvas.drawRightString(self.pagesize[0] - 15 * mm, self.pagesize[1] - 15 * mm,
settings.PRETIX_INSTANCE_NAME)
canvas.setStrokeColorRGB(0, 0, 0)

View File

@@ -14,7 +14,6 @@ from django.template.loader import get_template
from django.urls import reverse
from django.utils.crypto import get_random_string
from django.utils.http import urlquote
from django.utils.safestring import mark_safe
from django.utils.timezone import now
from django.utils.translation import pgettext, ugettext, ugettext_lazy as _
from django_countries import countries
@@ -112,8 +111,6 @@ class StripeSettingsHolder(BasePaymentProvider):
('live', pgettext('stripe', 'Live')),
('test', pgettext('stripe', 'Testing')),
),
help_text=_('If your event is in test mode, we will always use Stripe\'s test API, '
'regardless of this setting.')
)),
]
else:
@@ -233,21 +230,6 @@ class StripeMethod(BasePaymentProvider):
super().__init__(event)
self.settings = SettingsSandbox('payment', 'stripe', event)
@property
def test_mode_message(self):
if self.settings.connect_client_id and not self.settings.secret_key:
is_testmode = True
else:
is_testmode = '_test_' in self.settings.secret_key
if is_testmode:
return mark_safe(
_('The Stripe plugin is operating in test mode. You can use one of <a {args}>many test '
'cards</a> to perform a transaction. No money will actually be transferred.').format(
args='href="https://stripe.com/docs/testing#cards" target="_blank"'
)
)
return None
@property
def settings_form_fields(self):
return {}
@@ -280,7 +262,7 @@ class StripeMethod(BasePaymentProvider):
@property
def api_kwargs(self):
if self.settings.connect_client_id and self.settings.connect_user_id:
if self.settings.get('endpoint', 'live') == 'live' and not self.event.testmode:
if self.settings.get('endpoint', 'live') == 'live':
kwargs = {
'api_key': self.settings.connect_secret_key,
'stripe_account': self.settings.connect_user_id

View File

@@ -34,14 +34,7 @@ def html_head_presale(sender, request=None, **kwargs):
url = resolve(request.path_info)
if provider.settings.get('_enabled', as_type=bool) and ("checkout" in url.url_name or "order.pay" in url.url_name):
template = get_template('pretixplugins/stripe/presale_head.html')
ctx = {
'event': sender,
'settings': provider.settings,
'testmode': (
(provider.settings.get('endpoint', 'live') == 'test' or sender.testmode)
and provider.settings.publishable_test_key
)
}
ctx = {'event': sender, 'settings': provider.settings}
return template.render(ctx)
else:
return ""

View File

@@ -8,7 +8,7 @@
{% compress css %}
<link type="text/css" rel="stylesheet" href="{% static "pretixplugins/stripe/pretix-stripe.css" %}">
{% endcompress %}
{% if testmode %}
{% if settings.endpoint == "test" and settings.publishable_test_key %}
<script type="text/plain" id="stripe_pubkey">{{ settings.publishable_test_key }}</script>
{% else %}
<script type="text/plain" id="stripe_pubkey">{{ settings.publishable_key }}</script>

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