Compare commits

..

2 Commits

Author SHA1 Message Date
Raphael Michel
dc298c4202 Bundle behaviour 2019-12-11 13:31:56 +01:00
Raphael Michel
8822d572f5 Allow to redeem a voucher for an existing cart 2019-12-11 12:58:20 +01:00
116 changed files with 728 additions and 3773 deletions

View File

@@ -90,11 +90,6 @@ Example::
proxy that actively removes and re-adds the header to make sure the correct client IP is the first value.
Defaults to ``off``.
``trust_x_forwarded_proto``
Specifies whether the ``X-Forwarded-Proto`` header can be trusted. Only set to ``on`` if you have a reverse
proxy that actively removes and re-adds the header to make sure the correct client IP is the first value.
Defaults to ``off``.
Locale settings
---------------

View File

@@ -125,8 +125,6 @@ Fill the configuration file ``/etc/pretix/pretix.cfg`` with the following conten
; DO NOT change the following value, it has to be set to the location of the
; directory *inside* the docker container
datadir=/data
trust_x_forwarded_for=on
trust_x_forwarded_proto=on
[database]
; Replace postgresql with mysql for MySQL

View File

@@ -85,8 +85,6 @@ Fill the configuration file ``/etc/pretix/pretix.cfg`` with the following conten
url=https://pretix.mydomain.com
currency=EUR
datadir=/var/pretix/data
trust_x_forwarded_for=on
trust_x_forwarded_proto=on
[database]
; For MySQL, replace with "mysql"

View File

@@ -194,7 +194,6 @@ Cart position endpoints
* ``subevent`` (optional)
* ``expires`` (optional)
* ``includes_tax`` (optional)
* ``sales_channel`` (optional)
* ``answers``
* ``question``

View File

@@ -42,7 +42,6 @@ seating_plan integer If reserved sea
plan. Otherwise ``null``.
seat_category_mapping object An object mapping categories of the seating plan
(strings) to items in the event (integers or ``null``).
timezone string Event timezone name
===================================== ========================== =======================================================
@@ -75,10 +74,6 @@ timezone string Event timezone
The attributes ``geo_lat`` and ``geo_lon`` have been added.
.. versionchanged:: 3.4
The attribute ``timezone`` has been added.
Endpoints
---------
@@ -132,7 +127,6 @@ Endpoints
"meta_data": {},
"seating_plan": null,
"seat_category_mapping": {},
"timezone": "Europe/Berlin",
"plugins": [
"pretix.plugins.banktransfer"
"pretix.plugins.stripe"
@@ -203,7 +197,6 @@ Endpoints
"seating_plan": null,
"seat_category_mapping": {},
"meta_data": {},
"timezone": "Europe/Berlin",
"plugins": [
"pretix.plugins.banktransfer"
"pretix.plugins.stripe"
@@ -255,7 +248,6 @@ Endpoints
"geo_lon": null,
"has_subevents": false,
"meta_data": {},
"timezone": "Europe/Berlin",
"plugins": [
"pretix.plugins.stripe",
"pretix.plugins.paypal"
@@ -289,7 +281,6 @@ Endpoints
"seat_category_mapping": {},
"has_subevents": false,
"meta_data": {},
"timezone": "Europe/Berlin",
"plugins": [
"pretix.plugins.stripe",
"pretix.plugins.paypal"
@@ -343,7 +334,6 @@ Endpoints
"seat_category_mapping": {},
"has_subevents": false,
"meta_data": {},
"timezone": "Europe/Berlin",
"plugins": [
"pretix.plugins.stripe",
"pretix.plugins.paypal"
@@ -377,7 +367,6 @@ Endpoints
"seating_plan": null,
"seat_category_mapping": {},
"meta_data": {},
"timezone": "Europe/Berlin",
"plugins": [
"pretix.plugins.stripe",
"pretix.plugins.paypal"
@@ -443,7 +432,6 @@ Endpoints
"seating_plan": null,
"seat_category_mapping": {},
"meta_data": {},
"timezone": "Europe/Berlin",
"plugins": [
"pretix.plugins.banktransfer",
"pretix.plugins.stripe",

View File

@@ -221,14 +221,9 @@ Endpoints
"value": "15.37"
}
.. versionchanged:: 3.5
This endpoint now returns status code ``409`` if the transaction would lead to a negative gift card value.
:param organizer: The ``slug`` field of the organizer to modify
:param id: The ``id`` field of the gift card to modify
:statuscode 200: no error
:statuscode 400: The gift card could not be modified due to invalid submitted data
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to change this resource.
:statuscode 409: There is not sufficient credit on the gift card.

View File

@@ -61,10 +61,9 @@ invoice_address object Invoice address
└ vat_id_validated string ``true``, if the VAT ID has been validated against the
EU VAT service and validation was successful. This only
happens in rare cases.
positions list of objects List of order positions (see below). By default, only
non-canceled positions are included.
fees list of objects List of fees included in the order total. By default, only
non-canceled fees are included.
positions list of objects List of non-canceled order positions (see below)
fees list of objects List of non-canceled fees included in the order total
(i.e. payment fees)
├ fee_type string Type of fee (currently ``payment``, ``passbook``,
``other``)
├ value money (string) Fee amount
@@ -73,8 +72,7 @@ fees list of objects List of fees in
can be empty
├ tax_rate decimal (string) VAT rate applied for this fee
├ tax_value money (string) VAT included in this fee
tax_rule integer The ID of the used tax rule (or ``null``)
└ canceled boolean Whether or not this fee has been canceled.
tax_rule integer The ID of the used tax rule (or ``null``)
downloads list of objects List of ticket download options for order-wise ticket
downloading. This might be a multi-page PDF or a ZIP
file of tickets for outputs that do not support
@@ -147,10 +145,6 @@ last_modified datetime Last modificati
The ``invoice_address.state`` and ``url`` attributes have been added. When creating orders through the API,
vouchers are now supported and many fields are now optional.
.. versionchanged:: 3.5
The ``order.fees.canceled`` attribute has been added.
.. _order-position-resource:
@@ -165,8 +159,6 @@ Field Type Description
id integer Internal ID of the order position
order string Order code of the order the position belongs to
positionid integer Number of the position within the order
canceled boolean Whether or not this position has been canceled. Note that
by default, only non-canceled positions are shown.
item integer ID of the purchased item
variation integer ID of the purchased variation (or ``null``)
price money (string) Price of this position
@@ -232,10 +224,6 @@ pdf_data object Data object req
The ``url`` of a ticket ``download`` can now also return a ``text/uri-list`` instead of a file. See
:ref:`order-position-ticket-download` for details.
.. versionchanged:: 3.5
The attribute ``canceled`` has been added.
.. _order-payment-resource:
Order payment resource
@@ -302,10 +290,6 @@ List of all orders
Filtering for emails or order codes is now case-insensitive.
.. versionchanged:: 3.5
The ``include_canceled_positions`` and ``include_canceled_fees`` query parameters have been added.
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/orders/
Returns a list of all orders within a given event.
@@ -371,7 +355,6 @@ List of all orders
"id": 23442,
"order": "ABC12",
"positionid": 1,
"canceled": false,
"item": 1345,
"variation": null,
"price": "23.00",
@@ -444,9 +427,6 @@ List of all orders
: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 include_canceled_positions: If set to ``true``, the output will contain canceled order positions. Note that this
only affects position-level cancellations, not fully-canceled orders.
:query include_canceled_fees: If set to ``true``, the output will contain canceled order fees.
: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
@@ -464,10 +444,6 @@ List of all orders
Fetching individual orders
--------------------------
.. versionchanged:: 3.5
The ``include_canceled_positions`` and ``include_canceled_fees`` query parameters have been added.
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/orders/(code)/
Returns information on one order, identified by its order code.
@@ -527,7 +503,6 @@ Fetching individual orders
"id": 23442,
"order": "ABC12",
"positionid": 1,
"canceled": false,
"item": 1345,
"variation": null,
"price": "23.00",
@@ -593,9 +568,6 @@ Fetching individual orders
: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 fetch
:query include_canceled_positions: If set to ``true``, the output will contain canceled order positions. Note that this
only affects position-level cancellations, not fully-canceled orders.
:query include_canceled_fees: If set to ``true``, the output will contain canceled order fees.
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
@@ -1341,9 +1313,8 @@ List of all order positions
The value ``auto_checked_in`` has been added to the ``checkins``-attribute.
.. versionchanged:: 3.5
The ``include_canceled_positions`` and ``include_canceled_fees`` query parameters have been added.
.. note:: Individually canceled order positions are currently not visible via the API at all.
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/orderpositions/
@@ -1374,7 +1345,6 @@ List of all order positions
"id": 23442,
"order": "ABC12",
"positionid": 1,
"canceled": false,
"item": 1345,
"variation": null,
"price": "23.00",
@@ -1444,8 +1414,6 @@ List of all order positions
comma-separated IDs.
:query string voucher: Only return positions with a specific voucher.
:query string voucher__code: Only return positions with a specific voucher code.
:query include_canceled_positions: If set to ``true``, the output will contain canceled order positions. Note that this
only affects position-level cancellations, not fully-canceled orders.
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch
:statuscode 200: no error
@@ -1479,7 +1447,6 @@ Fetching individual positions
"id": 23442,
"order": "ABC12",
"positionid": 1,
"canceled": false,
"item": 1345,
"variation": null,
"price": "23.00",
@@ -1524,7 +1491,6 @@ Fetching individual positions
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch
:param id: The ``id`` field of the order position to fetch
:query include_canceled_positions: If set to ``true``, canceled positions may be returned (otherwise, they return 404).
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.

View File

@@ -18,7 +18,6 @@ Field Type Description
===================================== ========================== =======================================================
id integer Internal ID of the question
question multi-lingual string The field label shown to the customer
help_text multi-lingual string The help text shown to the customer
type string The expected type of answer. Valid options:
* ``N`` number
@@ -88,10 +87,6 @@ dependency_value string An old version
The attribute ``print_on_invoice`` has been added.
.. versionchanged:: 3.5
The attribute ``help_text`` has been added.
Endpoints
---------
@@ -128,7 +123,6 @@ Endpoints
{
"id": 1,
"question": {"en": "T-Shirt size"},
"help_text": {"en": "Choose your preferred t-shirt-size"},
"type": "C",
"required": false,
"items": [1, 2],
@@ -199,7 +193,6 @@ Endpoints
{
"id": 1,
"question": {"en": "T-Shirt size"},
"help_text": {"en": "Choose your preferred t-shirt-size"},
"type": "C",
"required": false,
"items": [1, 2],
@@ -255,7 +248,6 @@ Endpoints
{
"question": {"en": "T-Shirt size"},
"help_text": {"en": "Choose your preferred t-shirt-size"},
"type": "C",
"required": false,
"items": [1, 2],
@@ -290,7 +282,6 @@ Endpoints
{
"id": 1,
"question": {"en": "T-Shirt size"},
"help_text": {"en": "Choose your preferred t-shirt-size"},
"type": "C",
"required": false,
"items": [1, 2],
@@ -365,7 +356,6 @@ Endpoints
{
"id": 1,
"question": {"en": "T-Shirt size"},
"help_text": {"en": "Choose your preferred t-shirt-size"},
"type": "C",
"required": false,
"items": [1, 2],

View File

@@ -26,7 +26,7 @@ Frontend
--------
.. automodule:: pretix.presale.signals
:members: html_head, html_footer, footer_link, front_page_top, front_page_bottom, front_page_bottom_widget, fee_calculation_for_cart, contact_form_fields, question_form_fields, checkout_confirm_messages, checkout_confirm_page_content, checkout_all_optional, html_page_header, sass_preamble, sass_postamble, render_seating_plan, checkout_flow_steps, position_info, item_description
:members: html_head, html_footer, footer_link, front_page_top, front_page_bottom, fee_calculation_for_cart, contact_form_fields, question_form_fields, checkout_confirm_messages, checkout_confirm_page_content, checkout_all_optional, html_page_header, sass_preamble, sass_postamble, render_seating_plan, checkout_flow_steps, position_info, item_description
.. automodule:: pretix.presale.signals

View File

@@ -1,112 +0,0 @@
.. highlight:: python
:linenothreshold: 5
.. _`importcol`:
Extending the order import process
==================================
It's possible through the backend to import orders into pretix, for example from a legacy ticketing system. If your
plugins defines additional data structures around orders, it might be useful to make it possible to import them as well.
Import process
--------------
Here's a short description of pretix' import process to show you where the system will need to interact with your plugin.
You can find more detailed descriptions of the attributes and methods further below.
1. The user uploads a CSV file. The system tries to parse the CSV file and understand its column headers.
2. A preview of the file is shown to the user and the user is asked to assign the various different input parameters to
columns of the file or static values. For example, the user either needs to manually select a product or specify a
column that contains a product. For this purpose, a select field is rendered for every possible input column,
allowing the user to choose between a default/empty value (defined by your ``default_value``/``default_label``)
attributes, the columns of the uploaded file, or a static value (defined by your ``static_choices`` method).
3. The user submits its assignment and the system uses the ``resolve`` method of all columns to get the raw value for
all columns.
4. The system uses the ``clean`` method of all columns to verify that all input fields are valid and transformed to the
correct data type.
5. The system prepares internal model objects (``Order`` etc) and uses the ``assign`` method of all columns to assign
these objects with actual values.
6. The system saves all of these model objects to the database in a database transaction. Plugins can create additional
objects in this stage through their ``save`` method.
Column registration
-------------------
The import API does not make a lot of usage from signals, however, it
does use a signal to get a list of all available import columns. Your plugin
should listen for this signal and return the subclass of ``pretix.base.orderimport.ImportColumn``
that we'll provide in this plugin:
.. sourcecode:: python
from django.dispatch import receiver
from pretix.base.signals import order_import_columns
@receiver(order_import_columns, dispatch_uid="custom_columns")
def register_column(sender, **kwargs):
return [
EmailColumn(sender),
]
The column class API
--------------------
.. class:: pretix.base.orderimport.ImportColumn
The central object of each import extension is the subclass of ``ImportColumn``.
.. py:attribute:: ImportColumn.event
The default constructor sets this property to the event we are currently
working for.
.. autoattribute:: identifier
This is an abstract attribute, you **must** override this!
.. autoattribute:: verbose_name
This is an abstract attribute, you **must** override this!
.. autoattribute:: default_value
.. autoattribute:: default_label
.. autoattribute:: initial
.. automethod:: static_choices
.. automethod:: resolve
.. automethod:: clean
.. automethod:: assign
.. automethod:: save
Example
-------
For example, the import column responsible for assigning email addresses looks like this:
.. sourcecode:: python
class EmailColumn(ImportColumn):
identifier = 'email'
verbose_name = _('E-mail address')
def clean(self, value, previous_values):
if value:
EmailValidator()(value)
return value
def assign(self, value, order, position, invoice_address, **kwargs):
order.email = value

View File

@@ -15,7 +15,6 @@ Contents:
placeholder
invoice
shredder
import
customview
auth
general

View File

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

View File

@@ -30,12 +30,11 @@ class CartPositionCreateSerializer(I18nAwareModelSerializer):
expires = serializers.DateTimeField(required=False)
attendee_name = serializers.CharField(required=False, allow_null=True)
seat = serializers.CharField(required=False, allow_null=True)
sales_channel = serializers.CharField(required=False, default='sales_channel')
class Meta:
model = CartPosition
fields = ('cart_id', 'item', 'variation', 'price', 'attendee_name', 'attendee_name_parts', 'attendee_email',
'subevent', 'expires', 'includes_tax', 'answers', 'seat', 'sales_channel')
'subevent', 'expires', 'includes_tax', 'answers', 'seat')
def create(self, validated_data):
answers_data = validated_data.pop('answers')
@@ -87,12 +86,11 @@ class CartPositionCreateSerializer(I18nAwareModelSerializer):
raise ValidationError('The specified seat ID is not unique.')
else:
validated_data['seat'] = seat
if not seat.is_available(sales_channel=validated_data.get('sales_channel', 'web')):
if not seat.is_available():
raise ValidationError(ugettext_lazy('The selected seat "{seat}" is not available.').format(seat=seat.name))
elif seated:
raise ValidationError('The specified product requires to choose a seat.')
validated_data.pop('sales_channel')
cp = CartPosition.objects.create(event=self.context['event'], **validated_data)
for answ_data in answers_data:

View File

@@ -4,8 +4,7 @@ from django.db import transaction
from django.utils.functional import cached_property
from django.utils.translation import ugettext as _
from django_countries.serializers import CountryFieldMixin
from pytz import common_timezones
from rest_framework.fields import ChoiceField, Field
from rest_framework.fields import Field
from rest_framework.relations import SlugRelatedField
from pretix.api.serializers.i18n import I18nAwareModelSerializer
@@ -62,27 +61,17 @@ class PluginsField(Field):
}
class TimeZoneField(ChoiceField):
def get_attribute(self, instance):
return instance.cache.get_or_set(
'timezone_name',
lambda: instance.settings.timezone,
3600
)
class EventSerializer(I18nAwareModelSerializer):
meta_data = MetaDataField(required=False, source='*')
plugins = PluginsField(required=False, source='*')
seat_category_mapping = SeatCategoryMappingField(source='*', required=False)
timezone = TimeZoneField(required=False, choices=[(a, a) for a in common_timezones])
class Meta:
model = Event
fields = ('name', 'slug', 'live', 'testmode', 'currency', 'date_from',
'date_to', 'date_admission', 'is_public', 'presale_start',
'presale_end', 'location', 'geo_lat', 'geo_lon', 'has_subevents', 'meta_data', 'seating_plan',
'plugins', 'seat_category_mapping', 'timezone')
'plugins', 'seat_category_mapping')
def validate(self, data):
data = super().validate(data)
@@ -167,12 +156,8 @@ class EventSerializer(I18nAwareModelSerializer):
meta_data = validated_data.pop('meta_data', None)
validated_data.pop('seat_category_mapping', None)
plugins = validated_data.pop('plugins', settings.PRETIX_PLUGINS_DEFAULT.split(','))
tz = validated_data.pop('timezone', None)
event = super().create(validated_data)
if tz:
event.settings.timezone = tz
# Meta data
if meta_data is not None:
for key, value in meta_data.items():
@@ -197,12 +182,8 @@ class EventSerializer(I18nAwareModelSerializer):
meta_data = validated_data.pop('meta_data', None)
plugins = validated_data.pop('plugins', None)
seat_category_mapping = validated_data.pop('seat_category_mapping', None)
tz = validated_data.pop('timezone', None)
event = super().update(instance, validated_data)
if tz:
event.settings.timezone = tz
# Meta data
if meta_data is not None:
current = {mv.property: mv for mv in event.meta_values.select_related('property')}
@@ -259,7 +240,6 @@ class CloneEventSerializer(EventSerializer):
is_public = validated_data.pop('is_public', None)
testmode = validated_data.pop('testmode', None)
has_subevents = validated_data.pop('has_subevents', None)
tz = validated_data.pop('timezone', None)
new_event = super().create(validated_data)
event = Event.objects.filter(slug=self.context['event'], organizer=self.context['organizer'].pk).first()
@@ -274,8 +254,6 @@ class CloneEventSerializer(EventSerializer):
if has_subevents is not None:
new_event.has_subevents = has_subevents
new_event.save()
if tz:
new_event.settings.timezone = tz
return new_event

View File

@@ -227,7 +227,7 @@ class QuestionSerializer(I18nAwareModelSerializer):
model = Question
fields = ('id', 'question', 'type', 'required', 'items', 'options', 'position',
'ask_during_checkin', 'identifier', 'dependency_question', 'dependency_values',
'hidden', 'dependency_value', 'print_on_invoice', 'help_text')
'hidden', 'dependency_value', 'print_on_invoice')
def validate_identifier(self, value):
Question._clean_identifier(self.context['event'], value, self.instance)

View File

@@ -204,7 +204,7 @@ class OrderPositionSerializer(I18nAwareModelSerializer):
model = OrderPosition
fields = ('id', 'order', 'positionid', 'item', 'variation', 'price', 'attendee_name', 'attendee_name_parts',
'attendee_email', 'voucher', 'tax_rate', 'tax_value', 'secret', 'addon_to', 'subevent', 'checkins',
'downloads', 'answers', 'tax_rule', 'pseudonymization_id', 'pdf_data', 'seat', 'canceled')
'downloads', 'answers', 'tax_rule', 'pseudonymization_id', 'pdf_data', 'seat')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -284,7 +284,7 @@ class OrderPaymentDateField(serializers.DateField):
class OrderFeeSerializer(I18nAwareModelSerializer):
class Meta:
model = OrderFee
fields = ('fee_type', 'value', 'description', 'internal_type', 'tax_rate', 'tax_value', 'tax_rule', 'canceled')
fields = ('fee_type', 'value', 'description', 'internal_type', 'tax_rate', 'tax_value', 'tax_rule')
class PaymentURLField(serializers.URLField):
@@ -720,7 +720,6 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
consume_carts = validated_data.pop('consume_carts', [])
delete_cps = []
quota_avail_cache = {}
v_budget = {}
voucher_usage = Counter()
if consume_carts:
for cp in CartPosition.objects.filter(
@@ -743,14 +742,9 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
errs = [{} for p in positions_data]
for i, pos_data in enumerate(positions_data):
if pos_data.get('voucher'):
v = pos_data['voucher']
if pos_data.get('addon_to'):
errs[i]['voucher'] = ['Vouchers are currently not supported for add-on products.']
continue
if not v.applies_to(pos_data['item'], pos_data.get('variation')):
errs[i]['voucher'] = [error_messages['voucher_invalid_item']]
continue
@@ -774,44 +768,6 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
'The voucher has already been used the maximum number of times.'
]
if v.budget is not None:
price = pos_data.get('price')
if price is None:
price = get_price(
item=pos_data.get('item'),
variation=pos_data.get('variation'),
voucher=v,
custom_price=None,
subevent=pos_data.get('subevent'),
addon_to=pos_data.get('addon_to'),
invoice_address=ia,
).gross
pbv = get_price(
item=pos_data['item'],
variation=pos_data.get('variation'),
voucher=None,
custom_price=None,
subevent=pos_data.get('subevent'),
addon_to=pos_data.get('addon_to'),
invoice_address=ia,
)
if v not in v_budget:
v_budget[v] = v.budget - v.budget_used()
disc = pbv.gross - price
if disc > v_budget[v]:
new_disc = v_budget[v]
v_budget[v] -= new_disc
if new_disc == Decimal('0.00') or pos_data.get('price') is not None:
errs[i]['voucher'] = [
'The voucher has a remaining budget of {}, therefore a discount of {} can not be '
'given.'.format(v_budget[v] + new_disc, disc)
]
continue
pos_data['price'] = price + (disc - new_disc)
else:
v_budget[v] -= disc
seated = pos_data.get('item').seat_category_mappings.filter(subevent=pos_data.get('subevent')).exists()
if pos_data.get('seat'):
if not seated:
@@ -822,7 +778,7 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
errs[i]['seat'] = ['The specified seat does not exist.']
else:
pos_data['seat'] = seat
if (seat not in free_seats and not seat.is_available(sales_channel=validated_data.get('sales_channel', 'web'))) or seat in seats_seen:
if (seat not in free_seats and not seat.is_available()) or seat in seats_seen:
errs[i]['seat'] = [ugettext_lazy('The selected seat "{seat}" is not available.').format(seat=seat.name)]
seats_seen.add(seat)
elif seated:
@@ -900,17 +856,6 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
pos.tax_rule = pos.item.tax_rule
else:
pos._calculate_tax()
pos.price_before_voucher = get_price(
item=pos.item,
variation=pos.variation,
voucher=None,
custom_price=None,
subevent=pos.subevent,
addon_to=pos.addon_to,
invoice_address=ia,
).gross
if pos.voucher:
Voucher.objects.filter(pk=pos.voucher.pk).update(redeemed=F('redeemed') + 1)
pos.save()

View File

@@ -1,5 +1,3 @@
from decimal import Decimal
from django.db.models import Q
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
@@ -28,7 +26,7 @@ class SeatingPlanSerializer(I18nAwareModelSerializer):
class GiftCardSerializer(I18nAwareModelSerializer):
value = serializers.DecimalField(max_digits=10, decimal_places=2, min_value=Decimal('0.00'))
value = serializers.DecimalField(max_digits=10, decimal_places=2)
def validate(self, data):
data = super().validate(data)

View File

@@ -30,8 +30,8 @@ from pretix.api.serializers.order import (
from pretix.base.i18n import language
from pretix.base.models import (
CachedCombinedTicket, CachedTicket, Device, Event, Invoice, InvoiceAddress,
Order, OrderFee, OrderPayment, OrderPosition, OrderRefund, Quota,
TeamAPIToken, generate_position_secret, generate_secret,
Order, OrderPayment, OrderPosition, OrderRefund, Quota, TeamAPIToken,
generate_position_secret, generate_secret,
)
from pretix.base.payment import PaymentException
from pretix.base.services import tickets
@@ -82,29 +82,20 @@ class OrderViewSet(viewsets.ModelViewSet):
return ctx
def get_queryset(self):
if self.request.query_params.get('include_canceled_fees', 'false') == 'true':
fqs = OrderFee.all
else:
fqs = OrderFee.objects
qs = self.request.event.orders.prefetch_related(
Prefetch('fees', queryset=fqs.all()),
'payments', 'refunds', 'refunds__payment'
'fees', 'payments', 'refunds', 'refunds__payment'
).select_related(
'invoice_address'
)
if self.request.query_params.get('include_canceled_positions', 'false') == 'true':
opq = OrderPosition.all
else:
opq = OrderPosition.objects
if self.request.query_params.get('pdf_data', 'false') == 'true':
qs = qs.prefetch_related(
Prefetch(
'positions',
opq.all().prefetch_related(
OrderPosition.objects.all().prefetch_related(
'checkins', 'item', 'variation', 'answers', 'answers__options', 'answers__question',
'item__category', 'addon_to', 'seat',
Prefetch('addons', opq.select_related('item', 'variation', 'seat'))
Prefetch('addons', OrderPosition.objects.select_related('item', 'variation', 'seat'))
)
)
)
@@ -112,7 +103,7 @@ class OrderViewSet(viewsets.ModelViewSet):
qs = qs.prefetch_related(
Prefetch(
'positions',
opq.all().prefetch_related(
OrderPosition.objects.all().prefetch_related(
'checkins', 'item', 'variation', 'answers', 'answers__options', 'answers__question', 'seat',
)
)
@@ -663,16 +654,11 @@ class OrderPositionViewSet(mixins.DestroyModelMixin, viewsets.ReadOnlyModelViewS
}
def get_queryset(self):
if self.request.query_params.get('include_canceled_positions', 'false') == 'true':
qs = OrderPosition.all
else:
qs = OrderPosition.objects
qs = qs.filter(order__event=self.request.event)
qs = OrderPosition.objects.filter(order__event=self.request.event)
if self.request.query_params.get('pdf_data', 'false') == 'true':
qs = qs.prefetch_related(
'checkins', 'answers', 'answers__options', 'answers__question',
Prefetch('addons', qs.select_related('item', 'variation')),
Prefetch('addons', OrderPosition.objects.select_related('item', 'variation')),
Prefetch('order', Order.objects.select_related('invoice_address').prefetch_related(
Prefetch(
'event',
@@ -680,7 +666,7 @@ class OrderPositionViewSet(mixins.DestroyModelMixin, viewsets.ReadOnlyModelViewS
),
Prefetch(
'positions',
qs.prefetch_related(
OrderPosition.objects.prefetch_related(
'checkins', 'item', 'variation', 'answers', 'answers__options', 'answers__question',
)
)
@@ -690,7 +676,7 @@ class OrderPositionViewSet(mixins.DestroyModelMixin, viewsets.ReadOnlyModelViewS
)
else:
qs = qs.prefetch_related(
'checkins', 'answers', 'answers__options', 'answers__question',
'checkins', 'answers', 'answers__options', 'answers__question'
).select_related(
'item', 'order', 'order__event', 'order__event__organizer', 'seat'
)

View File

@@ -1,5 +1,3 @@
from decimal import Decimal
from django.db import transaction
from rest_framework import filters, serializers, status, viewsets
from rest_framework.decorators import action
@@ -138,10 +136,6 @@ class GiftCardViewSet(viewsets.ModelViewSet):
value = serializers.DecimalField(max_digits=10, decimal_places=2).to_internal_value(
request.data.get('value')
)
if gc.value + value < Decimal('0.00'):
return Response({
'value': ['The gift card does not have sufficient credit for this operation.']
}, status=status.HTTP_409_CONFLICT)
gc.transactions.create(value=value)
gc.log_action(
'pretix.giftcards.transaction.manual',

View File

@@ -13,7 +13,7 @@ class PretixBaseConfig(AppConfig):
from . import invoice # NOQA
from . import notifications # NOQA
from . import email # NOQA
from .services import auth, checkin, export, mail, tickets, cart, orderimport, orders, invoices, cleanup, update_check, quotas, notifications, vouchers # NOQA
from .services import auth, checkin, export, mail, tickets, cart, orders, invoices, cleanup, update_check, quotas, notifications, vouchers # NOQA
try:
from .celery_app import app as celery_app # NOQA

View File

@@ -9,7 +9,7 @@ from django.core.mail.backends.smtp import EmailBackend
from django.dispatch import receiver
from django.template.loader import get_template
from django.utils.timezone import now
from django.utils.translation import get_language, ugettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from inlinestyler.utils import inline_css
from pretix.base.i18n import LazyCurrencyNumber, LazyDate, LazyNumber
@@ -112,8 +112,7 @@ class TemplateBasedMailRenderer(BaseHTMLMailRenderer):
'site_url': settings.SITE_URL,
'body': body_md,
'subject': str(subject),
'color': '#8E44B3',
'rtl': get_language() in settings.LANGUAGES_RTL
'color': '#8E44B3'
}
if self.event:
htmlctx['event'] = self.event

View File

@@ -128,7 +128,7 @@ class ListExporter(BaseExporter):
def _render_xlsx(self, form_data, output_file=None):
wb = Workbook()
ws = wb.active
ws = wb.get_active_sheet()
try:
ws.title = str(self.verbose_name)
except:
@@ -207,7 +207,7 @@ class MultiSheetListExporter(ListExporter):
def _render_xlsx(self, form_data, output_file=None):
wb = Workbook()
ws = wb.active
ws = wb.get_active_sheet()
wb.remove(ws)
for s, l in self.sheets:
ws = wb.create_sheet(str(l))

View File

@@ -32,8 +32,7 @@ class LoginForm(forms.Form):
for k, f in backend.login_form_fields.items():
self.fields[k] = f
# Authentication backends which use urls cannot have long sessions.
if not settings.PRETIX_LONG_SESSIONS or backend.url:
if not settings.PRETIX_LONG_SESSIONS:
del self.fields['keep_logged_in']
else:
self.fields.move_to_end('keep_logged_in')
@@ -198,7 +197,6 @@ class ReauthForm(forms.Form):
self.request = request
self.user = user
self.backend = backend
self.backend.url = backend.authentication_url(self.request)
super().__init__(*args, **kwargs)
for k, f in backend.login_form_fields.items():
self.fields[k] = f

View File

@@ -1,30 +0,0 @@
# Generated by Django 2.2.7 on 2019-12-15 15:22
from decimal import Decimal
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0141_seat_sorting_rank'),
]
operations = [
migrations.AddField(
model_name='cartposition',
name='price_before_voucher',
field=models.DecimalField(decimal_places=2, max_digits=10, null=True),
),
migrations.AddField(
model_name='orderposition',
name='price_before_voucher',
field=models.DecimalField(decimal_places=2, max_digits=10, null=True),
),
migrations.AddField(
model_name='voucher',
name='budget',
field=models.DecimalField(decimal_places=2, max_digits=10, null=True),
),
]

View File

@@ -385,7 +385,7 @@ class Event(EventMixin, LoggedModel):
if img:
return urljoin(build_absolute_uri(self, 'presale:event.index'), img)
def free_seats(self, ignore_voucher=None, sales_channel='web'):
def free_seats(self, ignore_voucher=None):
from .orders import CartPosition, Order, OrderPosition
from .vouchers import Voucher
vqs = Voucher.objects.filter(
@@ -397,7 +397,7 @@ class Event(EventMixin, LoggedModel):
)
if ignore_voucher:
vqs = vqs.exclude(pk=ignore_voucher.pk)
qs = self.seats.annotate(
return self.seats.annotate(
has_order=Exists(
OrderPosition.objects.filter(
order__event=self,
@@ -415,10 +415,7 @@ class Event(EventMixin, LoggedModel):
has_voucher=Exists(
vqs
)
).filter(has_order=False, has_cart=False, has_voucher=False)
if sales_channel not in self.settings.seating_allow_blocked_seats_for_channel:
qs = qs.filter(blocked=False)
return qs
).filter(has_order=False, has_cart=False, has_voucher=False, blocked=False)
@property
def presale_has_ended(self):
@@ -522,7 +519,6 @@ class Event(EventMixin, LoggedModel):
self.is_public = other.is_public
self.testmode = other.testmode
self.save()
self.log_action('pretix.object.cloned', data={'source': other.slug, 'source_id': other.pk})
tax_map = {}
for t in other.tax_rules.all():
@@ -530,7 +526,6 @@ class Event(EventMixin, LoggedModel):
t.pk = None
t.event = self
t.save()
t.log_action('pretix.object.cloned')
category_map = {}
for c in ItemCategory.objects.filter(event=other):
@@ -538,7 +533,6 @@ class Event(EventMixin, LoggedModel):
c.pk = None
c.event = self
c.save()
c.log_action('pretix.object.cloned')
item_map = {}
variation_map = {}
@@ -554,7 +548,6 @@ class Event(EventMixin, LoggedModel):
if i.tax_rule_id:
i.tax_rule = tax_map[i.tax_rule_id]
i.save()
i.log_action('pretix.object.cloned')
for v in vars:
variation_map[v.pk] = v
v.pk = None
@@ -579,7 +572,6 @@ class Event(EventMixin, LoggedModel):
q.cached_availability_time = None
q.closed = False
q.save()
q.log_action('pretix.object.cloned')
for i in items:
if i.pk in item_map:
q.items.add(item_map[i.pk])
@@ -595,7 +587,6 @@ class Event(EventMixin, LoggedModel):
q.pk = None
q.event = self
q.save()
q.log_action('pretix.object.cloned')
for i in items:
q.items.add(item_map[i.pk])
@@ -613,7 +604,6 @@ class Event(EventMixin, LoggedModel):
cl.pk = None
cl.event = self
cl.save()
cl.log_action('pretix.object.cloned')
for i in items:
cl.limit_products.add(item_map[i.pk])
@@ -1016,7 +1006,7 @@ class SubEvent(EventMixin, LoggedModel):
def __str__(self):
return '{} - {}'.format(self.name, self.get_date_range_display())
def free_seats(self, ignore_voucher=None, sales_channel='web'):
def free_seats(self, ignore_voucher=None):
from .orders import CartPosition, Order, OrderPosition
from .vouchers import Voucher
vqs = Voucher.objects.filter(
@@ -1029,7 +1019,7 @@ class SubEvent(EventMixin, LoggedModel):
)
if ignore_voucher:
vqs = vqs.exclude(pk=ignore_voucher.pk)
qs = self.seats.annotate(
return self.seats.annotate(
has_order=Exists(
OrderPosition.objects.filter(
order__event_id=self.event_id,
@@ -1049,10 +1039,7 @@ class SubEvent(EventMixin, LoggedModel):
has_voucher=Exists(
vqs
)
).filter(has_order=False, has_cart=False, has_voucher=False)
if sales_channel not in self.settings.seating_allow_blocked_seats_for_channel:
qs = qs.filter(blocked=False)
return qs
).filter(has_order=False, has_cart=False, blocked=False, has_voucher=False)
@cached_property
def settings(self):

View File

@@ -191,9 +191,6 @@ class Invoice(models.Model):
self.prefix = self.event.settings.invoice_numbers_prefix or (self.event.slug.upper() + '-')
if self.is_cancellation:
self.prefix = self.event.settings.invoice_numbers_prefix_cancellations or self.prefix
if '%' in self.prefix:
self.prefix = self.date.strftime(self.prefix)
if not self.invoice_no:
if self.order.testmode:
self.prefix += 'TEST-'

View File

@@ -1106,25 +1106,17 @@ class Question(LoggedModel):
if self.type == Question.TYPE_CHOICE:
try:
return self.options.get(Q(pk=answer) | Q(identifier=answer))
return self.options.get(pk=answer)
except:
raise ValidationError(_('Invalid option selected.'))
elif self.type == Question.TYPE_CHOICE_MULTIPLE:
if isinstance(answer, str):
l_ = list(self.options.filter(
Q(pk__in=[a for a in answer.split(",") if a.isdigit()]) |
Q(identifier__in=answer.split(","))
))
llen = len(answer.split(','))
else:
l_ = list(self.options.filter(
Q(pk__in=[a for a in answer if isinstance(a, int) or a.isdigit()]) |
Q(identifier__in=answer)
))
llen = len(answer)
if len(l_) != llen:
try:
if isinstance(answer, str):
return list(self.options.filter(pk__in=answer.split(",")))
else:
return list(self.options.filter(pk__in=answer))
except:
raise ValidationError(_('Invalid option selected.'))
return l_
elif self.type == Question.TYPE_BOOLEAN:
return answer in ('true', 'True', True)
elif self.type == Question.TYPE_NUMBER:

View File

@@ -687,12 +687,10 @@ class Order(LockModel, LoggedModel):
error_messages = {
'unavailable': _('The ordered product "{item}" is no longer available.'),
'seat_unavailable': _('The seat "{seat}" is no longer available.'),
'voucher_budget': _('The voucher "{voucher}" no longer has sufficient budget.'),
}
now_dt = now_dt or now()
positions = self.positions.all().select_related('item', 'variation', 'seat', 'voucher')
positions = self.positions.all().select_related('item', 'variation', 'seat')
quota_cache = {}
v_budget = {}
try:
for i, op in enumerate(positions):
if op.seat:
@@ -701,16 +699,6 @@ class Order(LockModel, LoggedModel):
if force:
continue
if op.voucher and op.voucher.budget is not None and op.price_before_voucher is not None:
if op.voucher not in v_budget:
v_budget[op.voucher] = op.voucher.budget - op.voucher.budget_used()
disc = op.price_before_voucher - op.price
if disc > v_budget[op.voucher]:
raise Quota.QuotaExceededException(error_messages['voucher_budget'].format(
voucher=op.voucher.code
))
v_budget[op.voucher] -= disc
quotas = list(op.quotas)
if len(quotas) == 0:
raise Quota.QuotaExceededException(error_messages['unavailable'].format(
@@ -1003,9 +991,6 @@ class AbstractPosition(models.Model):
verbose_name=_("Variation"),
on_delete=models.PROTECT
)
price_before_voucher = models.DecimalField(
decimal_places=2, max_digits=10, null=True,
)
price = models.DecimalField(
decimal_places=2, max_digits=10,
verbose_name=_("Price")
@@ -2048,7 +2033,7 @@ class InvoiceAddress(models.Model):
internal_reference = models.TextField(
verbose_name=_('Internal reference'),
help_text=_('This reference will be printed on your invoice for your convenience.'),
blank=True,
blank=True
)
beneficiary = models.TextField(
verbose_name=_('Beneficiary'),

View File

@@ -135,15 +135,12 @@ class Seat(models.Model):
return self.name
return ', '.join(parts)
def is_available(self, ignore_cart=None, ignore_orderpos=None, ignore_voucher_id=None, sales_channel='web'):
def is_available(self, ignore_cart=None, ignore_orderpos=None, ignore_voucher_id=None):
from .orders import Order
if self.blocked and sales_channel not in self.event.settings.seating_allow_blocked_seats_for_channel:
if self.blocked:
return False
opqs = self.orderposition_set.filter(
order__status__in=[Order.STATUS_PENDING, Order.STATUS_PAID],
canceled=False
)
opqs = self.orderposition_set.filter(order__status__in=[Order.STATUS_PENDING, Order.STATUS_PAID])
cpqs = self.cartposition_set.filter(expires__gte=now())
vqs = self.vouchers.filter(
Q(Q(valid_until__isnull=True) | Q(valid_until__gte=now())) &

View File

@@ -4,8 +4,7 @@ from django.conf import settings
from django.core.exceptions import ValidationError
from django.core.validators import MinLengthValidator
from django.db import models
from django.db.models import F, OuterRef, Q, Subquery, Sum
from django.db.models.functions import Coalesce
from django.db.models import Q
from django.utils.crypto import get_random_string
from django.utils.timezone import now
from django.utils.translation import pgettext_lazy, ugettext_lazy as _
@@ -18,7 +17,7 @@ from ..decimal import round_decimal
from .base import LoggedModel
from .event import Event, SubEvent
from .items import Item, ItemVariation, Quota
from .orders import Order, OrderPosition
from .orders import Order
def _generate_random_code(prefix=None):
@@ -115,13 +114,6 @@ class Voucher(LoggedModel):
verbose_name=_("Redeemed"),
default=0
)
budget = models.DecimalField(
verbose_name=_("Maximum discount budget"),
help_text=_("This is the maximum monetary amount that will be discounted using this voucher across all usages. "
"If this is sum reached, the voucher can no longer be used."),
decimal_places=2, max_digits=10,
null=True, blank=True
)
valid_until = models.DateTimeField(
blank=True, null=True, db_index=True,
verbose_name=_("Valid until")
@@ -438,7 +430,7 @@ class Voucher(LoggedModel):
return False
return True
def calculate_price(self, original_price: Decimal, max_discount: Decimal=None) -> Decimal:
def calculate_price(self, original_price: Decimal) -> Decimal:
"""
Returns how the price given in original_price would be modified if this
voucher is applied, i.e. replaced by a different price or reduced by a
@@ -456,9 +448,7 @@ class Voucher(LoggedModel):
p = original_price
places = settings.CURRENCY_PLACES.get(self.event.currency, 2)
if places < 2:
p = p.quantize(Decimal('1') / 10 ** places, ROUND_HALF_UP)
if max_discount is not None:
p = max(p, original_price - max_discount)
return p.quantize(Decimal('1') / 10 ** places, ROUND_HALF_UP)
return p
return original_price
@@ -470,7 +460,7 @@ class Voucher(LoggedModel):
return Order.objects.filter(all_positions__voucher__in=[self]).distinct()
def seating_available(self, subevent):
def seating_available(self):
kwargs = {}
if self.subevent:
kwargs['subevent'] = self.subevent
@@ -479,27 +469,4 @@ class Voucher(LoggedModel):
elif self.item_id:
return self.item.seat_category_mappings.filter(**kwargs).exists()
else:
return bool(subevent.seating_plan) if subevent else self.event.seating_plan
@classmethod
def annotate_budget_used_orders(cls, qs):
opq = OrderPosition.objects.filter(
voucher_id=OuterRef('pk'),
price_before_voucher__isnull=False,
order__status__in=[
Order.STATUS_PAID,
Order.STATUS_PENDING
]
).order_by().values('voucher_id').annotate(s=Sum(F('price_before_voucher') - F('price'))).values('s')
return qs.annotate(budget_used_orders=Coalesce(Subquery(opq, output_field=models.DecimalField(max_digits=10, decimal_places=2)), Decimal('0.00')))
def budget_used(self):
ops = OrderPosition.objects.filter(
voucher=self,
price_before_voucher__isnull=False,
order__status__in=[
Order.STATUS_PAID,
Order.STATUS_PENDING
]
).aggregate(s=Sum(F('price_before_voucher') - F('price')))['s'] or Decimal('0.00')
return ops
return False

View File

@@ -1,612 +0,0 @@
import re
from decimal import Decimal, DecimalException
import pycountry
from django.conf import settings
from django.core.exceptions import ValidationError
from django.core.validators import EmailValidator
from django.utils import formats
from django.utils.functional import cached_property
from django.utils.translation import (
gettext as _, gettext_lazy, pgettext, pgettext_lazy,
)
from django_countries import countries
from django_countries.fields import Country
from pretix.base.channels import get_all_sales_channels
from pretix.base.forms.questions import guess_country
from pretix.base.models import (
ItemVariation, OrderPosition, QuestionAnswer, QuestionOption, Seat,
)
from pretix.base.services.pricing import get_price
from pretix.base.settings import (
COUNTRIES_WITH_STATE_IN_ADDRESS, PERSON_NAME_SCHEMES,
)
from pretix.base.signals import order_import_columns
class ImportColumn:
@property
def identifier(self):
"""
Unique, internal name of the column.
"""
raise NotImplementedError
@property
def verbose_name(self):
"""
Human-readable description of the column
"""
raise NotImplementedError
@property
def initial(self):
"""
Initial value for the form component
"""
return None
@property
def default_value(self):
"""
Internal default value for the assignment of this column. Defaults to ``empty``. Return ``None`` to disable this
option.
"""
return 'empty'
@property
def default_label(self):
"""
Human-readable description of the default assignment of this column, defaults to "Keep empty".
"""
return gettext_lazy('Keep empty')
def __init__(self, event):
self.event = event
def static_choices(self):
"""
This will be called when rendering the form component and allows you to return a list of values that can be
selected by the user statically during import.
:return: list of 2-tuples of strings
"""
return []
def resolve(self, settings, record):
"""
This method will be called to get the raw value for this field, usually by either using a static value or
inspecting the CSV file for the assigned header. You usually do not need to implement this on your own,
the default should be fine.
"""
k = settings.get(self.identifier, self.default_value)
if k == self.default_value:
return None
elif k.startswith('csv:'):
return record.get(k[4:], None) or None
elif k.startswith('static:'):
return k[7:]
raise ValidationError(_('Invalid setting for column "{header}".').format(header=self.verbose_name))
def clean(self, value, previous_values):
"""
Allows you to validate the raw input value for your column. Raise ``ValidationError`` if the value is invalid.
You do not need to include the column or row name or value in the error message as it will automatically be
included.
:param value: Contains the raw value of your column as returned by ``resolve``. This can usually be ``None``,
e.g. if the column is empty or does not exist in this row.
:param previous_values: Dictionary containing the validated values of all columns that have already been validated.
"""
return value
def assign(self, value, order, position, invoice_address, **kwargs):
"""
This will be called to perform the actual import. You are supposed to set attributes on the ``order``, ``position``,
or ``invoice_address`` objects based on the input ``value``. This is called *before* the actual database
transaction, so these three objects do not yet have a primary key. If you want to create related objects, you
need to place them into some sort of internal queue and persist them when ``save`` is called.
"""
pass
def save(self, order):
"""
This will be called to perform the actual import. This is called inside the actual database transaction and the
input object ``order`` has already been saved to the database.
"""
pass
class EmailColumn(ImportColumn):
identifier = 'email'
verbose_name = gettext_lazy('E-mail address')
def clean(self, value, previous_values):
if value:
EmailValidator()(value)
return value
def assign(self, value, order, position, invoice_address, **kwargs):
order.email = value
class SubeventColumn(ImportColumn):
identifier = 'subevent'
verbose_name = pgettext_lazy('subevents', 'Date')
default_value = None
@cached_property
def subevents(self):
return list(self.event.subevents.filter(active=True).order_by('date_from'))
def static_choices(self):
return [
(str(p.pk), str(p)) for p in self.subevents
]
def clean(self, value, previous_values):
if not value:
raise ValidationError(pgettext("subevent", "You need to select a date."))
matches = [
p for p in self.subevents
if str(p.pk) == value or any(
(v and v == value) for v in i18n_flat(p.name)) or p.date_from.isoformat() == value
]
if len(matches) == 0:
raise ValidationError(pgettext("subevent", "No matching date was found."))
if len(matches) > 1:
raise ValidationError(pgettext("subevent", "Multiple matching dates were found."))
return matches[0]
def assign(self, value, order, position, invoice_address, **kwargs):
position.subevent = value
def i18n_flat(l):
if isinstance(l.data, dict):
return l.data.values()
return [l.data]
class ItemColumn(ImportColumn):
identifier = 'item'
verbose_name = gettext_lazy('Product')
default_value = None
@cached_property
def items(self):
return list(self.event.items.filter(active=True))
def static_choices(self):
return [
(str(p.pk), str(p)) for p in self.items
]
def clean(self, value, previous_values):
matches = [
p for p in self.items
if str(p.pk) == value or (p.internal_name and p.internal_name == value) or any(
(v and v == value) for v in i18n_flat(p.name))
]
if len(matches) == 0:
raise ValidationError(_("No matching product was found."))
if len(matches) > 1:
raise ValidationError(_("Multiple matching products were found."))
return matches[0]
def assign(self, value, order, position, invoice_address, **kwargs):
position.item = value
class Variation(ImportColumn):
identifier = 'variation'
verbose_name = gettext_lazy('Product variation')
@cached_property
def items(self):
return list(ItemVariation.objects.filter(
active=True, item__active=True, item__event=self.event
).select_related('item'))
def static_choices(self):
return [
(str(p.pk), '{} {}'.format(p.item, p.value)) for p in self.items
]
def clean(self, value, previous_values):
if value:
matches = [
p for p in self.items
if str(p.pk) == value or any((v and v == value) for v in i18n_flat(p.value)) and p.item_id == previous_values['item'].pk
]
if len(matches) == 0:
raise ValidationError(_("No matching variation was found."))
if len(matches) > 1:
raise ValidationError(_("Multiple matching variations were found."))
return matches[0]
elif previous_values['item'].variations.exists():
raise ValidationError(_("You need to select a variation for this product."))
return value
def assign(self, value, order, position, invoice_address, **kwargs):
position.variation = value
class InvoiceAddressCompany(ImportColumn):
identifier = 'invoice_address_company'
@property
def verbose_name(self):
return _('Invoice address') + ': ' + _('Company')
def assign(self, value, order, position, invoice_address, **kwargs):
invoice_address.company = value or ''
invoice_address.is_business = bool(value)
class InvoiceAddressNamePart(ImportColumn):
def __init__(self, event, key, label):
self.key = key
self.label = label
super().__init__(event)
@property
def verbose_name(self):
return _('Invoice address') + ': ' + str(self.label)
@property
def identifier(self):
return 'invoice_address_name_{}'.format(self.key)
def assign(self, value, order, position, invoice_address, **kwargs):
invoice_address.name_parts[self.key] = value or ''
class InvoiceAddressStreet(ImportColumn):
identifier = 'invoice_address_street'
@property
def verbose_name(self):
return _('Invoice address') + ': ' + _('Address')
def assign(self, value, order, position, invoice_address, **kwargs):
invoice_address.address = value or ''
class InvoiceAddressZip(ImportColumn):
identifier = 'invoice_address_zipcode'
@property
def verbose_name(self):
return _('Invoice address') + ': ' + _('ZIP code')
def assign(self, value, order, position, invoice_address, **kwargs):
invoice_address.zipcode = value or ''
class InvoiceAddressCity(ImportColumn):
identifier = 'invoice_address_city'
@property
def verbose_name(self):
return _('Invoice address') + ': ' + _('City')
def assign(self, value, order, position, invoice_address, **kwargs):
invoice_address.city = value or ''
class InvoiceAddressCountry(ImportColumn):
identifier = 'invoice_address_country'
default_value = None
@property
def initial(self):
return 'static:' + str(guess_country(self.event))
@property
def verbose_name(self):
return _('Invoice address') + ': ' + _('Country')
def static_choices(self):
return list(countries)
def clean(self, value, previous_values):
if value and not Country(value).numeric:
raise ValidationError(_("Please enter a valid country code."))
return value
def assign(self, value, order, position, invoice_address, **kwargs):
invoice_address.country = value or ''
class InvoiceAddressState(ImportColumn):
identifier = 'invoice_address_state'
@property
def verbose_name(self):
return _('Invoice address') + ': ' + _('State')
def clean(self, value, previous_values):
if value:
if previous_values.get('invoice_address_country') not in COUNTRIES_WITH_STATE_IN_ADDRESS:
raise ValidationError(_("States are not supported for this country."))
types, form = COUNTRIES_WITH_STATE_IN_ADDRESS[previous_values.get('invoice_address_country')]
match = [
s for s in pycountry.subdivisions.get(country_code=previous_values.get('invoice_address_country'))
if s.type in types and (s.code[3:] == value or s.name == value)
]
if len(match) == 0:
raise ValidationError(_("Please enter a valid state."))
return match[0].code[3:]
def assign(self, value, order, position, invoice_address, **kwargs):
invoice_address.state = value or ''
class InvoiceAddressVATID(ImportColumn):
identifier = 'invoice_address_vat_id'
@property
def verbose_name(self):
return _('Invoice address') + ': ' + _('VAT ID')
def assign(self, value, order, position, invoice_address, **kwargs):
invoice_address.vat_id = value or ''
class InvoiceAddressReference(ImportColumn):
identifier = 'invoice_address_internal_reference'
@property
def verbose_name(self):
return _('Invoice address') + ': ' + _('Internal reference')
def assign(self, value, order, position, invoice_address, **kwargs):
invoice_address.internal_reference = value or ''
class AttendeeNamePart(ImportColumn):
def __init__(self, event, key, label):
self.key = key
self.label = label
super().__init__(event)
@property
def verbose_name(self):
return _('Attendee name') + ': ' + str(self.label)
@property
def identifier(self):
return 'attendee_name_{}'.format(self.key)
def assign(self, value, order, position, invoice_address, **kwargs):
position.attendee_name_parts[self.key] = value or ''
class AttendeeEmail(ImportColumn):
identifier = 'attendee_email'
verbose_name = gettext_lazy('Attendee e-mail address')
def clean(self, value, previous_values):
if value:
EmailValidator()(value)
return value
def assign(self, value, order, position, invoice_address, **kwargs):
position.attendee_email = value
class Price(ImportColumn):
identifier = 'price'
verbose_name = gettext_lazy('Price')
default_label = gettext_lazy('Calculate from product')
def clean(self, value, previous_values):
if value not in (None, ''):
value = formats.sanitize_separators(re.sub(r'[^0-9.,-]', '', value))
try:
value = Decimal(value)
except (DecimalException, TypeError):
raise ValidationError(_('You entered an invalid number.'))
return value
def assign(self, value, order, position, invoice_address, **kwargs):
if value is None:
p = get_price(position.item, position.variation, position.voucher, subevent=position.subevent,
invoice_address=invoice_address)
else:
p = get_price(position.item, position.variation, position.voucher, subevent=position.subevent,
invoice_address=invoice_address, custom_price=value, force_custom_price=True)
position.price = p.gross
position.tax_rule = position.item.tax_rule
position.tax_rate = p.rate
position.tax_value = p.tax
class Secret(ImportColumn):
identifier = 'secret'
verbose_name = gettext_lazy('Ticket code')
default_label = gettext_lazy('Generate automatically')
def __init__(self, *args):
self._cached = set()
super().__init__(*args)
def clean(self, value, previous_values):
if value and (value in self._cached or OrderPosition.all.filter(order__event=self.event, secret=value).exists()):
raise ValidationError(
_('You cannot assign a position secret that already exists.')
)
self._cached.add(value)
return value
def assign(self, value, order, position, invoice_address, **kwargs):
if value:
position.secret = value
class Locale(ImportColumn):
identifier = 'locale'
verbose_name = gettext_lazy('Order locale')
default_value = None
@property
def initial(self):
return 'static:' + self.event.settings.locale
def static_choices(self):
locale_names = dict(settings.LANGUAGES)
return [
(a, locale_names[a]) for a in self.event.settings.locales
]
def clean(self, value, previous_values):
if not value:
value = self.event.settings.locale
if value not in self.event.settings.locales:
raise ValidationError(_("Please enter a valid language code."))
return value
def assign(self, value, order, position, invoice_address, **kwargs):
order.locale = value
class Saleschannel(ImportColumn):
identifier = 'sales_channel'
verbose_name = gettext_lazy('Sales channel')
def static_choices(self):
return [
(sc.identifier, sc.verbose_name) for sc in get_all_sales_channels().values()
]
def clean(self, value, previous_values):
if not value:
value = 'web'
if value not in get_all_sales_channels():
raise ValidationError(_("Please enter a valid sales channel."))
return value
def assign(self, value, order, position, invoice_address, **kwargs):
order.sales_channel = value
class SeatColumn(ImportColumn):
identifier = 'seat'
verbose_name = gettext_lazy('Seat ID')
def __init__(self, *args):
self._cached = set()
super().__init__(*args)
def clean(self, value, previous_values):
if value:
try:
value = Seat.objects.get(
seat_guid=value,
subevent=previous_values.get('subevent')
)
except Seat.DoesNotExist:
raise ValidationError(_('No matching seat was found.'))
if not value.is_available() or value in self._cached:
raise ValidationError(
_('The seat you selected has already been taken. Please select a different seat.'))
self._cached.add(value)
elif previous_values['item'].seat_category_mappings.filter(subevent=previous_values.get('subevent')).exists():
raise ValidationError(_('You need to select a specific seat.'))
return value
def assign(self, value, order, position, invoice_address, **kwargs):
position.seat = value
class Comment(ImportColumn):
identifier = 'comment'
verbose_name = gettext_lazy('Comment')
def assign(self, value, order, position, invoice_address, **kwargs):
order.comment = value or ''
class QuestionColumn(ImportColumn):
def __init__(self, event, q):
self.q = q
super().__init__(event)
@property
def verbose_name(self):
return _('Question') + ': ' + str(self.q.question)
@property
def identifier(self):
return 'question_{}'.format(self.q.pk)
def clean(self, value, previous_values):
if value:
return self.q.clean_answer(value)
def assign(self, value, order, position, invoice_address, **kwargs):
if value:
if not hasattr(order, '_answers'):
order._answers = []
if isinstance(value, QuestionOption):
a = QuestionAnswer(orderposition=position, question=self.q, answer=str(value))
a._options = [value]
order._answers.append(a)
elif isinstance(value, list):
a = QuestionAnswer(orderposition=position, question=self.q, answer=', '.join(str(v) for v in value))
a._options = value
order._answers.append(a)
else:
order._answers.append(QuestionAnswer(question=self.q, answer=str(value), orderposition=position))
def save(self, order):
for a in getattr(order, '_answers', []):
a.orderposition = a.orderposition # This is apparently required after save() again
a.save()
if hasattr(a, '_options'):
a.options.add(*a._options)
def get_all_columns(event):
default = []
if event.has_subevents:
default.append(SubeventColumn(event))
default += [
EmailColumn(event),
ItemColumn(event),
Variation(event),
InvoiceAddressCompany(event),
]
scheme = PERSON_NAME_SCHEMES.get(event.settings.name_scheme)
for n, l, w in scheme['fields']:
default.append(InvoiceAddressNamePart(event, n, l))
default += [
InvoiceAddressStreet(event),
InvoiceAddressZip(event),
InvoiceAddressCity(event),
InvoiceAddressCountry(event),
InvoiceAddressState(event),
InvoiceAddressVATID(event),
InvoiceAddressReference(event),
]
for n, l, w in scheme['fields']:
default.append(AttendeeNamePart(event, n, l))
default += [
AttendeeEmail(event),
Price(event),
Secret(event),
Locale(event),
Saleschannel(event),
SeatColumn(event),
Comment(event)
]
for q in event.questions.exclude(type='F'):
default.append(QuestionColumn(event, q))
for recv, resp in order_import_columns.send(sender=event):
default += resp
return default

View File

@@ -34,7 +34,6 @@ from reportlab.pdfbase.ttfonts import TTFont
from reportlab.pdfgen.canvas import Canvas
from reportlab.platypus import Paragraph
from pretix.base.i18n import language
from pretix.base.invoice import ThumbnailingImageReader
from pretix.base.models import Order, OrderPosition
from pretix.base.settings import PERSON_NAME_SCHEMES
@@ -201,11 +200,6 @@ DEFAULT_VARIABLES = OrderedDict((
"editor_sample": _("Sample company"),
"evaluate": lambda op, order, ev: escape(order.invoice_address.company if getattr(order, 'invoice_address', None) else '')
}),
("invoice_city", {
"label": _("Invoice address city"),
"editor_sample": _("Sample city"),
"evaluate": lambda op, order, ev: escape(order.invoice_address.city if getattr(order, 'invoice_address', None) else '')
}),
("addons", {
"label": _("List of Add-Ons"),
"editor_sample": _("Addon 1\nAddon 2"),
@@ -413,11 +407,7 @@ class Renderer:
def _get_ev(self, op, order):
return op.subevent or order.event
def _get_text_content(self, op: OrderPosition, order: Order, o: dict, inner=False):
if o.get('locale', None) and not inner:
with language(o['locale']):
return self._get_text_content(op, order, o, True)
def _get_text_content(self, op: OrderPosition, order: Order, o: dict):
ev = self._get_ev(op, order)
if not o['content']:
return '(error)'
@@ -472,21 +462,10 @@ class Renderer:
text = "<br/>".join(get_display(reshaper.reshape(l)) for l in text.split("<br/>"))
p = Paragraph(text, style=style)
w, h = p.wrapOn(canvas, float(o['width']) * mm, 1000 * mm)
p.wrapOn(canvas, float(o['width']) * mm, 1000 * mm)
# p_size = p.wrap(float(o['width']) * mm, 1000 * mm)
ad = getAscentDescent(font, float(o['fontsize']))
canvas.saveState()
# The ascent/descent offsets here are not really proven to be correct, they're just empirical values to get
# reportlab render similarly to browser canvas.
if o.get('downward', False):
canvas.translate(float(o['left']) * mm, float(o['bottom']) * mm)
canvas.rotate(o.get('rotation', 0) * -1)
p.drawOn(canvas, 0, -h - ad[1] / 2)
else:
canvas.translate(float(o['left']) * mm, float(o['bottom']) * mm + h)
canvas.rotate(o.get('rotation', 0) * -1)
p.drawOn(canvas, 0, -h - ad[1])
canvas.restoreState()
p.drawOn(canvas, float(o['left']) * mm, float(o['bottom']) * mm - ad[1])
def draw_page(self, canvas: Canvas, order: Order, op: OrderPosition):
for o in self.layout:

View File

@@ -1,4 +1,3 @@
import os
import sys
from enum import Enum
from typing import List
@@ -45,14 +44,13 @@ def get_all_plugins(event=None) -> List[type]:
class PluginConfig(AppConfig):
IGNORE = False
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if not hasattr(self, 'PretixPluginMeta'):
raise ImproperlyConfigured("A pretix plugin config should have a PretixPluginMeta inner class.")
if hasattr(self.PretixPluginMeta, 'compatibility') and not os.environ.get("PRETIX_IGNORE_CONFLICTS") == "True":
if hasattr(self.PretixPluginMeta, 'compatibility'):
import pkg_resources
try:
pkg_resources.require(self.PretixPluginMeta.compatibility)

View File

@@ -108,12 +108,11 @@ error_messages = {
class CartManager:
AddOperation = namedtuple('AddOperation', ('count', 'item', 'variation', 'price', 'voucher', 'quotas',
'addon_to', 'subevent', 'includes_tax', 'bundled', 'seat',
'price_before_voucher'))
'addon_to', 'subevent', 'includes_tax', 'bundled', 'seat'))
RemoveOperation = namedtuple('RemoveOperation', ('position',))
VoucherOperation = namedtuple('VoucherOperation', ('position', 'voucher', 'price'))
ExtendOperation = namedtuple('ExtendOperation', ('position', 'count', 'item', 'variation', 'price', 'voucher',
'quotas', 'subevent', 'seat', 'price_before_voucher'))
'quotas', 'subevent', 'seat'))
order = {
RemoveOperation: 10,
VoucherOperation: 15,
@@ -234,7 +233,7 @@ class CartManager:
# TODO: i18n plurals
raise CartError(_(error_messages['max_items']) % (self.event.settings.max_items_per_order,))
def _check_item_constraints(self, op, current_ops=[]):
def _check_item_constraints(self, op):
if isinstance(op, self.AddOperation) or isinstance(op, self.ExtendOperation):
if not (
(isinstance(op, self.AddOperation) and op.addon_to == 'FAKE') or
@@ -277,7 +276,7 @@ class CartManager:
raise CartError(error_messages['ended'])
seated = self._is_seated(op.item, op.subevent)
if seated and (not op.seat or (op.seat.blocked and self._sales_channel not in self.event.settings.seating_allow_blocked_seats_for_channel)):
if seated and (not op.seat or op.seat.blocked):
raise CartError(error_messages['seat_invalid'])
elif op.seat and not seated:
raise CartError(error_messages['seat_forbidden'])
@@ -306,10 +305,10 @@ class CartManager:
if op.item.max_per_order or op.item.min_per_order:
new_total = (
len([1 for p in self.positions if p.item_id == op.item.pk]) +
sum([_op.count for _op in self._operations + current_ops
sum([_op.count for _op in self._operations
if isinstance(_op, self.AddOperation) and _op.item == op.item]) +
op.count -
len([1 for _op in self._operations + current_ops
len([1 for _op in self._operations
if isinstance(_op, self.RemoveOperation) and _op.position.item_id == op.item.pk])
)
@@ -385,7 +384,6 @@ class CartManager:
else:
price = self._get_price(cp.item, cp.variation, cp.voucher, price, cp.subevent,
force_custom_price=True)
pbv = TAXED_ZERO
else:
bundled_sum = Decimal('0.00')
if not cp.addon_to_id:
@@ -398,14 +396,9 @@ class CartManager:
price = self._get_price(cp.item, cp.variation, cp.voucher, cp.price, cp.subevent,
cp_is_net=True, bundled_sum=bundled_sum)
price = TaxedPrice(net=price.net, gross=price.net, rate=0, tax=0, name='')
pbv = self._get_price(cp.item, cp.variation, None, cp.price, cp.subevent,
cp_is_net=True, bundled_sum=bundled_sum)
pbv = TaxedPrice(net=pbv.net, gross=pbv.net, rate=0, tax=0, name='')
else:
price = self._get_price(cp.item, cp.variation, cp.voucher, cp.price, cp.subevent,
bundled_sum=bundled_sum)
pbv = self._get_price(cp.item, cp.variation, None, cp.price, cp.subevent,
bundled_sum=bundled_sum)
quotas = list(cp.quotas)
if not quotas:
@@ -421,7 +414,7 @@ class CartManager:
op = self.ExtendOperation(
position=cp, item=cp.item, variation=cp.variation, voucher=cp.voucher, count=1,
price=price, quotas=quotas, subevent=cp.subevent, seat=cp.seat, price_before_voucher=pbv
price=price, quotas=quotas, subevent=cp.subevent, seat=cp.seat
)
self._check_item_constraints(op)
@@ -468,10 +461,8 @@ class CartManager:
bundled_sum += bundledprice
price = self._get_price(p.item, p.variation, voucher, None, p.subevent, bundled_sum=bundled_sum)
"""
if price.gross > p.price:
continue
"""
voucher_use_diff[voucher] += 1
ops.append((p.price - price.gross, self.VoucherOperation(p, voucher, price)))
@@ -576,20 +567,18 @@ class CartManager:
bop = self.AddOperation(
count=bundle.count, item=bitem, variation=bvar, price=bprice,
voucher=None, quotas=bundle_quotas, addon_to='FAKE', subevent=subevent,
includes_tax=bool(bprice.rate), bundled=[], seat=None, price_before_voucher=bprice,
includes_tax=bool(bprice.rate), bundled=[], seat=None
)
self._check_item_constraints(bop, operations)
self._check_item_constraints(bop)
bundled.append(bop)
price = self._get_price(item, variation, voucher, i.get('price'), subevent, bundled_sum=bundled_sum)
pbv = self._get_price(item, variation, None, i.get('price'), subevent, bundled_sum=bundled_sum)
op = self.AddOperation(
count=i['count'], item=item, variation=variation, price=price, voucher=voucher, quotas=quotas,
addon_to=False, subevent=subevent, includes_tax=bool(price.rate), bundled=bundled, seat=seat,
price_before_voucher=pbv
addon_to=False, subevent=subevent, includes_tax=bool(price.rate), bundled=bundled, seat=seat
)
self._check_item_constraints(op, operations)
self._check_item_constraints(op)
operations.append(op)
self._quota_diff.update(quota_diff)
@@ -693,10 +682,9 @@ class CartManager:
op = self.AddOperation(
count=1, item=item, variation=variation, price=price, voucher=None, quotas=quotas,
addon_to=cp, subevent=cp.subevent, includes_tax=bool(price.rate), bundled=[], seat=None,
price_before_voucher=None
addon_to=cp, subevent=cp.subevent, includes_tax=bool(price.rate), bundled=[], seat=None
)
self._check_item_constraints(op, operations)
self._check_item_constraints(op)
operations.append(op)
# Check constraints on the add-on combinations
@@ -899,7 +887,7 @@ class CartManager:
available_count = 0
if isinstance(op, self.AddOperation):
if op.seat and not op.seat.is_available(ignore_voucher_id=op.voucher.id if op.voucher else None, sales_channel=self._sales_channel):
if op.seat and not op.seat.is_available(ignore_voucher_id=op.voucher.id if op.voucher else None):
available_count = 0
err = err or error_messages['seat_unavailable']
@@ -908,8 +896,7 @@ class CartManager:
event=self.event, item=op.item, variation=op.variation,
price=op.price.gross, expires=self._expiry, cart_id=self.cart_id,
voucher=op.voucher, addon_to=op.addon_to if op.addon_to else None,
subevent=op.subevent, includes_tax=op.includes_tax, seat=op.seat,
price_before_voucher=op.price_before_voucher.gross if op.price_before_voucher is not None else None
subevent=op.subevent, includes_tax=op.includes_tax, seat=op.seat
)
if self.event.settings.attendee_names_asked:
scheme = PERSON_NAME_SCHEMES.get(self.event.settings.name_scheme)
@@ -948,7 +935,7 @@ class CartManager:
new_cart_positions.append(cp)
elif isinstance(op, self.ExtendOperation):
if op.seat and not op.seat.is_available(ignore_cart=op.position, sales_channel=self._sales_channel,
if op.seat and not op.seat.is_available(ignore_cart=op.position,
ignore_voucher_id=op.position.voucher_id):
err = err or error_messages['seat_unavailable']
op.position.addons.all().delete()
@@ -956,8 +943,6 @@ class CartManager:
elif available_count == 1:
op.position.expires = self._expiry
op.position.price = op.price.gross
if op.price_before_voucher is not None:
op.position.price_before_voucher = op.price_before_voucher.gross
try:
op.position.save(force_update=True)
except DatabaseError:
@@ -977,7 +962,6 @@ class CartManager:
# be expected
continue
op.position.price_before_voucher = op.position.price
op.position.price = op.price.gross
op.position.voucher = op.voucher
op.position.save()
@@ -1147,7 +1131,7 @@ def add_items_to_cart(self, event: int, items: List[dict], cart_id: str=None, lo
@app.task(base=ProfiledEventTask, bind=True, max_retries=5, default_retry_delay=1, throws=(CartError,))
def apply_voucher(self, event: Event, voucher: str, cart_id: str=None, locale='en', sales_channel='web') -> None:
def apply_voucher(self, event: Event, voucher: str, cart_id: str=None, locale='en') -> None:
"""
Removes a list of items from a user's cart.
:param event: The event ID in question
@@ -1157,7 +1141,7 @@ def apply_voucher(self, event: Event, voucher: str, cart_id: str=None, locale='e
with language(locale):
try:
try:
cm = CartManager(event=event, cart_id=cart_id, sales_channel=sales_channel)
cm = CartManager(event=event, cart_id=cart_id)
cm.apply_voucher(voucher)
cm.commit()
except LockTimeoutException:
@@ -1167,7 +1151,7 @@ def apply_voucher(self, event: Event, voucher: str, cart_id: str=None, locale='e
@app.task(base=ProfiledEventTask, bind=True, max_retries=5, default_retry_delay=1, throws=(CartError,))
def remove_cart_position(self, event: Event, position: int, cart_id: str=None, locale='en', sales_channel='web') -> None:
def remove_cart_position(self, event: Event, position: int, cart_id: str=None, locale='en') -> None:
"""
Removes a list of items from a user's cart.
:param event: The event ID in question
@@ -1177,7 +1161,7 @@ def remove_cart_position(self, event: Event, position: int, cart_id: str=None, l
with language(locale):
try:
try:
cm = CartManager(event=event, cart_id=cart_id, sales_channel=sales_channel)
cm = CartManager(event=event, cart_id=cart_id)
cm.remove_item(position)
cm.commit()
except LockTimeoutException:
@@ -1187,7 +1171,7 @@ def remove_cart_position(self, event: Event, position: int, cart_id: str=None, l
@app.task(base=ProfiledEventTask, bind=True, max_retries=5, default_retry_delay=1, throws=(CartError,))
def clear_cart(self, event: Event, cart_id: str=None, locale='en', sales_channel='web') -> None:
def clear_cart(self, event: Event, cart_id: str=None, locale='en') -> None:
"""
Removes a list of items from a user's cart.
:param event: The event ID in question
@@ -1196,7 +1180,7 @@ def clear_cart(self, event: Event, cart_id: str=None, locale='en', sales_channel
with language(locale):
try:
try:
cm = CartManager(event=event, cart_id=cart_id, sales_channel=sales_channel)
cm = CartManager(event=event, cart_id=cart_id)
cm.clear()
cm.commit()
except LockTimeoutException:

View File

@@ -3,7 +3,6 @@ import logging
import os
import re
import smtplib
import ssl
import warnings
from email.mime.image import MIMEImage
from email.utils import formataddr
@@ -133,7 +132,7 @@ def mail(email: str, subject: str, template: Union[str, LazyI18nString],
else:
sender = formataddr((settings.PRETIX_INSTANCE_NAME, sender))
subject = raw_subject = str(subject)
subject = str(subject)
signature = ""
bcc = []
@@ -199,13 +198,13 @@ def mail(email: str, subject: str, template: Union[str, LazyI18nString],
try:
if 'position' in inspect.signature(renderer.render).parameters:
body_html = renderer.render(content_plain, signature, raw_subject, order, position)
body_html = renderer.render(content_plain, signature, str(subject), order, position)
else:
# Backwards compatibility
warnings.warn('E-mail renderer called without position argument because position argument is not '
'supported.',
DeprecationWarning)
body_html = renderer.render(content_plain, signature, raw_subject, order)
body_html = renderer.render(content_plain, signature, str(subject), order)
except:
logger.exception('Could not render HTML body')
body_html = None
@@ -366,8 +365,6 @@ def mail_send_task(self, *args, to: List[str], subject: str, body: str, html: st
raise SendMailException('Failed to send an email to {}.'.format(to))
except Exception as e:
if isinstance(e, (smtplib.SMTPServerDisconnected, smtplib.SMTPConnectError, ssl.SSLError, OSError)):
self.retry(max_retries=5, countdown=2 ** (self.request.retries * 2))
if order:
order.log_action(
'pretix.event.order.email.error',

View File

@@ -1,173 +0,0 @@
import csv
import io
from decimal import Decimal
from django.core.exceptions import ValidationError
from django.db import transaction
from django.utils.timezone import now
from django.utils.translation import gettext as _
from pretix.base.i18n import LazyLocaleException, language
from pretix.base.models import (
CachedFile, Event, InvoiceAddress, Order, OrderPayment, OrderPosition,
User,
)
from pretix.base.orderimport import get_all_columns
from pretix.base.services.invoices import generate_invoice, invoice_qualified
from pretix.base.services.tasks import ProfiledEventTask
from pretix.base.signals import order_paid, order_placed
from pretix.celery_app import app
class DataImportError(LazyLocaleException):
def __init__(self, *args):
msg = args[0]
msgargs = args[1] if len(args) > 1 else None
self.args = args
if msgargs:
msg = _(msg) % msgargs
else:
msg = _(msg)
super().__init__(msg)
def parse_csv(file, length=None):
data = file.read(length)
try:
import chardet
charset = chardet.detect(data)['encoding']
except ImportError:
charset = file.charset
data = data.decode(charset or 'utf-8')
# If the file was modified on a Mac, it only contains \r as line breaks
if '\r' in data and '\n' not in data:
data = data.replace('\r', '\n')
dialect = csv.Sniffer().sniff(data.split("\n")[0], delimiters=";,.#:")
if dialect is None:
return None
reader = csv.DictReader(io.StringIO(data), dialect=dialect)
return reader
def setif(record, obj, attr, setting):
if setting.startswith('csv:'):
setattr(obj, attr, record[setting[4:]] or '')
@app.task(base=ProfiledEventTask, throws=(DataImportError,))
def import_orders(event: Event, fileid: str, settings: dict, locale: str, user) -> None:
# TODO: quotacheck?
cf = CachedFile.objects.get(id=fileid)
user = User.objects.get(pk=user)
with language(locale):
cols = get_all_columns(event)
parsed = parse_csv(cf.file)
orders = []
order = None
data = []
# Run validation
for i, record in enumerate(parsed):
values = {}
for c in cols:
val = c.resolve(settings, record)
try:
values[c.identifier] = c.clean(val, values)
except ValidationError as e:
raise DataImportError(
_(
'Error while importing value "{value}" for column "{column}" in line "{line}": {message}').format(
value=val if val is not None else '', column=c.verbose_name, line=i + 1, message=e.message
)
)
data.append(values)
# Prepare model objects. Yes, this might consume lots of RAM, but allows us to make the actual SQL transaction
# shorter. We'll see what works better in reality…
for i, record in enumerate(data):
try:
if order is None or settings['orders'] == 'many':
order = Order(
event=event,
testmode=settings['testmode'],
)
order.meta_info = {}
order._positions = []
order._address = InvoiceAddress()
order._address.name_parts = {'_scheme': event.settings.name_scheme}
orders.append(order)
position = OrderPosition()
position.attendee_name_parts = {'_scheme': event.settings.name_scheme}
position.meta_info = {}
order._positions.append(position)
position.assign_pseudonymization_id()
for c in cols:
c.assign(record.get(c.identifier), order, position, order._address)
except ImportError as e:
raise ImportError(
_('Invalid data in row {row}: {message}').format(row=i, message=str(e))
)
# quota check?
with event.lock():
with transaction.atomic():
for o in orders:
o.total = sum([c.price for c in o._positions]) # currently no support for fees
if o.total == Decimal('0.00'):
o.status = Order.STATUS_PAID
o.save()
OrderPayment.objects.create(
local_id=1,
order=o,
amount=Decimal('0.00'),
provider='free',
info='{}',
payment_date=now(),
state=OrderPayment.PAYMENT_STATE_CONFIRMED
)
elif settings['status'] == 'paid':
o.status = Order.STATUS_PAID
o.save()
OrderPayment.objects.create(
local_id=1,
order=o,
amount=o.total,
provider='manual',
info='{}',
payment_date=now(),
state=OrderPayment.PAYMENT_STATE_CONFIRMED
)
else:
o.status = Order.STATUS_PENDING
o.save()
for p in o._positions:
p.order = o
p.save()
o._address.order = o
o._address.save()
for c in cols:
c.save(o)
o.log_action(
'pretix.event.order.placed',
user=user,
data={'source': 'import'}
)
for o in orders:
with language(o.locale):
order_placed.send(event, order=o)
if o.status == Order.STATUS_PAID:
order_paid.send(event, order=o)
gen_invoice = invoice_qualified(o) and (
(event.settings.get('invoice_generate') == 'True') or
(event.settings.get('invoice_generate') == 'paid' and o.status == Order.STATUS_PAID)
) and not o.invoices.last()
if gen_invoice:
generate_invoice(o, trigger_pdf=True)
cf.delete()

View File

@@ -70,8 +70,6 @@ error_messages = {
'voucher_invalid': _('The voucher code used for one of the items in your cart is not known in our database.'),
'voucher_redeemed': _('The voucher code used for one of the items in your cart has already been used the maximum '
'number of times allowed. We removed this item from your cart.'),
'voucher_budget_used': _('The voucher code used for one of the items in your cart has already been too often. We '
'adjusted the price of the item in your cart.'),
'voucher_expired': _('The voucher code used for one of the items in your cart is expired. We removed this item '
'from your cart.'),
'voucher_invalid_item': _('The voucher code used for one of the items in your cart is not valid for this item. We '
@@ -420,15 +418,13 @@ def _check_date(event: Event, now_dt: datetime):
raise OrderError(error_messages['ended'])
def _check_positions(event: Event, now_dt: datetime, positions: List[CartPosition], address: InvoiceAddress=None,
sales_channel='web'):
def _check_positions(event: Event, now_dt: datetime, positions: List[CartPosition], address: InvoiceAddress=None):
err = None
errargs = None
_check_date(event, now_dt)
products_seen = Counter()
changed_prices = {}
v_budget = {}
deleted_positions = set()
seats_seen = set()
@@ -471,20 +467,6 @@ def _check_positions(event: Event, now_dt: datetime, positions: List[CartPositio
delete(cp)
continue
if cp.voucher.budget is not None:
if cp.voucher not in v_budget:
v_budget[cp.voucher] = cp.voucher.budget - cp.voucher.budget_used()
disc = cp.price_before_voucher - cp.price
if disc > v_budget[cp.voucher]:
new_disc = max(0, v_budget[cp.voucher])
cp.price = cp.price + (disc - new_disc)
cp.save()
err = err or error_messages['voucher_budget_used']
v_budget[cp.voucher] -= new_disc
continue
else:
v_budget[cp.voucher] -= disc
if cp.subevent and cp.subevent.presale_start and now_dt < cp.subevent.presale_start:
err = err or error_messages['some_subevent_not_started']
delete(cp)
@@ -530,7 +512,7 @@ def _check_positions(event: Event, now_dt: datetime, positions: List[CartPositio
if cp.seat:
# Unlike quotas (which we blindly trust as long as the position is not expired), we check seats every
# time, since we absolutely can not overbook a seat.
if not cp.seat.is_available(ignore_cart=cp, ignore_voucher_id=cp.voucher_id, sales_channel=sales_channel):
if not cp.seat.is_available(ignore_cart=cp, ignore_voucher_id=cp.voucher_id) or cp.seat.blocked:
err = err or error_messages['seat_unavailable']
cp.delete()
continue
@@ -539,11 +521,6 @@ def _check_positions(event: Event, now_dt: datetime, positions: List[CartPositio
# Other checks are not necessary
continue
max_discount = None
if cp.price_before_voucher is not None and cp.voucher in v_budget:
current_discount = cp.price_before_voucher - cp.price
max_discount = max(v_budget[cp.voucher] + current_discount, 0)
if cp.is_bundled:
try:
bundle = cp.addon_to.item.bundles.get(bundled_item=cp.item, bundled_variation=cp.variation)
@@ -551,9 +528,7 @@ def _check_positions(event: Event, now_dt: datetime, positions: List[CartPositio
except ItemBundle.DoesNotExist:
bprice = cp.price
price = get_price(cp.item, cp.variation, cp.voucher, bprice, cp.subevent, custom_price_is_net=False,
invoice_address=address, force_custom_price=True, max_discount=max_discount)
pbv = get_price(cp.item, cp.variation, None, bprice, cp.subevent, custom_price_is_net=False,
invoice_address=address, force_custom_price=True, max_discount=max_discount)
invoice_address=address, force_custom_price=True)
changed_prices[cp.pk] = bprice
else:
bundled_sum = 0
@@ -563,14 +538,7 @@ def _check_positions(event: Event, now_dt: datetime, positions: List[CartPositio
bundled_sum += changed_prices.get(bundledp.pk, bundledp.price)
price = get_price(cp.item, cp.variation, cp.voucher, cp.price, cp.subevent, custom_price_is_net=False,
addon_to=cp.addon_to, invoice_address=address, bundled_sum=bundled_sum,
max_discount=max_discount)
pbv = get_price(cp.item, cp.variation, None, cp.price, cp.subevent, custom_price_is_net=False,
addon_to=cp.addon_to, invoice_address=address, bundled_sum=bundled_sum,
max_discount=max_discount)
if max_discount is not None:
v_budget[cp.voucher] = v_budget[cp.voucher] + current_discount - (pbv.gross - price.gross)
addon_to=cp.addon_to, invoice_address=address, bundled_sum=bundled_sum)
if price is False or len(quotas) == 0:
err = err or error_messages['unavailable']
@@ -583,11 +551,6 @@ def _check_positions(event: Event, now_dt: datetime, positions: List[CartPositio
delete(cp)
continue
if pbv is not None and pbv.gross != price.gross:
cp.price_before_voucher = pbv.gross
else:
cp.price_before_voucher = None
if price.gross != cp.price and not (cp.item.free_price and cp.price > price.gross):
cp.price = price.gross
cp.includes_tax = bool(price.rate)
@@ -838,15 +801,12 @@ def _perform_order(event: Event, payment_provider: str, position_ids: List[str],
lockfn = event.lock
with lockfn() as now_dt:
positions = list(
positions.select_related('item', 'variation', 'subevent', 'seat', 'addon_to').prefetch_related('addons')
)
positions.sort(key=lambda k: position_ids.index(k.pk))
positions = list(positions.select_related('item', 'variation', 'subevent', 'seat', 'addon_to').prefetch_related('addons'))
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, sales_channel=sales_channel)
_check_positions(event, now_dt, positions, address=addr)
order, payment = _create_order(event, email, positions, now_dt, pprov,
locale=locale, address=addr, meta_info=meta_info, sales_channel=sales_channel,
gift_cards=gift_cards, shown_total=shown_total)
@@ -984,7 +944,7 @@ def send_download_reminders(sender, **kwargs):
continue
reminder_date = (o.first_date - timedelta(days=days)).replace(hour=0, minute=0, second=0, microsecond=0)
if now() < reminder_date or o.datetime > reminder_date:
if now() < reminder_date:
continue
with transaction.atomic():
@@ -1104,21 +1064,6 @@ class OrderChangeManager:
self._operations.append(self.ItemOperation(position, item, variation))
def change_seat(self, position: OrderPosition, seat: Seat):
if isinstance(seat, str):
subev = None
if self.event.has_subevents:
subev = position.subevent
for p in self._operations:
if isinstance(p, self.SubeventOperation) and p.position == position:
subev = p.subevent
try:
seat = Seat.objects.get(
event=self.event,
subevent=subev,
seat_guid=seat
)
except Seat.DoesNotExist:
raise OrderError(error_messages['seat_invalid'])
if position.seat:
self._seatdiff.subtract([position.seat])
if seat:
@@ -1202,19 +1147,6 @@ class OrderChangeManager:
def add_position(self, item: Item, variation: ItemVariation, price: Decimal, addon_to: Order = None,
subevent: SubEvent = None, seat: Seat = None):
if isinstance(seat, str):
if not seat:
seat = None
else:
try:
seat = Seat.objects.get(
event=self.event,
subevent=subevent,
seat_guid=seat
)
except Seat.DoesNotExist:
raise OrderError(error_messages['seat_invalid'])
if price is None:
price = get_price(item, variation, subevent=subevent, invoice_address=self._invoice_address)
else:
@@ -1238,7 +1170,7 @@ class OrderChangeManager:
raise OrderError(self.error_messages['seat_required'])
elif not seated and seat:
raise OrderError(self.error_messages['seat_forbidden'])
if seat and subevent and seat.subevent_id != subevent.pk:
if seat and subevent and seat.subevent_id != subevent:
raise OrderError(self.error_messages['seat_subevent_mismatch'].format(seat=seat.name))
new_quotas = (variation.quotas.filter(subevent=subevent)
@@ -1265,7 +1197,7 @@ class OrderChangeManager:
for seat, diff in self._seatdiff.items():
if diff <= 0:
continue
if not seat.is_available(sales_channel=self.order.sales_channel) or diff > 1:
if not seat.is_available() or diff > 1:
raise OrderError(self.error_messages['seat_unavailable'].format(seat=seat.name))
if self.event.has_subevents:
@@ -1401,16 +1333,6 @@ class OrderChangeManager:
op.position.item = op.item
op.position.variation = op.variation
op.position._calculate_tax()
if op.position.price_before_voucher is not None and op.position.voucher and not op.position.addon_to_id:
op.position.price_before_voucher = max(
op.position.price,
get_price(
op.position.item, op.position.variation,
subevent=op.position.subevent,
custom_price=op.position.price,
invoice_address=self._invoice_address
).gross
)
op.position.save()
elif isinstance(op, self.SeatOperation):
self.order.log_action('pretix.event.order.changed.seat', user=self.user, auth=self.auth, data={
@@ -1434,16 +1356,6 @@ class OrderChangeManager:
})
op.position.subevent = op.subevent
op.position.save()
if op.position.price_before_voucher is not None and op.position.voucher and not op.position.addon_to_id:
op.position.price_before_voucher = max(
op.position.price,
get_price(
op.position.item, op.position.variation,
subevent=op.position.subevent,
custom_price=op.position.price,
invoice_address=self._invoice_address
).gross
)
elif isinstance(op, self.FeeValueOperation):
self.order.log_action('pretix.event.order.changed.feevalue', user=self.user, auth=self.auth, data={
'fee': op.fee.pk,

View File

@@ -12,8 +12,7 @@ def get_price(item: Item, variation: ItemVariation = None,
voucher: Voucher = None, custom_price: Decimal = None,
subevent: SubEvent = None, custom_price_is_net: bool = False,
addon_to: AbstractPosition = None, invoice_address: InvoiceAddress = None,
force_custom_price: bool = False, bundled_sum: Decimal = Decimal('0.00'),
max_discount: Decimal = None) -> TaxedPrice:
force_custom_price: bool = False, bundled_sum: Decimal = Decimal('0.00')) -> TaxedPrice:
if addon_to:
try:
iao = addon_to.item.addons.get(addon_category_id=item.category_id)
@@ -33,7 +32,7 @@ def get_price(item: Item, variation: ItemVariation = None,
price = subevent.var_price_overrides[variation.pk]
if voucher:
price = voucher.calculate_price(price, max_discount=max_discount)
price = voucher.calculate_price(price)
if item.tax_rule:
tax_rule = item.tax_rule

View File

@@ -683,10 +683,6 @@ Your {event} team"""))
)),
'type': LazyI18nString
},
'order_import_settings': {
'default': '{}',
'type': dict
},
'organizer_info_text': {
'default': '',
'type': LazyI18nString
@@ -754,11 +750,7 @@ Your {event} team"""))
'giftcard_length': {
'default': settings.ENTROPY['giftcard_secret'],
'type': int
},
'seating_allow_blocked_seats_for_channel': {
'default': [],
'type': list
},
}
}
PERSON_NAME_TITLE_GROUPS = OrderedDict([
('english_common', (_('Most common English titles'), (

View File

@@ -630,14 +630,3 @@ invoice_line_text = EventPluginSignal(
This signal is sent out when an invoice is built for an order. You can return additional text that
should be shown on the invoice for the given ``position``.
"""
order_import_columns = EventPluginSignal(
providing_args=[]
)
"""
This signal is sent out if the user performs an import of orders from an external source. You can use this
to define additional columns that can be read during import. You are expected to return a list of instances of
``ImportColumn`` subclasses.
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
"""

View File

@@ -139,14 +139,6 @@
text-decoration: none;
color: {{ color }};
}
{% if rtl %}
body {
direction: rtl;
}
.content table td {
text-align: right;
}
{% endif %}
{% block addcss %}{% endblock %}
</style>

View File

@@ -1,11 +1,8 @@
from urllib.parse import urlencode
from django import forms
from django.conf import settings
from django.contrib.auth.hashers import check_password
from django.core.exceptions import ValidationError
from django.core.validators import (
MaxValueValidator, MinValueValidator, RegexValidator, validate_email,
)
from django.core.validators import RegexValidator, validate_email
from django.db.models import Q
from django.forms import formset_factory
from django.urls import reverse
@@ -149,8 +146,7 @@ class EventWizardBasicsForm(I18nModelForm):
self.user = kwargs.pop('user')
kwargs.pop('session')
super().__init__(*args, **kwargs)
if 'timezone' not in self.initial:
self.initial['timezone'] = get_current_timezone_name()
self.initial['timezone'] = get_current_timezone_name()
self.fields['locale'].choices = [(a, b) for a, b in settings.LANGUAGES if a in self.locales]
self.fields['location'].widget.attrs['rows'] = '3'
self.fields['location'].widget.attrs['placeholder'] = _(
@@ -274,18 +270,12 @@ class EventMetaValueForm(forms.ModelForm):
super().__init__(*args, **kwargs)
self.fields['value'].required = False
self.fields['value'].widget.attrs['placeholder'] = self.property.default
self.fields['value'].widget.attrs['data-typeahead-url'] = (
reverse('control:events.meta.typeahead') + '?' + urlencode({
'property': self.property.name,
'organizer': self.property.organizer.slug,
})
)
class Meta:
model = EventMetaValue
fields = ['value']
widgets = {
'value': forms.TextInput()
'value': forms.TextInput
}
@@ -683,9 +673,6 @@ class PaymentSettingsForm(SettingsForm):
"you use slow payment methods like bank transfer, we recommend 14 days. If you only use real-time "
"payment methods, we recommend still setting two or three days to allow people to retry failed "
"payments."),
validators=[MinValueValidator(0),
MaxValueValidator(1000000)]
)
payment_term_last = RelativeDateField(
label=_('Last date of payments'),
@@ -850,9 +837,7 @@ class InvoiceSettingsForm(SettingsForm):
help_text=_("This will be prepended to invoice numbers. If you leave this field empty, your event slug will "
"be used followed by a dash. Attention: If multiple events within the same organization use the "
"same value in this field, they will share their number range, i.e. every full number will be "
"used at most once over all of your events. This setting only affects future invoices. You can "
"use %Y (with century) %y (without century) to insert the year of the invoice, or %m and %d for "
"the day of month."),
"used at most once over all of your events. This setting only affects future invoices."),
required=False,
)
invoice_numbers_prefix_cancellations = forms.CharField(
@@ -1453,8 +1438,14 @@ class WidgetCodeForm(forms.Form):
class EventDeleteForm(forms.Form):
error_messages = {
'pw_current_wrong': _("The password you entered was not correct."),
'slug_wrong': _("The slug you entered was not correct."),
}
user_pw = forms.CharField(
max_length=255,
label=_("Your password"),
widget=forms.PasswordInput()
)
slug = forms.CharField(
max_length=255,
label=_("Event slug"),
@@ -1462,8 +1453,19 @@ class EventDeleteForm(forms.Form):
def __init__(self, *args, **kwargs):
self.event = kwargs.pop('event')
self.user = kwargs.pop('user')
super().__init__(*args, **kwargs)
def clean_user_pw(self):
user_pw = self.cleaned_data.get('user_pw')
if not check_password(user_pw, self.user.password):
raise forms.ValidationError(
self.error_messages['pw_current_wrong'],
code='pw_current_wrong',
)
return user_pw
def clean_slug(self):
slug = self.cleaned_data.get('slug')
if slug != self.event.slug:

View File

@@ -1,53 +0,0 @@
from django import forms
from django.utils.translation import gettext_lazy as _
from pretix.base.services.orderimport import get_all_columns
class ProcessForm(forms.Form):
orders = forms.ChoiceField(
label=_('Import mode'),
choices=(
('many', _('Create a separate order for each line')),
('one', _('Create one order with one position per line')),
)
)
status = forms.ChoiceField(
label=_('Order status'),
choices=(
('paid', _('Create orders as fully paid')),
('pending', _('Create orders as pending and still require payment')),
)
)
testmode = forms.BooleanField(
label=_('Create orders as test mode orders'),
required=False
)
def __init__(self, *args, **kwargs):
headers = kwargs.pop('headers')
initital = kwargs.pop('initial', {})
self.event = kwargs.pop('event')
initital['testmode'] = self.event.testmode
kwargs['initial'] = initital
super().__init__(*args, **kwargs)
header_choices = [
('csv:{}'.format(h), _('CSV column: "{name}"').format(name=h)) for h in headers
]
for c in get_all_columns(self.event):
choices = []
if c.default_value:
choices.append((c.default_value, c.default_label))
choices += header_choices
for k, v in c.static_choices():
choices.append(('static:{}'.format(k), v))
self.fields[c.identifier] = forms.ChoiceField(
label=str(c.verbose_name),
choices=choices,
widget=forms.Select(
attrs={'data-static': 'true'}
)
)

View File

@@ -11,7 +11,9 @@ from django.utils.translation import pgettext_lazy, ugettext_lazy as _
from pretix.base.email import get_available_placeholders
from pretix.base.forms import I18nModelForm, PlaceholderValidator
from pretix.base.forms.widgets import DatePickerWidget
from pretix.base.models import InvoiceAddress, ItemAddOn, Order, OrderPosition
from pretix.base.models import (
InvoiceAddress, ItemAddOn, Order, OrderPosition, Seat,
)
from pretix.base.models.event import SubEvent
from pretix.base.services.pricing import get_price
from pretix.control.forms.widgets import Select2
@@ -202,10 +204,11 @@ class OrderPositionAddForm(forms.Form):
required=False,
label=_('Add-on to'),
)
seat = forms.CharField(
seat = forms.ModelChoiceField(
Seat.objects.none(),
required=False,
widget=forms.TextInput(attrs={'placeholder': _('General admission'), 'data-seat-guid-field': 'true'}),
label=_('Seat')
label=_('Seat'),
empty_label=_('General admission')
)
price = forms.DecimalField(
required=False,
@@ -252,6 +255,19 @@ class OrderPositionAddForm(forms.Form):
else:
del self.fields['addon_to']
self.fields['seat'].queryset = order.event.seats.all()
self.fields['seat'].widget = Select2(
attrs={
'data-model-select2': 'seat',
'data-select2-url': reverse('control:event.seats.select2', kwargs={
'event': order.event.slug,
'organizer': order.event.organizer.slug,
}),
'data-placeholder': _('General admission')
}
)
self.fields['seat'].widget.choices = self.fields['seat'].choices
if order.event.has_subevents:
self.fields['subevent'].queryset = order.event.subevents.all()
self.fields['subevent'].widget = Select2(
@@ -302,9 +318,10 @@ class OrderPositionChangeForm(forms.Form):
required=False,
empty_label=_('(Unchanged)')
)
seat = forms.CharField(
seat = forms.ModelChoiceField(
Seat.objects.none(),
required=False,
widget=forms.TextInput(attrs={'placeholder': _('(Unchanged)'), 'data-seat-guid-field': 'true'})
empty_label=_('(Unchanged)')
)
price = forms.DecimalField(
required=False,
@@ -349,7 +366,20 @@ class OrderPositionChangeForm(forms.Form):
else:
del self.fields['subevent']
if not instance.seat:
if instance.seat:
self.fields['seat'].queryset = instance.order.event.seats.all()
self.fields['seat'].widget = Select2(
attrs={
'data-model-select2': 'seat',
'data-select2-url': reverse('control:event.seats.select2', kwargs={
'event': instance.order.event.slug,
'organizer': instance.order.event.organizer.slug,
}),
'data-placeholder': _('(Unchanged)')
}
)
self.fields['seat'].widget.choices = self.fields['seat'].choices
else:
del self.fields['seat']
choices = [

View File

@@ -1,9 +1,7 @@
from datetime import timedelta
from urllib.parse import urlencode
from django import forms
from django.forms import formset_factory
from django.urls import reverse
from django.utils.dates import MONTHS, WEEKDAYS
from django.utils.functional import cached_property
from django.utils.timezone import now
@@ -173,12 +171,6 @@ class SubEventMetaValueForm(forms.ModelForm):
super().__init__(*args, **kwargs)
self.fields['value'].required = False
self.fields['value'].widget.attrs['placeholder'] = self.default or self.property.default
self.fields['value'].widget.attrs['data-typeahead-url'] = (
reverse('control:events.meta.typeahead') + '?' + urlencode({
'property': self.property.name,
'organizer': self.property.organizer.slug,
})
)
class Meta:
model = SubEventMetaValue

View File

@@ -38,7 +38,7 @@ class VoucherForm(I18nModelForm):
localized_fields = '__all__'
fields = [
'code', 'valid_until', 'block_quota', 'allow_ignore_quota', 'value', 'tag',
'comment', 'max_usages', 'price_mode', 'subevent', 'show_hidden_items', 'budget'
'comment', 'max_usages', 'price_mode', 'subevent', 'show_hidden_items',
]
field_classes = {
'valid_until': SplitDateTimeField,
@@ -268,7 +268,7 @@ class VoucherBulkForm(VoucherForm):
localized_fields = '__all__'
fields = [
'valid_until', 'block_quota', 'allow_ignore_quota', 'value', 'tag', 'comment',
'max_usages', 'price_mode', 'subevent', 'show_hidden_items', 'budget'
'max_usages', 'price_mode', 'subevent', 'show_hidden_items'
]
field_classes = {
'valid_until': SplitDateTimeField,

View File

@@ -178,7 +178,6 @@ def _display_checkin(event, logentry):
@receiver(signal=logentry_display, dispatch_uid="pretixcontrol_logentry_display")
def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
plains = {
'pretix.object.cloned': _('This object has been created by cloning.'),
'pretix.event.comment': _('The event\'s internal comment has been updated.'),
'pretix.event.order.modified': _('The order details have been changed.'),
'pretix.event.order.unpaid': _('The order has been marked as unpaid.'),
@@ -325,8 +324,6 @@ def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
'pretix.device.initialized': _('The device has been initialized.'),
'pretix.device.keyroll': _('The access token of the device has been regenerated.'),
'pretix.device.updated': _('The device has notified the server of an hardware or software update.'),
'pretix.giftcards.created': _('The gift card has been created.'),
'pretix.giftcards.transaction.manual': _('A manual transaction has been performed.'),
}
data = json.loads(logentry.data)

View File

@@ -169,57 +169,6 @@ def get_event_navigation(request: HttpRequest):
})
if 'can_view_orders' in request.eventpermset:
children = [
{
'label': _('All orders'),
'url': reverse('control:event.orders', kwargs={
'event': request.event.slug,
'organizer': request.event.organizer.slug,
}),
'active': url.url_name in ('event.orders', 'event.order') or "event.order." in url.url_name,
},
{
'label': _('Overview'),
'url': reverse('control:event.orders.overview', kwargs={
'event': request.event.slug,
'organizer': request.event.organizer.slug,
}),
'active': 'event.orders.overview' in url.url_name,
},
{
'label': _('Refunds'),
'url': reverse('control:event.orders.refunds', kwargs={
'event': request.event.slug,
'organizer': request.event.organizer.slug,
}),
'active': 'event.orders.refunds' in url.url_name,
},
{
'label': _('Export'),
'url': reverse('control:event.orders.export', kwargs={
'event': request.event.slug,
'organizer': request.event.organizer.slug,
}),
'active': 'event.orders.export' in url.url_name,
},
{
'label': _('Waiting list'),
'url': reverse('control:event.orders.waitinglist', kwargs={
'event': request.event.slug,
'organizer': request.event.organizer.slug,
}),
'active': 'event.orders.waitinglist' in url.url_name,
},
]
if 'can_change_orders' in request.eventpermset:
children.append({
'label': _('Import'),
'url': reverse('control:event.orders.import', kwargs={
'event': request.event.slug,
'organizer': request.event.organizer.slug,
}),
'active': 'event.orders.import' in url.url_name,
})
nav.append({
'label': _('Orders'),
'url': reverse('control:event.orders', kwargs={
@@ -228,7 +177,48 @@ def get_event_navigation(request: HttpRequest):
}),
'active': False,
'icon': 'shopping-cart',
'children': children
'children': [
{
'label': _('All orders'),
'url': reverse('control:event.orders', kwargs={
'event': request.event.slug,
'organizer': request.event.organizer.slug,
}),
'active': url.url_name in ('event.orders', 'event.order') or "event.order." in url.url_name,
},
{
'label': _('Overview'),
'url': reverse('control:event.orders.overview', kwargs={
'event': request.event.slug,
'organizer': request.event.organizer.slug,
}),
'active': 'event.orders.overview' in url.url_name,
},
{
'label': _('Refunds'),
'url': reverse('control:event.orders.refunds', kwargs={
'event': request.event.slug,
'organizer': request.event.organizer.slug,
}),
'active': 'event.orders.refunds' in url.url_name,
},
{
'label': _('Export'),
'url': reverse('control:event.orders.export', kwargs={
'event': request.event.slug,
'organizer': request.event.organizer.slug,
}),
'active': 'event.orders.export' in url.url_name,
},
{
'label': _('Waiting list'),
'url': reverse('control:event.orders.waitinglist', kwargs={
'event': request.event.slug,
'organizer': request.event.organizer.slug,
}),
'active': 'event.orders.waitinglist' in url.url_name,
},
]
})
if 'can_view_vouchers' in request.eventpermset:

View File

@@ -22,16 +22,9 @@
{% csrf_token %}
{% bootstrap_form form %}
<div class="form-group buttons">
{% if backend.login_form_fields %}
<button type="submit" class="btn btn-primary btn-block">
{% trans "Log in" %}
</button>
{% endif %}
{% if backend.url %}
<a href="{{ backend.url }}" class="btn btn-primary btn-block">
{{ backend.verbose_name }}
</a>
{% endif %}
{% if backend.identifier == "native" %}
{% if can_reset %}
<a href="{% url "control:auth.forgot" %}" class="btn btn-link btn-block">

View File

@@ -24,6 +24,12 @@
{% endblocktrans %}
</p>
{% bootstrap_field form.slug layout="inline" %}
<p>
{% blocktrans trimmed with slug=request.event.slug %}
Also, to make sure it's really you, please enter your user password here:
{% endblocktrans %}
</p>
{% bootstrap_field form.user_pw layout="inline" %}
<div class="form-group submit-group">
<button type="submit" class="btn btn-danger btn-save">

View File

@@ -73,22 +73,13 @@
</h3>
</div>
<div class="panel-body">
<div class="form-order-change" data-pricecalc-endpoint="{% url "api-v1:orderposition-price_calc" organizer=order.event.organizer.slug event=order.event.slug pk=position.pk %}" {% if position.subevent %}data-subevent="{{ position.subevent.id }}"{% endif %} data-position="{{ position.pk }}">
<div class="form-order-change" data-pricecalc-endpoint="{% url "api-v1:orderposition-price_calc" organizer=order.event.organizer.slug event=order.event.slug pk=position.pk %}" {% if position.subevent %}data-subevent="{{ position.subevent.id }}{% endif %}">
{% bootstrap_form_errors position.form %}
{% if position.custom_error %}
<div class="alert alert-danger">
{{ position.custom_error }}
</div>
{% endif %}
{% if position.voucher and position.voucher.budget %}
<div class="alert alert-warning">
{% blocktrans trimmed %}
This position has been created with a voucher with a limited budget. If you
change the price or item, the discount will still be calculated from the original
price at the time of purchase.
{% endblocktrans %}
</div>
{% endif %}
<div class="row">
<div class="col-sm-5 col-sm-offset-3">
<strong>{% trans "Current value" %}</strong>
@@ -190,7 +181,7 @@
{% bootstrap_formset_errors add_formset %}
<div data-formset-body>
{% for add_form in add_formset %}
<div class="panel panel-default items" data-formset-form data-subevent="0">
<div class="panel panel-default items" data-formset-form>
<div class="panel-heading">
<h3 class="panel-title">
<button type="button" class="btn btn-danger btn-xs pull-right flip"
@@ -228,7 +219,7 @@
</div>
<script type="form-template" data-formset-empty-form>
{% escapescript %}
<div class="panel panel-default items" data-formset-form data-subevent="0">
<div class="panel panel-default items" data-formset-form>
<div class="panel-heading">
<h3 class="panel-title">
<button type="button" class="btn btn-danger btn-xs pull-right flip"

View File

@@ -272,9 +272,7 @@
{% endif %}
{% if line.voucher %}
<br/><span class="fa fa-tags"></span> {% trans "Voucher code used:" %}
<a
{% if line.price_before_voucher|default_if_none:"NONE" != "NONE" %}data-toggle="tooltip" title="{% blocktrans trimmed with price=line.price_before_voucher|money:request.event.currency %}Original price: {{ price }}{% endblocktrans %}"{% endif %}
href="{% url "control:event.voucher" event=request.event.slug organizer=request.event.organizer.slug voucher=line.voucher.pk %}">
<a href="{% url "control:event.voucher" event=request.event.slug organizer=request.event.organizer.slug voucher=line.voucher.pk %}">
{{ line.voucher.code }}
</a>
{% endif %}

View File

@@ -1,61 +0,0 @@
{% extends "pretixcontrol/event/base.html" %}
{% load i18n %}
{% load static %}
{% load getitem %}
{% load bootstrap3 %}
{% block title %}{% trans "Import attendees" %}{% endblock %}
{% block content %}
<h1>{% trans "Import attendees" %}</h1>
<form action="" method="post" class="form-horizontal" data-asynctask data-asynctask-long>
{% csrf_token %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{% trans "Data preview" %}</h3>
</div>
<div class="table-responsive panel-body">
<table class="table table-condensed">
<thead>
<tr>
{% for fn in parsed.fieldnames %}
<th>{{ fn }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for r in sample_rows %}
<tr>
{% for fn in parsed.fieldnames %}
<td>{{ r|getitem:fn }}</td>
{% endfor %}
</tr>
{% endfor %}
<tr>
<td class="text-center" colspan="{{ parsed.fieldnames|length }}">
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{% trans "Import settings" %}</h3>
</div>
<div class="panel-body">
{% bootstrap_form_errors form %}
{% bootstrap_form form layout="horizontal" %}
<div class="alert alert-warning">
{% blocktrans trimmed %}
The import will be performed regardless of your quotas, so it will be possible to overbook your event using this option.
{% endblocktrans %}
</div>
</div>
</div>
<div class="form-group submit-group">
<button type="submit" class="btn btn-primary btn-save">
{% trans "Perform import" %}
</button>
</div>
</form>
{% endblock %}

View File

@@ -1,31 +0,0 @@
{% extends "pretixcontrol/event/base.html" %}
{% load i18n %}
{% load static %}
{% block title %}{% trans "Import attendees" %}{% endblock %}
{% block content %}
<h1>{% trans "Import attendees" %}</h1>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{% trans "Upload a new file" %}</h3>
</div>
<div class="panel-body">
<form action="" method="post" enctype="multipart/form-data" class="form-inline">
{% csrf_token %}
<p>
{% blocktrans trimmed %}
The uploaded file should be a CSV file with a header row. You will be able to assign the
meanings of the different columns in the next step.
{% endblocktrans %}
</p>
<div class="form-group">
<label for="file">{% trans "Import file" %}: </label> <input id="file" type="file" name="file"/>
</div>
<div class="clearfix"></div>
<button class="btn btn-primary pull-right flip" type="submit">
<span class="icon icon-upload"></span> {% trans "Start import" %}
</button>
</form>
</div>
</div>
{% endblock %}

View File

@@ -11,95 +11,81 @@
<span class="label label-warning">{% trans "TEST MODE" %}</span>
{% endif %}
</h1>
<div class="row">
<div class="col-md-10 col-xs-12">
<div class="panel panel-primary items">
<div class="panel-heading">
<h3 class="panel-title">
{% trans "Details" %}
</h3>
</div>
<div class="panel-body">
<dl class="dl-horizontal">
<dt>{% trans "Gift card code" %}</dt>
<dd>{{ card.secret }}</dd>
<dt>{% trans "Creation date" %}</dt>
<dd>{{ card.issuance|date:"SHORT_DATETIME_FORMAT" }}</dd>
<dt>{% trans "Current value" %}</dt>
<dd>{{ card.value|money:card.currency }}</dd>
<dt>{% trans "Currency" %}</dt>
<dd>{{ card.currency }}</dd>
{% if card.issued_in %}
<dt>{% trans "Issued through sale" %}</dt>
<dd>
<a href="{% url "control:event.order" event=card.issued_in.order.event.slug organizer=card.issued_in.order.event.organizer.slug code=card.issued_in.order.code %}">
{{ card.issued_in.order.full_code }}</a>-{{ card.issued_in.positionid }}
</dd>
{% endif %}
</dl>
</div>
</div>
<div class="panel panel-default items">
<div class="panel-heading">
<h3 class="panel-title">
{% trans "Transactions" %}
</h3>
</div>
<table class="panel-body table">
<thead>
<tr>
<th>{% trans "Date" %}</th>
<th>{% trans "Order" %}</th>
<th class="text-right">{% trans "Value" %}</th>
</tr>
</thead>
<tbody>
{% for t in card.transactions.all %}
<tr>
<td>{{ t.datetime|date:"SHORT_DATETIME_FORMAT" }}</td>
<td>
{% if t.order %}
<a href="{% url "control:event.order" event=t.order.event.slug organizer=t.order.event.organizer.slug code=t.order.code %}">
{{ t.order.full_code }}
</a>
{% else %}
<em>{% trans "Manual transaction" %}</em>
{% endif %}
</td>
<td class="text-right">
{{ t.value|money:card.currency }}
</td>
</tr>
{% endfor %}
</tbody>
<tfoot>
<tr>
<td></td>
<td></td>
<td class="text-right">
<form class="helper-display-inline form-inline" method="post" action="">
{% csrf_token %}
<input type="text" class="form-control input-sm" placeholder="{% trans "Value" %}" name="value">
<button class="btn btn-primary">
<span class="fa fa-plus"></span>
</button>
</form>
</td>
</tr>
</tfoot>
</table>
</div>
<div class="panel panel-primary items">
<div class="panel-heading">
<h3 class="panel-title">
{% trans "Details" %}
</h3>
</div>
<div class="col-md-2 col-xs-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
{% trans "Gift card history" %}
</h3>
</div>
{% include "pretixcontrol/includes/logs.html" with obj=card %}
</div>
<div class="panel-body">
<dl class="dl-horizontal">
<dt>{% trans "Gift card code" %}</dt>
<dd>{{ card.secret }}</dd>
<dt>{% trans "Creation date" %}</dt>
<dd>{{ card.issuance|date:"SHORT_DATETIME_FORMAT" }}</dd>
<dt>{% trans "Current value" %}</dt>
<dd>{{ card.value|money:card.currency }}</dd>
<dt>{% trans "Currency" %}</dt>
<dd>{{ card.currency }}</dd>
{% if card.issued_in %}
<dt>{% trans "Issued through sale" %}</dt>
<dd>
<a href="{% url "control:event.order" event=card.issued_in.order.event.slug organizer=card.issued_in.order.event.organizer.slug code=card.issued_in.order.code %}">
{{ card.issued_in.order.full_code }}</a>-{{ card.issued_in.positionid }}
</dd>
{% endif %}
</dl>
</div>
</div>
<div class="panel panel-default items">
<div class="panel-heading">
<h3 class="panel-title">
{% trans "Transactions" %}
</h3>
</div>
<table class="panel-body table">
<thead>
<tr>
<th>{% trans "Date" %}</th>
<th>{% trans "Order" %}</th>
<th class="text-right">{% trans "Value" %}</th>
</tr>
</thead>
<tbody>
{% for t in card.transactions.all %}
<tr>
<td>{{ t.datetime|date:"SHORT_DATETIME_FORMAT" }}</td>
<td>
{% if t.order %}
<a href="{% url "control:event.order" event=t.order.event.slug organizer=t.order.event.organizer.slug code=t.order.code %}">
{{ t.order.full_code }}
</a>
{% else %}
<em>{% trans "Manual transaction" %}</em>
{% endif %}
</td>
<td class="text-right">
{{ t.value|money:card.currency }}
</td>
</tr>
{% endfor %}
</tbody>
<tfoot>
<tr>
<td></td>
<td></td>
<td class="text-right">
<form class="helper-display-inline form-inline" method="post" action="">
{% csrf_token %}
<input type="text" class="form-control input-sm" placeholder="{% trans "Value" %}" name="value">
<button class="btn btn-primary">
<span class="fa fa-plus"></span>
</button>
</form>
</td>
</tr>
</tfoot>
</table>
</div>
{% endblock %}

View File

@@ -198,24 +198,11 @@
</div>
<div class="col-sm-12 help-inline">
<p>
{% blocktrans trimmed %}
After you changed the page size, you need to create a new empty background. If you
want to use a custom background, it already needs to have the correct size.
{% endblocktrans %}
After you changed the page size, you need to create a new empty background. If you
want to use a custom background, it already needs to have the correct size.
</p>
</div>
</div>
<div class="row control-group pdf-info">
<div class="col-sm-12">
<label>{% trans "Prefered language" %}</label><br>
<select class="form-control" id="pdf-info-locale">
<option value="">{% trans "Order locale" %}</option>
{% for l in locales %}
<option value="{{ l.0 }}">{{ l.1 }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="row control-group position">
<div class="col-sm-6">
<label>{% trans "x (mm)" %}</label><br>
@@ -264,12 +251,6 @@
<span class="fa fa-italic"></span>
</button>
</div>
<div class="btn-group" role="group">
<button type="button" class="btn btn-default toggling" data-action="downward"
data-toggle="tooltip" title="{% trans "Flow multiple lines downward from specified position" %}">
<span class="fa fa-caret-square-o-down"></span>
</button>
</div>
</div>
</div>
</div>
@@ -312,16 +293,11 @@
</div>
</div>
<div class="row control-group text">
<div class="col-sm-6">
<div class="col-sm-12">
<label>{% trans "Width (mm)" %}</label><br>
<input type="number" value="13" class="input-block-level form-control" step="0.01"
id="toolbox-textwidth">
</div>
<div class="col-sm-6">
<label>{% trans "Rotation (°)" %}</label><br>
<input type="number" value="0" class="input-block-level form-control" step="0.1"
id="toolbox-textrotation">
</div>
</div>
<div class="row control-group poweredby">
<div class="col-sm-12">

View File

@@ -40,12 +40,12 @@
<fieldset>
<legend>{% trans "Step 3: Confirm deletion" %}</legend>
<p>
{% blocktrans trimmed with event=request.event.name slug=request.event.slug %}
{% blocktrans trimmed with event=request.event.name %}
Please re-check that you are fully certain that you want to delete the selected categories of data from the event <strong>{{ event }}</strong>.
To confirm you really want this, please type out the event's short name ("{{ slug }}") here:
In this case, please enter your user password here:
{% endblocktrans %}
</p>
<input type="text" class="form-control" name="slug" required placeholder="{% trans "Event short name" %}">
<input type="password" class="form-control" name="password" required placeholder="{% trans "Your password" %}">
</fieldset>
<input type="hidden" name="file" value="{{ file.pk }}">
<div class="form-group submit-group">

View File

@@ -8,11 +8,7 @@
{% csrf_token %}
<h3>{% trans "Welcome back!" %}</h3>
<p>
{% if form.backend.url %}
{% blocktrans trimmed with login_provider=form.backend.verbose_name %}We just want to make sure it's really you. Please re-authenticate with '{{ login_provider }}'.{% endblocktrans %}
{% else %}
{% trans "We just want to make sure it's really you. Please re-enter your password to continue." %}
{% endif %}
{% trans "We just want to make sure it's really you. Please re-enter your password to continue." %}
</p>
{% bootstrap_form form %}
<input class="form-control" id="webauthn-response" name="webauthn"
@@ -27,15 +23,9 @@
</small></p>
{% endif %}
<div class="form-group text-right flip">
{% if form.backend.url %}
<a href="{{ form.backend.url }}" class="btn btn-primary btn-block">
{% trans "Continue" %}
</a>
{% else %}
<button type="submit" class="btn btn-primary btn-block">
{% trans "Continue" %}
</button>
{% endif %}
<button type="submit" class="btn btn-primary btn-block">
{% trans "Continue" %}
</button>
<a href="{% url "control:auth.logout" %}" class="btn btn-link btn-block">
{% trans "Log in as someone else" %}
</a>

View File

@@ -73,7 +73,6 @@
<legend>{% trans "Advanced settings" %}</legend>
{% bootstrap_field form.block_quota layout="control" %}
{% bootstrap_field form.allow_ignore_quota layout="control" %}
{% bootstrap_field form.budget addon_after=request.event.currency layout="control" %}
{% bootstrap_field form.tag layout="control" %}
{% bootstrap_field form.comment layout="control" %}
{% bootstrap_field form.show_hidden_items layout="control" %}

View File

@@ -75,7 +75,6 @@
<legend>{% trans "Advanced settings" %}</legend>
{% bootstrap_field form.block_quota layout="control" %}
{% bootstrap_field form.allow_ignore_quota layout="control" %}
{% bootstrap_field form.budget addon_after=request.event.currency layout="control" %}
{% bootstrap_field form.tag layout="control" %}
{% bootstrap_field form.comment layout="control" %}
{% bootstrap_field form.show_hidden_items layout="control" %}

View File

@@ -2,7 +2,6 @@
{% load i18n %}
{% load bootstrap3 %}
{% load urlreplace %}
{% load money %}
{% block title %}{% trans "Vouchers" %}{% endblock %}
{% block content %}
<h1>{% trans "Vouchers" %}</h1>
@@ -144,15 +143,7 @@
<strong><a href="{% url "control:event.voucher" organizer=request.event.organizer.slug event=request.event.slug voucher=v.id %}">{{ v.code }}</a></strong>
{% if not v.is_active %}</del>{% endif %}
</td>
<td>
{{ v.redeemed }} / {{ v.max_usages }}
{% if v.budget|default_if_none:"NONE" != "NONE" %}
<br>
<small class="text-muted">
{{ v.budget_used_orders|money:request.event.currency }} / {{ v.budget|money:request.event.currency }}
</small>
{% endif %}
</td>
<td>{{ v.redeemed }} / {{ v.max_usages }}</td>
<td>{{ v.valid_until|date }}</td>
<td>
{{ v.tag }}

View File

@@ -1,11 +0,0 @@
from django import template
register = template.Library()
@register.filter(name='getitem')
def getitem_filter(value, itemname):
if not value:
return ''
return value[itemname]

View File

@@ -2,8 +2,8 @@ from django.conf.urls import include, url
from pretix.control.views import (
auth, checkin, dashboards, event, geo, global_settings, item, main, oauth,
orderimport, orders, organizer, pdf, search, shredder, subevents,
typeahead, user, users, vouchers, waitinglist,
orders, organizer, pdf, search, shredder, subevents, typeahead, user,
users, vouchers, waitinglist,
)
urlpatterns = [
@@ -147,6 +147,7 @@ urlpatterns = [
url(r'^pdf/editor/(?P<filename>[^/]+).pdf$', pdf.PdfView.as_view(), name='pdf.background'),
url(r'^subevents/$', subevents.SubEventList.as_view(), name='event.subevents'),
url(r'^subevents/select2$', typeahead.subevent_select2, name='event.subevents.select2'),
url(r'^seats/select2$', typeahead.seat_select2, name='event.seats.select2'),
url(r'^subevents/(?P<subevent>\d+)/$', subevents.SubEventUpdate.as_view(), name='event.subevent'),
url(r'^subevents/(?P<subevent>\d+)/delete$', subevents.SubEventDelete.as_view(),
name='event.subevent.delete'),
@@ -256,8 +257,6 @@ urlpatterns = [
url(r'^invoice/(?P<invoice>[^/]+)$', orders.InvoiceDownload.as_view(),
name='event.invoice.download'),
url(r'^orders/overview/$', orders.OverView.as_view(), name='event.orders.overview'),
url(r'^orders/import/$', orderimport.ImportView.as_view(), name='event.orders.import'),
url(r'^orders/import/(?P<file>[^/]+)/$', orderimport.ProcessView.as_view(), name='event.orders.import.process'),
url(r'^orders/export/$', orders.ExportView.as_view(), name='event.orders.export'),
url(r'^orders/export/do$', orders.ExportDoView.as_view(), name='event.orders.export.do'),
url(r'^orders/refunds/$', orders.RefundList.as_view(), name='event.orders.refunds'),

View File

@@ -44,7 +44,6 @@ from pretix.control.forms.event import (
TicketSettingsForm, WidgetCodeForm,
)
from pretix.control.permissions import EventPermissionRequiredMixin
from pretix.control.views.user import RecentAuthenticationRequiredMixin
from pretix.helpers.database import rolledback_transaction
from pretix.multidomain.urlreverse import get_domain
from pretix.plugins.stripe.payment import StripeSettingsHolder
@@ -825,7 +824,7 @@ class EventLive(EventPermissionRequiredMixin, TemplateView):
})
class EventDelete(RecentAuthenticationRequiredMixin, EventPermissionRequiredMixin, FormView):
class EventDelete(EventPermissionRequiredMixin, FormView):
permission = 'can_change_event_settings'
template_name = 'pretixcontrol/event/delete.html'
form_class = EventDeleteForm
@@ -838,6 +837,7 @@ class EventDelete(RecentAuthenticationRequiredMixin, EventPermissionRequiredMixi
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['user'] = self.request.user
kwargs['event'] = self.request.event
return kwargs

View File

@@ -1,125 +0,0 @@
import logging
from datetime import timedelta
from django.conf import settings
from django.contrib import messages
from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse
from django.utils.functional import cached_property
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _
from django.views.generic import FormView, TemplateView
from pretix.base.models import CachedFile
from pretix.base.services.orderimport import import_orders, parse_csv
from pretix.base.views.tasks import AsyncAction
from pretix.control.forms.orderimport import ProcessForm
from pretix.control.permissions import EventPermissionRequiredMixin
logger = logging.getLogger(__name__)
class ImportView(EventPermissionRequiredMixin, TemplateView):
template_name = 'pretixcontrol/orders/import_start.html'
permission = 'can_change_orders'
def post(self, request, *args, **kwargs):
if 'file' not in request.FILES:
return redirect(reverse('control:event.orders.import', kwargs={
'event': request.event.slug,
'organizer': request.organizer.slug,
}))
if not request.FILES['file'].name.endswith('.csv'):
messages.error(request, _('Please only upload CSV files.'))
return redirect(reverse('control:event.orders.import', kwargs={
'event': request.event.slug,
'organizer': request.organizer.slug,
}))
if request.FILES['file'].size > 1024 * 1024 * 10:
messages.error(request, _('Please do not upload files larger than 10 MB.'))
return redirect(reverse('control:event.orders.import', kwargs={
'event': request.event.slug,
'organizer': request.organizer.slug,
}))
cf = CachedFile.objects.create(
expires=now() + timedelta(days=1),
date=now(),
filename='import.csv',
type='text/csv',
)
cf.file.save('import.csv', request.FILES['file'])
return redirect(reverse('control:event.orders.import.process', kwargs={
'event': request.event.slug,
'organizer': request.organizer.slug,
'file': cf.id
}))
class ProcessView(EventPermissionRequiredMixin, AsyncAction, FormView):
permission = 'can_change_orders'
template_name = 'pretixcontrol/orders/import_process.html'
form_class = ProcessForm
task = import_orders
known_errortypes = ['DataImportError']
def get_form_kwargs(self):
k = super().get_form_kwargs()
k.update({
'event': self.request.event,
'initial': self.request.event.settings.order_import_settings,
'headers': self.parsed.fieldnames
})
return k
def form_valid(self, form):
self.request.event.settings.order_import_settings = form.cleaned_data
return self.do(
self.request.event.pk, self.file.id, form.cleaned_data, self.request.LANGUAGE_CODE,
self.request.user.pk
)
@cached_property
def file(self):
return get_object_or_404(CachedFile, pk=self.kwargs.get("file"), filename="import.csv")
@cached_property
def parsed(self):
return parse_csv(self.file.file, 1024 * 1024)
def get(self, request, *args, **kwargs):
if 'async_id' in request.GET and settings.HAS_CELERY:
return self.get_result(request)
return FormView.get(self, request, *args, **kwargs)
def get_success_message(self, value):
return _('The import was successful.')
def get_success_url(self, value):
return reverse('control:event.orders', kwargs={
'event': self.request.event.slug,
'organizer': self.request.organizer.slug,
})
def dispatch(self, request, *args, **kwargs):
if not self.parsed:
messages.error(request, _('We\'ve been unable to parse the uploaded file as a CSV file.'))
return redirect(reverse('control:event.orders.import', kwargs={
'event': request.event.slug,
'organizer': request.organizer.slug,
}))
return super().dispatch(request, *args, **kwargs)
def get_error_url(self):
return reverse('control:event.orders.import.process', kwargs={
'event': self.request.event.slug,
'organizer': self.request.organizer.slug,
'file': self.file.id
})
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['file'] = self.file
ctx['parsed'] = self.parsed
ctx['sample_rows'] = list(self.parsed)[:3]
return ctx

View File

@@ -724,7 +724,6 @@ class OrderRefundView(OrderView):
currency=self.request.event.currency,
testmode=self.order.testmode
)
giftcard.log_action('pretix.giftcards.created', user=self.request.user, data={})
refunds.append(OrderRefund(
order=self.order,
payment=None,
@@ -1304,7 +1303,6 @@ class OrderChange(OrderView):
positions = list(self.order.positions.all())
for p in positions:
p.form = OrderPositionChangeForm(prefix='op-{}'.format(p.pk), instance=p,
initial={'seat': p.seat.seat_guid if p.seat else None},
data=self.request.POST if self.request.method == "POST" else None)
try:
ia = self.order.invoice_address
@@ -1400,12 +1398,12 @@ class OrderChange(OrderView):
if item != p.item or variation != p.variation:
ocm.change_item(p, item, variation)
if p.seat and p.form.cleaned_data['seat'] and p.form.cleaned_data['seat'] != p.seat:
ocm.change_seat(p, p.form.cleaned_data['seat'])
if self.request.event.has_subevents and p.form.cleaned_data['subevent'] and p.form.cleaned_data['subevent'] != p.subevent:
ocm.change_subevent(p, p.form.cleaned_data['subevent'])
if p.seat and p.form.cleaned_data['seat'] and p.form.cleaned_data['seat'] != p.seat.seat_guid:
ocm.change_seat(p, p.form.cleaned_data['seat'])
if p.form.cleaned_data['price'] != p.price:
ocm.change_price(p, p.form.cleaned_data['price'])

View File

@@ -28,7 +28,6 @@ from pretix.base.models import (
Device, GiftCard, Organizer, Team, TeamInvite, User,
)
from pretix.base.models.event import Event, EventMetaProperty, EventMetaValue
from pretix.base.models.giftcards import gen_giftcard_secret
from pretix.base.models.organizer import TeamAPIToken
from pretix.base.services.mail import SendMailException, mail
from pretix.control.forms.filter import (
@@ -689,7 +688,7 @@ class DeviceListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin,
def get_queryset(self):
return self.request.organizer.devices.prefetch_related(
'limit_events'
).order_by('revoked', '-device_id')
).order_by('-device_id')
class DeviceCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, CreateView):
@@ -1039,8 +1038,7 @@ class GiftCardCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMi
kwargs = super().get_form_kwargs()
any_event = self.request.organizer.events.first()
kwargs['initial'] = {
'currency': any_event.currency if any_event else settings.DEFAULT_CURRENCY,
'secret': gen_giftcard_secret(self.request.organizer.settings.giftcard_length)
'currency': any_event.currency if any_event else settings.DEFAULT_CURRENCY
}
kwargs['organizer'] = self.request.organizer
return kwargs
@@ -1056,11 +1054,9 @@ class GiftCardCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMi
form.instance.transactions.create(
value=form.cleaned_data['value']
)
form.instance.log_action('pretix.giftcards.created', user=self.request.user, data={})
if form.cleaned_data['value']:
form.instance.log_action('pretix.giftcards.transaction.manual', user=self.request.user, data={
'value': form.cleaned_data['value']
})
form.instance.log_action('pretix.giftcards.transaction.manual', user=self.request.user, data={
'value': form.cleaned_data['value']
})
return redirect(reverse(
'control:organizer.giftcard',
kwargs={

View File

@@ -4,7 +4,6 @@ import mimetypes
from datetime import timedelta
from io import BytesIO
from django.conf import settings
from django.core.files import File
from django.core.files.base import ContentFile
from django.core.files.storage import default_storage
@@ -218,7 +217,6 @@ class BaseEditorView(EventPermissionRequiredMixin, TemplateView):
ctx['variables'] = self.get_variables()
ctx['layout'] = json.dumps(self.get_current_layout())
ctx['title'] = self.title
ctx['locales'] = [p for p in settings.LANGUAGES if p[0] in self.request.event.settings.locales]
return ctx

View File

@@ -13,7 +13,6 @@ from pretix.base.services.shredder import export, shred
from pretix.base.shredder import ShredError, shred_constraints
from pretix.base.views.tasks import AsyncAction
from pretix.control.permissions import EventPermissionRequiredMixin
from pretix.control.views.user import RecentAuthenticationRequiredMixin
logger = logging.getLogger(__name__)
@@ -27,7 +26,7 @@ class ShredderMixin:
)
class StartShredView(RecentAuthenticationRequiredMixin, EventPermissionRequiredMixin, ShredderMixin, TemplateView):
class StartShredView(EventPermissionRequiredMixin, ShredderMixin, TemplateView):
permission = 'can_change_orders'
template_name = 'pretixcontrol/shredder/index.html'
@@ -38,7 +37,7 @@ class StartShredView(RecentAuthenticationRequiredMixin, EventPermissionRequiredM
return ctx
class ShredDownloadView(RecentAuthenticationRequiredMixin, EventPermissionRequiredMixin, ShredderMixin, TemplateView):
class ShredDownloadView(EventPermissionRequiredMixin, ShredderMixin, TemplateView):
permission = 'can_change_orders'
template_name = 'pretixcontrol/shredder/download.html'
@@ -49,7 +48,7 @@ class ShredDownloadView(RecentAuthenticationRequiredMixin, EventPermissionRequir
return ctx
class ShredExportView(RecentAuthenticationRequiredMixin, EventPermissionRequiredMixin, ShredderMixin, AsyncAction, View):
class ShredExportView(EventPermissionRequiredMixin, ShredderMixin, AsyncAction, View):
permission = 'can_change_orders'
task = export
known_errortypes = ['ShredError']
@@ -78,7 +77,7 @@ class ShredExportView(RecentAuthenticationRequiredMixin, EventPermissionRequired
return self.do(self.request.event.id, request.POST.getlist("shredder"))
class ShredDoView(RecentAuthenticationRequiredMixin, EventPermissionRequiredMixin, ShredderMixin, AsyncAction, View):
class ShredDoView(EventPermissionRequiredMixin, ShredderMixin, AsyncAction, View):
permission = 'can_change_orders'
task = shred
known_errortypes = ['ShredError']
@@ -104,7 +103,7 @@ class ShredDoView(RecentAuthenticationRequiredMixin, EventPermissionRequiredMixi
if constr:
return self.error(ShredError(self.get_error_url()))
if request.event.slug != request.POST.get("slug"):
return self.error(ShredError(_("The slug you entered was not correct.")))
if not self.request.user.check_password(request.POST.get("password")):
return self.error(ShredError(_("The current password you entered was not correct.")))
return self.do(self.request.event.id, request.POST.get("file"), request.POST.get("confirm_code"))

View File

@@ -13,7 +13,8 @@ from django.utils.timezone import make_aware
from django.utils.translation import pgettext, ugettext as _
from pretix.base.models import (
EventMetaProperty, EventMetaValue, Order, Organizer, User, Voucher,
EventMetaProperty, EventMetaValue, Order, Organizer, SubEvent, User,
Voucher,
)
from pretix.control.forms.event import EventWizardCopyForm
from pretix.control.permissions import event_permission_required
@@ -224,6 +225,46 @@ def nav_context_list(request):
return JsonResponse(doc)
@event_permission_required("can_view_orders")
def seat_select2(request, **kwargs):
query = request.GET.get('query', '')
try:
page = int(request.GET.get('page', '1'))
except ValueError:
page = 1
if request.event.has_subevents:
try:
qs = request.event.subevents.get(active=True, pk=request.GET.get('subevent', 0)).free_seats()
except SubEvent.DoesNotExist:
qs = request.event.seats.none()
else:
qs = request.event.free_seats()
qs = qs.filter(
Q(name__icontains=query) | Q(seat_guid__icontains=query)
).order_by('name').select_related('product', 'subevent')
total = qs.count()
pagesize = 20
offset = (page - 1) * pagesize
doc = {
'results': [
{
'id': e.pk,
'text': '{} ({})'.format(e.name, str(e.product)),
'product': e.product_id,
'event': str(e.subevent) if e.subevent else ''
}
for e in qs[offset:offset + pagesize]
],
'pagination': {
"more": total >= (offset + pagesize)
}
}
return JsonResponse(doc)
@event_permission_required(None)
def subevent_select2(request, **kwargs):
query = request.GET.get('query', '')

View File

@@ -37,11 +37,9 @@ class VoucherList(PaginationMixin, EventPermissionRequiredMixin, ListView):
permission = 'can_view_vouchers'
def get_queryset(self):
qs = Voucher.annotate_budget_used_orders(self.request.event.vouchers.filter(
waitinglistentries__isnull=True
).select_related(
qs = self.request.event.vouchers.filter(waitinglistentries__isnull=True).select_related(
'item', 'variation', 'seat'
))
)
if self.filter_form.is_valid():
qs = self.filter_form.filter_qs(qs)
@@ -67,7 +65,7 @@ class VoucherList(PaginationMixin, EventPermissionRequiredMixin, ListView):
headers = [
_('Voucher code'), _('Valid until'), _('Product'), _('Reserve quota'), _('Bypass quota'),
_('Price effect'), _('Value'), _('Tag'), _('Redeemed'), _('Maximum usages'), _('Seat')
_('Price effect'), _('Value'), _('Tag'), _('Redeemed'), _('Maximum usages')
]
writer.writerow(headers)
@@ -91,8 +89,7 @@ class VoucherList(PaginationMixin, EventPermissionRequiredMixin, ListView):
str(v.value) if v.value is not None else "",
v.tag,
str(v.redeemed),
str(v.max_usages),
str(v.seat) if v.seat else ""
str(v.max_usages)
]
writer.writerow(row)
@@ -311,7 +308,6 @@ class VoucherBulkCreate(EventPermissionRequiredMixin, CreateView):
if self.copy_from:
i = modelcopy(self.copy_from)
i.pk = None
i.redeemed = 0
kwargs['instance'] = i
else:
kwargs['instance'] = Voucher(event=self.request.event)

View File

@@ -6,13 +6,6 @@ from django.conf import settings
def set_cookie_without_samesite(request, response, key, *args, **kwargs):
assert 'samesite' not in kwargs
response.set_cookie(key, *args, **kwargs)
is_secure = (
kwargs.get('secure', False) or request.scheme == 'https' or
settings.SITE_URL.startswith('https://')
)
if not is_secure:
# https://www.chromestatus.com/feature/5633521622188032
return
if should_send_same_site_none(request.headers.get('User-Agent', '')):
# Chromium is rolling out SameSite=Lax as a default
# https://www.chromestatus.com/feature/5088147346030592
@@ -22,7 +15,10 @@ def set_cookie_without_samesite(request, response, key, *args, **kwargs):
response.cookies[key]['samesite'] = 'None'
# This will only work on secure cookies as well
# https://www.chromestatus.com/feature/5633521622188032
response.cookies[key]['secure'] = is_secure
response.cookies[key]['secure'] = (
kwargs.get('secure', False) or request.scheme == 'https' or
settings.SITE_URL.startswith('https://')
)
# Based on https://www.chromium.org/updates/same-site/incompatible-clients
@@ -52,7 +48,7 @@ def drops_unrecognized_same_site_cookies(useragent):
# Regex parsing of User-Agent string. (See note above!)
RE_CHROMIUM = re.compile(r"Chrom(e|ium)")
RE_CHROMIUM_VERSION = re.compile(r"Chrom[^ /]+[ /]([0-9]+)[.0-9]*")
RE_CHROMIUM_VERSION = re.compile(r"Chrom[^ /]+/([0-9]+)[.0-9]*")
RE_UC_VERSION = re.compile(r"UCBrowser/([0-9]+)\.([0-9]+)\.([0-9]+)[.0-9]* ")
RE_IOS_VERSION = re.compile(r"\(iP.+; CPU .*OS ([0-9]+)[_0-9]*.*\) AppleWebKit/")
RE_MAC_VERSION = re.compile(r"\(Macintosh;.*Mac OS X ([0-9]+)_([0-9]+)[_0-9]*.*\) AppleWebKit/")
@@ -90,10 +86,7 @@ def is_chromium_based(useragent):
def is_chromium_version_at_least(major, useragent):
# Extract digits from first capturing group.
match = RE_CHROMIUM_VERSION.search(useragent)
if not match:
return False
version = int(match.group(1))
version = int(RE_CHROMIUM_VERSION.search(useragent).group(1))
return version >= major

View File

@@ -8,24 +8,24 @@ msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-10-30 16:23+0000\n"
"PO-Revision-Date: 2019-12-25 01:00+0000\n"
"Last-Translator: Abdullah <abdullah.gumaijan@gmail.com>\n"
"PO-Revision-Date: 2019-12-07 15:05+0100\n"
"Last-Translator: saad91 <saad.almoosa@gmail.com>\n"
"Language-Team: Arabic <https://translate.pretix.eu/projects/pretix/pretix/ar/"
">\n"
"Language: ar\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 "
"&& n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n"
"X-Generator: Weblate 3.5.1\n"
"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && "
"n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n"
"X-Generator: Poedit 2.2.4\n"
#: htmlcov/pretix_control_views_dashboards_py.html:963
#: pretix/control/templates/pretixcontrol/events/index.html:144
#: pretix/control/templates/pretixcontrol/organizers/detail.html:98
#: pretix/control/views/dashboards.py:437
msgid "Shop disabled"
msgstr "البيع متوقف الآن"
msgstr "تسوق المعوقين"
#: htmlcov/pretix_control_views_dashboards_py.html:965
#: pretix/control/forms/filter.py:396 pretix/control/forms/filter.py:554
@@ -33,7 +33,7 @@ msgstr "البيع متوقف الآن"
#: pretix/control/templates/pretixcontrol/organizers/detail.html:100
#: pretix/control/templates/pretixcontrol/subevents/index.html:115
msgid "Presale over"
msgstr "انتهى البيع الأولي"
msgstr "[خبر] أكثر"
#: htmlcov/pretix_control_views_dashboards_py.html:967
#: pretix/control/forms/filter.py:395 pretix/control/forms/filter.py:553
@@ -41,7 +41,7 @@ msgstr "انتهى البيع الأولي"
#: pretix/control/templates/pretixcontrol/organizers/detail.html:102
#: pretix/control/templates/pretixcontrol/subevents/index.html:117
msgid "Presale not started"
msgstr "لم يبدأ البيع الأولي بعد"
msgstr "[خبر] لم تبدأ"
#: htmlcov/pretix_control_views_dashboards_py.html:969
#: pretix/control/templates/pretixcontrol/events/index.html:150
@@ -449,7 +449,7 @@ msgstr "بيانات الطلبيات"
#: pretix/control/templates/pretixcontrol/orders/index.html:7
#: pretix/control/templates/pretixcontrol/orders/index.html:9
msgid "Orders"
msgstr "الطلبات"
msgstr "أوامر"
#: pretix/base/exporters/orderlist.py:29 pretix/base/models/orders.py:1775
#: pretix/base/notifications.py:190
@@ -458,11 +458,11 @@ msgstr "مواقف ترتيب"
#: pretix/base/exporters/orderlist.py:30
msgid "Order fees"
msgstr "رسوم الطلب"
msgstr "رسوم إشتراك"
#: pretix/base/exporters/orderlist.py:39
msgid "Only paid orders"
msgstr "الطلبات المدفوعة"
msgstr "أوامر دفع فقط"
#: pretix/base/exporters/orderlist.py:91 pretix/base/exporters/orderlist.py:198
#: pretix/base/exporters/orderlist.py:273 pretix/base/exporters/orderlist.py:507
@@ -490,7 +490,7 @@ msgstr "رمز الطلب"
#: pretix/plugins/reports/exporters.py:380
#: pretix/plugins/reports/exporters.py:517
msgid "Order total"
msgstr "المجموع"
msgstr "الطلب الكلي"
#: pretix/base/exporters/orderlist.py:91 pretix/base/exporters/orderlist.py:199
#: pretix/base/exporters/orderlist.py:275 pretix/base/exporters/orderlist.py:434
@@ -572,7 +572,7 @@ msgstr "شركة"
#: pretix/presale/templates/pretixpresale/event/order.html:222
#: pretix/presale/templates/pretixpresale/organizers/index.html:56
msgid "Name"
msgstr "الاسم"
msgstr "اسم"
#: pretix/base/exporters/orderlist.py:99 pretix/base/exporters/orderlist.py:216
#: pretix/base/exporters/orderlist.py:312 pretix/base/exporters/orderlist.py:513
@@ -793,7 +793,7 @@ msgstr "تاريخ"
#: pretix/plugins/reports/exporters.py:219
#: pretix/presale/templates/pretixpresale/event/waitinglist.html:10
msgid "Product"
msgstr "التذاكر"
msgstr "المنتج"
#: pretix/base/exporters/orderlist.py:285 pretix/base/models/orders.py:991
msgid "Variation"
@@ -809,7 +809,7 @@ msgstr "الاختلاف"
#: pretix/plugins/ticketoutputpdf/exporters.py:39
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:46
msgid "Attendee name"
msgstr "الاسم"
msgstr "اسم الحضور"
#: pretix/base/exporters/orderlist.py:297 pretix/base/forms/questions.py:253
#: pretix/base/models/orders.py:1008
@@ -1243,11 +1243,11 @@ msgstr "نائب الكان خاطئ: %(value)s"
#: pretix/base/forms/widgets.py:134 pretix/base/forms/widgets.py:139
#: pretix/base/models/orders.py:2020
msgid "Business customer"
msgstr "الشركات"
msgstr "عملاء الأعمال"
#: pretix/base/forms/widgets.py:138
msgid "Individual customer"
msgstr "الأفراد"
msgstr "العملاء الفردية"
#: pretix/base/invoice.py:59
#, python-format
@@ -1308,7 +1308,7 @@ msgstr "تاريخ الفاتورة"
#: pretix/base/invoice.py:378
msgctxt "invoice"
msgid "Event"
msgstr "الفعالية"
msgstr "هدف"
#: pretix/base/invoice.py:397
#, python-brace-format
@@ -1693,7 +1693,7 @@ msgstr "سلسلة الحدث"
#: pretix/control/templates/pretixcontrol/search/orders.html:39
#: pretix/presale/templates/pretixpresale/event/waitinglist.html:18
msgid "Event"
msgstr "الفعالية"
msgstr "هدف"
#: pretix/base/models/event.py:359 pretix/control/navigation.py:295
#: pretix/control/navigation.py:397
@@ -2507,7 +2507,7 @@ msgstr "قيد الانتظار"
#: pretix/base/models/orders.py:119
msgid "paid"
msgstr "مدفوع"
msgstr "دفع"
#: pretix/base/models/orders.py:120
msgid "expired"
@@ -3446,7 +3446,7 @@ msgstr "لا يتم اعتماد مبلغ معاد الأوتوماتيكية ه
msgid ""
"No payment is required as this order only includes products which are free of "
"charge."
msgstr "جميع التذاكر في طلبك مجانية."
msgstr "لا يلزم الدفع في هذا النظام يشمل فقط المنتجات التي هي مجانا."
#: pretix/base/payment.py:742
msgid "Free of charge"
@@ -3972,7 +3972,8 @@ msgstr "ولا يعرف هذا الرمز قسيمة في قاعدة البيا
#: pretix/base/services/cart.py:74
msgid ""
"This voucher code has already been used the maximum number of times allowed."
msgstr "انتهى كود الخصم المدخل."
msgstr ""
"وقد تم بالفعل استخدام هذا الرمز قسيمة أكبر عدد ممكن من المرات المسموح بها."
#: pretix/base/services/cart.py:75
#, python-format
@@ -4185,7 +4186,7 @@ msgstr "عينة المنتج A"
msgid ""
"You are receiving this email because someone placed an order for {event} for "
"you."
msgstr "لقد استلمت هذا البريد لأن الحجز مسجل بايميلك ل {event}."
msgstr "كنت تتلقى هذا البريد الإلكتروني لشخص ما وضعت أمر {event} حدث لك."
#: pretix/base/services/mail.py:171 pretix/base/services/mail.py:187
#, python-brace-format
@@ -4199,7 +4200,7 @@ msgstr ""
#: pretix/base/services/mail.py:183
#, python-brace-format
msgid "You are receiving this email because you placed an order for {event}."
msgstr "تلقيت هذا البريد لأنك قمت بالحجز في{event}."
msgstr "كنت تتلقى هذا البريد الإلكتروني لأنك وضعت على أمر ل{event} الحدث."
#: pretix/base/services/orders.py:56
msgid ""
@@ -4221,7 +4222,8 @@ msgid ""
"The price of some of the items in your cart has changed in the meantime. "
"Please see below for details."
msgstr ""
"لقد تغير أسعار بعض العناصر في سلة تسوقك. انظر أدناه للحصول على التفاصيل."
"لقد تغير أسعار بعض العناصر في سلة التسوق الخاصة بك في هذه الأثناء. انظر أدناه "
"للحصول على التفاصيل."
#: pretix/base/services/orders.py:62
msgid "An internal error occurred, please try again."
@@ -4609,12 +4611,13 @@ msgid ""
msgstr ""
"مرحبا،\n"
"\n"
"تجد في الأسفل التذاكر المسجلة بإيميلك ل{event}.\n"
"طلب شخص ما على قائمة من الأوامر الخاصة بك ل{event}.\n"
"والقائمة على النحو التالي:\n"
"\n"
"{orders}\n"
"\n"
"تحياتنا،\n"
"فريق {event}"
"تحياتي الحارة،\n"
"لديك {event} فريق"
#: pretix/base/settings.py:355
#, python-brace-format
@@ -4655,14 +4658,14 @@ msgid ""
msgstr ""
"مرحبا،\n"
"\n"
"حجزك ل {event}. هذا الحجز مجاني.\n"
"\n"
"يمكنك تغيير تفاصيل طلبك وعرض حالة طلبك من خلال:\n"
"كان طلبك {event} الناجح. كما كنت قد طلبت فقط المنتجات الخالية،\n"
"لا يلزم الدفع.\n"
"\n"
"يمكنك تغيير تفاصيل طلبك وعرض حالة طلبك في\n"
"{url}\n"
"\n"
"تحياتنا،\n"
"فريق {event}"
"تحياتي الحارة،\n"
"لديك {event} فريق"
#: pretix/base/settings.py:384
#, python-brace-format
@@ -4710,15 +4713,15 @@ msgstr ""
"مرحبا،\n"
"\n"
"تلقينا طلبك بنجاح ل{event} بقيمة إجمالية\n"
"{total_with_currency}. يرجى إتمام عملية الدفع قبل {expire_date}.\n"
"من {total_with_currency}. يرجى إتمام عملية الدفع قبل {expire_date}.\n"
"\n"
"{payment_info}\n"
"\n"
"يمكنك تغيير تفاصيل طلبك وعرض حالة طلبك من خلال\n"
"يمكنك تغيير تفاصيل طلبك وعرض حالة طلبك في\n"
"{url}\n"
"\n"
"تحياتنا،\n"
"فريق{event}"
"تحياتي الحارة،\n"
"لديك {event} فريق"
#: pretix/base/settings.py:417
#, python-brace-format
@@ -4735,13 +4738,13 @@ msgid ""
msgstr ""
"مرحبا {attendee_name}،\n"
"\n"
"تم طلب تذكرة لك لحضور {event}.\n"
"وقد أمر تذكرة ل{event} لك.\n"
"\n"
"يمكنك عرض تفاصيل الحجز من خلال:\n"
"يمكنك عرض التفاصيل ووضع التذكرة هنا:\n"
"{url}\n"
"\n"
"تحياتنا،\n"
"فريق{event}"
"تحياتي الحارة،\n"
"لديك {event} فريق"
#: pretix/base/settings.py:429
#, python-brace-format
@@ -4763,8 +4766,8 @@ msgstr ""
"يمكنك عرض حالة طلبك في\n"
"{url}\n"
"\n"
"تحياتنا،\n"
"فريق{event}"
"تحياتي الحارة،\n"
"لديك {event} فريق"
#: pretix/base/settings.py:441
#, python-brace-format
@@ -4787,11 +4790,11 @@ msgstr ""
"\n"
"{payment_info}\n"
"\n"
"يمكنك تغيير تفاصيل طلبك وعرض حالة طلبك من خلال\n"
"يمكنك تغيير تفاصيل طلبك وعرض حالة طلبك في\n"
"{url}\n"
"\n"
"تحياتنا،\n"
"فريق{event}"
"تحياتي الحارة،\n"
"لديك {event} فريق"
#: pretix/base/settings.py:459
#, python-brace-format
@@ -4808,13 +4811,13 @@ msgid ""
msgstr ""
"مرحبا {attendee_name}،\n"
"\n"
"تم دفع تذكرة لك لحضور {event}.\n"
"والآن يدفع تذكرة ل{event} الذي قد أمرت لك.\n"
"\n"
"يمكنك عرض تفاصيل التذكرة من خلال:\n"
"يمكنك عرض التفاصيل ووضع التذكرة هنا:\n"
"{url}\n"
"\n"
"تحياتنا،\n"
"فريق {event}"
"تحياتي الحارة،\n"
"لديك {event} فريق"
#: pretix/base/settings.py:475
#, python-brace-format
@@ -4833,15 +4836,15 @@ msgid ""
msgstr ""
"مرحبا،\n"
"\n"
"لم يتم دفع كامل المبلغ حتى الآن لطلبك ل{event}.\n"
"يرجى أن تضع في عين الإعتبار أننا نضمن طلبك إذا قمت بالدفع قبل {expire_date}."
"أننا لم نتلق حتى الآن على دفع كامل لطلبك ل{event}.\n"
"يرجى أن نضع في اعتبارنا أن نضمن فقط طلبك إذا تلقينا\n"
"الدفع الخاص بك قبل {expire_date}.\n"
"\n"
"\n"
"يمكنك عرض معلومات الدفع وحالة طلبك من خلال\n"
"يمكنك عرض معلومات الدفع وحالة طلبك في\n"
"{url}\n"
"\n"
"تحياتنا،\n"
"فريق{event}"
"تحياتي الحارة،\n"
"لديك {event} فريق"
#: pretix/base/settings.py:489
#, python-brace-format
@@ -4869,25 +4872,24 @@ msgid ""
msgstr ""
"مرحبا،\n"
"\n"
"قمت بتسجيل اسمك إلى قائمة انتظار لحضور {event}،\n"
"نوع التذكرة: {product}.\n"
"قمت بتقديم نفسك إلى قائمة انتظار {event}،\n"
"للمنتج {product}.\n"
"\n"
"لدينا الآن تذكرة جاهزة! \n"
"يجب اكمال الحجز قبل {hours} ساعة القادمة، للحصول على التذكرة استخدم الكود "
"التالي:\n"
"لدينا الآن تذكرة على استعداد للكم! يمكنك استبدال عليها في متجر تذكرة لنا\n"
"ضمن {hours} ساعة القادمة عن طريق إدخال رمز القسيمة التالية:\n"
"\n"
"{code}\n"
"\n"
"أو يمكنك فقط اضغط على الرابط التالي:\n"
"بدلا من ذلك، يمكنك فقط اضغط على الرابط التالي:\n"
"\n"
"{url}\n"
"\n"
"يرجى ملاحظة أن هذا الرابط صالح فقط خلال{hours} ساعة القادمة!\n"
تكون التذكرة من نصيب الشخص التالي على الإنتظار \n"
"اذا لم تقم باستكمال الحجز في المدة المطلوبة.\n"
"يرجى ملاحظة أن هذا الارتباط هو صالح فقط داخل{hours} ساعة القادمة!\n"
نقوم إعادة تعيين التذكرة للشخص المقبل على لائحة اذا كنت لا\n"
"استبدال القسيمة في هذا الإطار الزمني.\n"
"\n"
"تحياتنا،\n"
"فريق{event}"
"تحياتي الحارة،\n"
"لديك {event} فريق"
#: pretix/base/settings.py:512
#, python-brace-format
@@ -4904,13 +4906,13 @@ msgid ""
msgstr ""
"مرحبا،\n"
"\n"
"تم إلغاء طلبك {code} من{event}.\n"
"تم إلغاء طلبك {code} عن {event}.\n"
"\n"
"يمكنك عرض تفاصيل طلبك من خلال\n"
"يمكنك عرض تفاصيل طلبك في\n"
"{url}\n"
"\n"
"تحياتنا،\n"
"فريق{event}"
"تحياتي الحارة،\n"
"لديك {event} فريق"
#: pretix/base/settings.py:524
#, python-brace-format
@@ -4931,16 +4933,17 @@ msgid ""
msgstr ""
"مرحبا،\n"
"\n"
"وافقنا طلبك {event} ونسعد بقدومكم.\n"
"وافقنا طلبك {event} وسوف نكون سعداء أن أرحب بكم\n"
"في هذا الحدث.\n"
"\n"
"الرجاء اكمال عملية الدفع قبل {expire_date}.\n"
"الرجاء الاستمرار عن طريق دفع لطلبك قبل {expire_date}.\n"
"\n"
"يمكنك تحديد طريقة الدفع وإجراء الدفع هنا:\n"
"\n"
"{url}\n"
"\n"
"تحياتنا،\n"
"فريق{event}"
"تحياتي الحارة،\n"
"لديك {event} فريق"
#: pretix/base/settings.py:540
#, python-brace-format
@@ -4960,7 +4963,7 @@ msgid ""
msgstr ""
"مرحبا،\n"
"\n"
"نعتذر منك، تم رفض طلبك {event}.\n"
"للأسف، نحن رفض طلب طلبك {event}.\n"
"\n"
"{comment}\n"
"\n"
@@ -4968,8 +4971,8 @@ msgstr ""
"\n"
"{url}\n"
"\n"
"تحياتنا،\n"
"فريق{event}"
"تحياتي الحارة،\n"
"لديك {event} فريق"
#: pretix/base/settings.py:555
#, python-brace-format
@@ -4984,11 +4987,11 @@ msgid ""
msgstr ""
"مرحبا،\n"
"\n"
"يمكنك تغيير تفاصيل طلبك وعرض حالة طلبك من خلال\n"
"يمكنك تغيير تفاصيل طلبك وعرض حالة طلبك في\n"
"{url}\n"
"\n"
"تحياتنا،\n"
"فريق{event}"
"تحياتي الحارة،\n"
"لديك {event} فريق"
#: pretix/base/settings.py:573
#, python-brace-format
@@ -5005,13 +5008,13 @@ msgid ""
msgstr ""
"مرحبا {attendee_name}،\n"
"\n"
"أنت مسجل في {event}.\n"
"أنت مسجل ل{event}.\n"
"\n"
"يمكنك تحميل تذكرتك من خلال الرابط:\n"
"إذا كنت لم تفعل ذلك بالفعل، يمكنك تحميل تذكرتك هنا:\n"
"{url}\n"
"\n"
"تحياتنا،\n"
"فريق{event}"
"تحياتي الحارة،\n"
"لديك {event} فريق"
#: pretix/base/settings.py:585
#, python-brace-format
@@ -5028,13 +5031,13 @@ msgid ""
msgstr ""
"مرحبا،\n"
"\n"
"اشتريت تذكرة لحضور {event}.\n"
"اشتريت تذكرة ل{event}.\n"
"\n"
" يمكنك تحميل تذكرتك هنا:\n"
"إذا كنت لم تفعل ذلك بالفعل، يمكنك تحميل تذكرتك هنا:\n"
"{url}\n"
"\n"
"تحياتنا،\n"
"فريق{event}"
"تحياتي الحارة،\n"
"لديك {event} فريق"
#: pretix/base/settings.py:748
msgid "Most common English titles"
@@ -5281,14 +5284,15 @@ msgid ""
"If this takes longer than a few minutes, please refresh this page or contact "
"us."
msgstr ""
"إذا استغرقت العملية أكثر من بضع دقائق، يرجى تحديث هذه الصفحة أو الاتصال بنا."
"إذا كان هذا يستغرق وقتا أطول من بضع دقائق، يرجى تحديث هذه الصفحة أو الاتصال "
"بنا."
#: pretix/base/templates/pretixbase/email/email_footer.html:3
#: pretix/control/templates/pretixcontrol/auth/base.html:45
#: pretix/control/templates/pretixcontrol/base.html:412
#, python-format
msgid "powered by <a %(a_attr)s>pretix</a>"
msgstr "المنصة من<a %(a_attr)s>pretix</a>"
msgstr "مدعوم من <a %(a_attr)s>pretix</a>"
#: pretix/base/templates/pretixbase/email/notification.html:55
#: pretix/base/templates/pretixbase/email/notification.txt:14
@@ -5315,7 +5319,8 @@ msgstr "انقر هنا تعطيل كافة الإخطارات على الفور
msgid ""
"You are receiving this email because someone signed you up for the following "
"event:"
msgstr "تلقيت هذا البريد لأنك قمت بالحجز في:"
msgstr ""
"كنت تتلقى هذا البريد الإلكتروني لشخص ما وقعت أنت على مستوى الحدث التالي:"
#: pretix/base/templates/pretixbase/email/plainwrapper.html:28
#: pretix/base/templates/pretixbase/email/plainwrapper.html:36
@@ -5340,7 +5345,7 @@ msgstr "عرض تفاصيل التسجيل"
msgid ""
"You are receiving this email because you placed an order for the following "
"event:"
msgstr "تلقيت هذا البريد لأنك قمت بالحجز في:"
msgstr "كنت تتلقى هذا البريد الإلكتروني لأنك وضعت على أمر لهذا الحدث التالية:"
#: pretix/base/templates/pretixbase/forms/widgets/reldate.html:15
#: pretix/base/templates/pretixbase/forms/widgets/reldatetime.html:15
@@ -5806,7 +5811,7 @@ msgstr "محجوزة عدد الدقائق البنود في عربة المست
#: pretix/control/forms/event.py:467 pretix/control/forms/event.py:1499
msgid "Imprint URL"
msgstr "مقدم الخدمة URL"
msgstr "بصمة URL"
#: pretix/control/forms/event.py:468 pretix/control/forms/event.py:1500
msgid ""
@@ -9024,13 +9029,13 @@ msgstr "يعمل في نمط التنمية"
#: pretix/control/templates/pretixcontrol/base.html:427
#: pretix/presale/templates/pretixpresale/waiting.html:22
msgid "We are processing your request …"
msgstr "نقوم بمعالجة طلبك "
msgstr "نحن معالجة طلبك ..."
#: pretix/control/templates/pretixcontrol/base.html:429
#: pretix/presale/templates/pretixpresale/fragment_modals.html:12
#: pretix/presale/templates/pretixpresale/waiting.html:25
msgid "If this takes longer than a few minutes, please contact us."
msgstr "إذا استغرقت العملية وقتا أطول من بضع دقائق، يرجى الاتصال بنا."
msgstr "إذا كان هذا يستغرق وقتا أطول من بضع دقائق، يرجى الاتصال بنا."
#: pretix/control/templates/pretixcontrol/boxoffice/payment.html:4
#: pretix/control/templates/pretixcontrol/organizers/devices.html:34
@@ -9087,7 +9092,7 @@ msgstr "قائمة Edit."
#: pretix/control/templates/pretixcontrol/checkin/index.html:22
#: pretix/plugins/ticketoutputpdf/ticketoutput.py:30
msgid "PDF"
msgstr "PDF"
msgstr "بي دي إف"
#: pretix/control/templates/pretixcontrol/checkin/index.html:27
msgid "CSV"
@@ -10919,7 +10924,7 @@ msgstr "تحرير مسألة"
#: pretix/plugins/reports/exporters.py:223
#: pretix/presale/templates/pretixpresale/event/fragment_order_status.html:13
msgid "Paid"
msgstr "مدفوع"
msgstr "دفع"
#: pretix/control/templates/pretixcontrol/items/question.html:44
msgid "No matching answers found."
@@ -11636,7 +11641,7 @@ msgstr "منتجات التغيير"
#: pretix/control/templates/pretixcontrol/order/index.html:240
#: pretix/presale/templates/pretixpresale/event/order.html:126
msgid "Ordered items"
msgstr "تفاصيل الطلب"
msgstr "المواد المطلوبة"
#: pretix/control/templates/pretixcontrol/order/index.html:259
#, python-format
@@ -15712,7 +15717,7 @@ msgstr "تحقق في قائمة (PDF)"
#: pretix/plugins/checkinlists/exporters.py:224
msgctxt "tablehead"
msgid "paid"
msgstr "مدفع"
msgstr "دفع"
#: pretix/plugins/checkinlists/exporters.py:327
msgid "Automatically checked in"
@@ -17157,7 +17162,7 @@ msgstr "وكانت معلومات الدفع التي أدخلتها غير مك
#: pretix/presale/checkoutflow.py:621
msgctxt "checkoutflow"
msgid "Review order"
msgstr "مراجعة الحجز"
msgstr "أجل مراجعة"
#: pretix/presale/checkoutflow.py:706
msgid "You need to check all checkboxes on the bottom of the page."
@@ -17278,11 +17283,11 @@ msgstr ""
#: pretix/presale/templates/pretixpresale/event/base.html:114
msgid "Contact event organizer"
msgstr "تواصل مع المنظمين"
msgstr "الاتصال الحدث منظم"
#: pretix/presale/templates/pretixpresale/event/base.html:117
msgid "Imprint"
msgstr "المنظم"
msgstr "بصمة"
#: pretix/presale/templates/pretixpresale/event/checkout_addons.html:7
msgid ""
@@ -17348,7 +17353,7 @@ msgstr "انتهت العربة"
#: pretix/presale/templates/pretixpresale/event/index.html:53
#, python-format
msgid "The items in your cart are reserved for you for %(minutes)s minutes."
msgstr "سيتم الغاء الحجز تلقائيا بعد %(minutes)s دقائق."
msgstr "محجوزة البنود في عربة التسوق لك ل %(minutes)s دقائق."
#: pretix/presale/templates/pretixpresale/event/checkout_base.html:34
#: pretix/presale/templates/pretixpresale/event/checkout_confirm.html:45
@@ -17359,7 +17364,7 @@ msgstr "العناصر الموجودة في سلة التسوق الخاصة ب
#: pretix/presale/templates/pretixpresale/event/checkout_confirm.html:6
#: pretix/presale/templates/pretixpresale/event/checkout_confirm.html:8
msgid "Review order"
msgstr "مراجعة الحجز"
msgstr "أجل مراجعة"
#: pretix/presale/templates/pretixpresale/event/checkout_confirm.html:10
msgid "Please review the details below and confirm your order."
@@ -17367,7 +17372,7 @@ msgstr "يرجى مراجعة التفاصيل أدناه وتأكيد طلبك.
#: pretix/presale/templates/pretixpresale/event/checkout_confirm.html:12
msgid "Please hang tight, we're finalizing your order!"
msgstr "يتم الآن تأكيد حجزك!"
msgstr "يرجى شنق ضيق، ونحن على وشك الانتهاء من طلبك!"
#: pretix/presale/templates/pretixpresale/event/checkout_confirm.html:19
#: pretix/presale/templates/pretixpresale/event/checkout_confirm.html:60
@@ -17399,12 +17404,12 @@ msgid ""
"your order. If your order was approved, we will send you a link that you can "
"use to pay."
msgstr ""
"سنقوم بإرسال ايميل لك فور الموافقة على حجزك من المنظمين. إذا تمت الموافقة "
"على طلبك، سوف تجد رابط في الإيميل يمكنك استخدامه للدفع."
"فإننا سوف أرسل لك رسالة بالبريد الالكتروني في أقرب وقت الحدث منظم قبول أو رفض "
"طلبك. إذا تمت الموافقة على طلبك، سوف نرسل لك رابطا التي يمكنك استخدامها لدفع."
#: pretix/presale/templates/pretixpresale/event/checkout_confirm.html:188
msgid "Place binding order"
msgstr "اتمام عملية الدفع"
msgstr "المكان ترتيب الربط"
#: pretix/presale/templates/pretixpresale/event/checkout_confirm.html:190
msgid "Submit registration"
@@ -17412,7 +17417,7 @@ msgstr "تاكيد التسجيل"
#: pretix/presale/templates/pretixpresale/event/checkout_payment.html:7
msgid "Please select how you want to pay."
msgstr "يرجى اختيار طريقة الدفع."
msgstr "يرجى اختيار الطريقة التي تريد أن تدفع."
#: pretix/presale/templates/pretixpresale/event/checkout_payment.html:41
msgid "This sales channel does not provide support for testmode."
@@ -17441,12 +17446,12 @@ msgstr "يرجى الدخول إلى إعدادات دفع وتفعيل واحد
#: pretix/presale/templates/pretixpresale/event/checkout_questions.html:6
msgid "Before we continue, we need you to answer some questions."
msgstr "نحتاج بعض المعلومات قبل إتمام عملية الدفع."
msgstr "قبل أن نواصل، نحن بحاجة لكم للإجابة على بعض الأسئلة."
#: pretix/presale/templates/pretixpresale/event/checkout_questions.html:8
msgid ""
"You need to fill all fields that are marked with <span>*</span> to continue."
msgstr "يجب تعبئة الخانات التي تحتوي على رمز <span>*</span> للمتابعة."
msgstr "كنت بحاجة لملء جميع الحقول التي تحمل علامة <span>*</span> للمتابعة."
#: pretix/presale/templates/pretixpresale/event/checkout_questions.html:65
msgid "Copy answers from above"
@@ -17467,8 +17472,10 @@ msgid ""
"embedded into the website. Please try to open the ticket shop in a new tab or "
"change your browser settings."
msgstr ""
"متصفحك لا يدعم الكوكيز. يجب تغيير إعدادات المتصفح حتى تتمكن من تصفح الموقع. "
رجى تغيير الإعدادات في متصفحك لقبول الكوكيز."
"تم تكوين المستعرض الخاص بك لمنع الكوكيز من عناصر موقع طرف ثالث. للأسف، وهذا "
عني أننا لا يمكن أن تظهر لك هذا المحل تذكرة جزءا لا يتجزأ في الموقع. يرجى "
"المحاولة لفتح متجر تذكرة في علامة تبويب جديدة أو تغيير إعدادات المتصفح الخاص "
"بك."
#: pretix/presale/templates/pretixpresale/event/cookies.html:16
msgid "We apologize for the inconvenience!"
@@ -17505,15 +17512,15 @@ msgstr "كل المنتجات المتبقية هي محجوزة لكنها قد
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:106
msgid "Okay, we're removing that…"
msgstr "يتم الآن إلغاء التذكرة…"
msgstr "حسنا، نحن إزالة هذا ..."
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:110
msgid "Remove one"
msgstr "إزالة تذكرة"
msgstr "إزالة واحد"
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:122
msgid "We're trying to reserve another one for you!"
msgstr "إضافة تذكرة!"
msgstr "نحاول حجز واحد آخر لك!"
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:123
#: pretix/presale/templates/pretixpresale/event/index.html:215
@@ -17523,7 +17530,9 @@ msgstr "إضافة تذكرة!"
msgid ""
"Once the items are in your cart, you will have %(time)s minutes to complete "
"your purchase."
msgstr "سيكون لديك %(time)s دقائق لإتمام عملية الشراء."
msgstr ""
"وبمجرد أن تكون هذه البنود في عربة التسوق، سيكون لديك %(time)s دقائق لإتمام "
"عملية الشراء."
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:138
msgid "Add one more"
@@ -17540,11 +17549,11 @@ msgid ""
"ticket ready when entering the event."
msgstr ""
"يمكنك تحميل التذاكر الخاصة بك باستخدام الأزرار أدناه. يرجى أن يكون التذكرة "
"جاهزة عند الدخول."
"جاهزة عند دخول هذا الحدث."
#: pretix/presale/templates/pretixpresale/event/fragment_downloads.html:11
msgid "Download all tickets at once:"
msgstr "تحميل جميع التذاكر:"
msgstr "تحميل جميع تذاكر في وقت واحد:"
#: pretix/presale/templates/pretixpresale/event/fragment_downloads.html:29
#, python-format
@@ -17574,7 +17583,7 @@ msgstr "من %(price)s"
#: pretix/presale/templates/pretixpresale/event/voucher.html:231
msgctxt "price"
msgid "FREE"
msgstr "مجانا"
msgstr "حر"
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html:68
msgid "Show variants"
@@ -17586,7 +17595,7 @@ msgstr "مشاهدة المتغيرات"
#: pretix/presale/templates/pretixpresale/event/voucher.html:226
#, python-format
msgid "Modify price for %(item)s"
msgstr "تعديل سعر %(item)s"
msgstr "سعر تعديل ل %(item)s"
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html:126
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html:240
@@ -17628,7 +17637,7 @@ msgstr "إدخال رمز قسيمة أدناه لشراء هذه التذاكر
#: pretix/presale/templates/pretixpresale/event/voucher.html:169
#, python-format
msgid "Amount of %(item)s %(var)s to order"
msgstr "عدد %(item)s - %(var)s للطلب"
msgstr "كمية %(item)s - %(var)s للطلب"
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html:275
#: pretix/presale/templates/pretixpresale/event/voucher.html:266
@@ -17665,16 +17674,16 @@ msgstr "ليس بعد على بيع"
#: pretix/presale/templates/pretixpresale/event/index.html:9
msgid "Presale"
msgstr "مبيعات"
msgstr "[خبر]"
#: pretix/presale/templates/pretixpresale/event/index.html:65
#: pretix/presale/templates/pretixpresale/event/index.html:82
msgid "Empty cart"
msgstr "الغاء الحجز"
msgstr "عربة فارغة"
#: pretix/presale/templates/pretixpresale/event/index.html:74
msgid "Proceed with checkout"
msgstr "إتمام عملية الدفع"
msgstr "الشروع في الخروج"
#: pretix/presale/templates/pretixpresale/event/index.html:100
msgid "Add tickets for a different date"
@@ -17686,7 +17695,7 @@ msgstr "عرض تاريخ آخر"
#: pretix/presale/templates/pretixpresale/event/index.html:105
msgid "Choose date to buy a ticket"
msgstr "تاريخ شراء التذاكر"
msgstr "تاريخ اختيار لشراء تذكرة"
#: pretix/presale/templates/pretixpresale/event/index.html:110
msgctxt "subevent"
@@ -17696,7 +17705,7 @@ msgstr "تواريخ أخرى"
#: pretix/presale/templates/pretixpresale/event/index.html:143
#: pretix/presale/views/widget.py:484
msgid "The presale period for this event is over."
msgstr "انتهت فترة بيع التذاكر."
msgstr "فترة [خبر] لهذا الحدث قد انتهت."
#: pretix/presale/templates/pretixpresale/event/index.html:148
#: pretix/presale/views/widget.py:486
@@ -17737,7 +17746,7 @@ msgstr "إضافة إلى التقويم"
#: pretix/presale/templates/pretixpresale/event/seatingplan.html:23
#: pretix/presale/templates/pretixpresale/event/voucher.html:25
msgid "We're now trying to reserve this for you!"
msgstr "نجهز حجزك!"
msgstr "نحاول الآن لحجز هذا بالنسبة لك!"
#: pretix/presale/templates/pretixpresale/event/index.html:233
#: pretix/presale/templates/pretixpresale/event/voucher.html:300
@@ -17746,15 +17755,15 @@ msgstr "أضف إلى السلة"
#: pretix/presale/templates/pretixpresale/event/index.html:245
msgid "Redeem a voucher"
msgstr "كود خصم"
msgstr "استبدال قسيمة"
#: pretix/presale/templates/pretixpresale/event/index.html:263
msgid "Redeem voucher"
msgstr "ادخل كود الخصم"
msgstr "استبدال القسيمة"
#: pretix/presale/templates/pretixpresale/event/index.html:274
msgid "If you already ordered a ticket"
msgstr "إذا كان لديك تذكرة"
msgstr "إذا كنت قد طلبت بالفعل تذكرة"
#: pretix/presale/templates/pretixpresale/event/index.html:278
msgid ""
@@ -17763,15 +17772,16 @@ msgid ""
"cannot find the link, click on the following button to request the link to "
"your order to be sent to you again."
msgstr ""
"إذا كنت تريد أن ترى أو تغير حالة وتفاصيل طلبك، اضغط على زر إعادة الإرسال "
"ليتم إعادة ارسال تفاصيل طلبك إلى إيميلك ومن ثم قم بالضغط على الرابط في "
"الإيميل."
"إذا كنت تريد أن ترى أو تغيير حالة وتفاصيل طلبك، انقر على الرابط في واحدة من "
"رسائل البريد الإلكتروني التي أرسلناها إليك أثناء عملية النظام. إذا لم تتمكن "
"من العثور على وصلة، انقر على زر التالي للطلب على الرابط لطلبك ليتم إرسالها لك "
"مرة أخرى."
#: pretix/presale/templates/pretixpresale/event/index.html:287
#: pretix/presale/templates/pretixpresale/event/resend_link.html:4
#: pretix/presale/templates/pretixpresale/event/resend_link.html:7
msgid "Resend order links"
msgstr "إعادة إرسال التذكرة"
msgstr "الروابط أجل إعادة إرسال"
#: pretix/presale/templates/pretixpresale/event/order.html:13
msgid "Thank you!"
@@ -17779,25 +17789,27 @@ msgstr "شكرا جزيلا!"
#: pretix/presale/templates/pretixpresale/event/order.html:16
msgid "Your order has been placed successfully. See below for details."
msgstr "تم إتمام الطلب بنجاح، تجد التفاصيل في الأسفل."
msgstr "وقد وضعت طلبك بنجاح. انظر أدناه للحصول على التفاصيل."
#: pretix/presale/templates/pretixpresale/event/order.html:19
msgid ""
"Please note that we still await approval by the event organizer before you "
"can pay and complete this order."
msgstr "يرجى ملاحظة أننا لا نزال ننتظر موافقة منظم الفعالية."
msgstr ""
"يرجى ملاحظة أننا لا نزال ننتظر موافقة من قبل منظم الحدث قبل أن تتمكن من دفع "
"واستكمال هذا النظام."
#: pretix/presale/templates/pretixpresale/event/order.html:23
msgid "Please note that we still await your payment to complete the process."
msgstr "يرجى دفع المبلغ المطلوب لإكمال الحجز."
msgstr "يرجى ملاحظة أننا لا نزال ننتظر دفع لإكمال العملية."
#: pretix/presale/templates/pretixpresale/event/order.html:28
msgid "Your order has been processed successfully! See below for details."
msgstr "تم طلبك بنجاح! تجد التفاصيل في الأسفل."
msgstr "وقد تم تجهيز طلبك بنجاح! انظر أدناه للحصول على التفاصيل."
#: pretix/presale/templates/pretixpresale/event/order.html:30
msgid "We successfully received your payment. See below for details."
msgstr "تم إتمام عملية الدفع بنجاح. تجد التفاصيل في الأسفل."
msgstr "تلقينا بنجاح دفعتك. انظر أدناه للحصول على التفاصيل."
#: pretix/presale/templates/pretixpresale/event/order.html:32
msgid ""
@@ -17805,16 +17817,17 @@ msgid ""
"your order later. We also sent you an email containing the link to the "
"address you specified."
msgstr ""
"الرجاء حفظ الصفحة في المفضلة، أو حفظ الرابط حتى يمكنك مراجعة طلبك لاحقا. "
"قمنا أيضا بإرسال الرابط للإيميل المسجل في الطلب."
"يرجى الإشارة المرجعية أو حفظ الارتباط إلى هذه الصفحة بالضبط إذا كنت ترغب في "
"الحصول على طلبك في وقت لاحق. علينا أيضا أن أرسل لك رسالة بريد إلكتروني تحتوي "
"على رابط إلى العنوان الذي حددته."
#: pretix/presale/templates/pretixpresale/event/order.html:35
msgid ""
"Please save the following link if you want to access your order later. We "
"also sent you an email containing the link to the address you specified."
msgstr ""
"الرجاء حفظ الرابط التالي إذا كنت ترغب في الوصول لحجزك في وقت آخر. سنقوم أيضا "
"بإرسال رابط الحجز لإيميلك."
"الرجاء حفظ الرابط التالي إذا كنت ترغب في الحصول على طلبك في وقت لاحق. علينا "
"أيضا أن أرسل لك رسالة بريد إلكتروني تحتوي على رابط إلى العنوان الذي حددته."
#: pretix/presale/templates/pretixpresale/event/order.html:52
#: pretix/presale/templates/pretixpresale/event/position.html:18
@@ -17824,7 +17837,7 @@ msgstr "عرض في الخلفية"
#: pretix/presale/templates/pretixpresale/event/order.html:66
#, python-format
msgid "A payment of %(total)s is still pending for this order."
msgstr "تبقى دفع %(total)s لإتمام طلبك."
msgstr "ودفع %(total)s ليالي لا يزال معلقا لهذا النظام."
#: pretix/presale/templates/pretixpresale/event/order.html:69
#, python-format
@@ -17851,8 +17864,8 @@ msgid ""
"A refund of %(amount)s has been sent to you. Depending on the payment method, "
"please allow for up to 14 days until it shows up on your statement."
msgstr ""
"تم طلب استرداد مبلغ %(amount)s لحسابك، قد تستغرق العملية 14 يوم حتى تظهر في "
"حسابك."
"ق تم ارسال استرداد %(amount)s لك. اعتمادا على طريقة الدفع، أرجو أن تسمحوا "
"لمدة تصل إلى 14 يوما حتى يظهر على بيانكم."
#: pretix/presale/templates/pretixpresale/event/order.html:121
#: pretix/presale/templates/pretixpresale/event/order.html:204
@@ -17890,7 +17903,7 @@ msgstr ""
#: pretix/presale/templates/pretixpresale/event/order.html:284
#: pretix/presale/templates/pretixpresale/event/order_cancel.html:16
msgid "This will invalidate all tickets in this order."
msgstr "سيتم إلغاء جميع التذاكر في هذا الطلب."
msgstr "وهذا يبطل جميع التذاكر في هذا النظام."
#: pretix/presale/templates/pretixpresale/event/order.html:268
msgid ""
@@ -17900,7 +17913,7 @@ msgstr "يمكنك إلغاء هذا النظام والحصول على كامل
#: pretix/presale/templates/pretixpresale/event/order.html:281
msgid "You can cancel this order using the following button."
msgstr "يمكنك إلغاء الحجز باستخدام الزر التالي."
msgstr "يمكنك إلغاء هذا النظام باستخدام زر التالي."
#: pretix/presale/templates/pretixpresale/event/order_cancel.html:8
#, python-format
@@ -18018,8 +18031,9 @@ msgid ""
"you used for your order. We will send you an email with links to all orders "
"you placed using this email address."
msgstr ""
"إذا فقدت تذكرتك، يرجى إدخال إيميلك الذي استخدمته أثناء الطلب، وسوف نقوم "
"بإرسال التذاكر مجددا لإيميلك."
"إذا كنت فقدت تصل إلى طلبك أو أوامر، يرجى إدخال عنوان البريد الإلكتروني الذي "
"استخدمته لطلبك. سوف نرسل لك رسالة بالبريد الالكتروني مع وصلات لجميع أوامر كنت "
"قد وضعت باستخدام عنوان البريد الإلكتروني هذا."
#: pretix/presale/templates/pretixpresale/event/resend_link.html:26
msgid "Send links"
@@ -18028,13 +18042,14 @@ msgstr "إرسال الروابط"
#: pretix/presale/templates/pretixpresale/event/voucher.html:9
#: pretix/presale/templates/pretixpresale/event/voucher.html:12
msgid "Voucher redemption"
msgstr "كود الخصم"
msgstr "قسيمة الفداء"
#: pretix/presale/templates/pretixpresale/event/voucher.html:17
msgid ""
"You entered a voucher code that allows you to buy one of the following "
"products at the specified price:"
msgstr "كود الخصم المدخل يمكنك من الحصول على التذكرة بسعر محدد في الأسفل:"
msgstr ""
"لقد أدخلت رمز القسيمة التي تسمح لك لشراء واحدة من المنتجات التالية بسعر محدد:"
#: pretix/presale/templates/pretixpresale/event/voucher.html:76
#, python-format
@@ -18194,7 +18209,7 @@ msgstr "عربة التسوق الخاصة بك الآن فارغة."
#: pretix/presale/views/cart.py:375
msgid "The products have been successfully added to your cart."
msgstr "تم إضافة التذاكر إلى سلة التسوق."
msgstr "تم إضافة منتجات بنجاح إلى سلة التسوق الخاصة بك."
#: pretix/presale/views/checkout.py:34
msgid "Your cart is empty"
@@ -18250,7 +18265,7 @@ msgstr ""
#: pretix/presale/views/order.py:665
msgid "You cannot modify this order"
msgstr "لا يمكنك تعديل هذا الطلب"
msgstr "لا يمكنك تعديل هذا النظام"
#: pretix/presale/views/order.py:780
msgid "Ticket download is not (yet) enabled for this order."
@@ -18270,7 +18285,9 @@ msgstr "طلباتكم ل{}"
#: pretix/presale/views/user.py:53
msgid "We have trouble sending emails right now, please check back later."
msgstr "لدينا مشكلة في إرسال البريد حاليا، يرجى التحقق مرة أخرى في وقت لاحق."
msgstr ""
"لدينا رسائل البريد الإلكتروني المرسلة من المتاعب في الوقت الراهن، يرجى التحقق "
"مرة أخرى في وقت لاحق."
#: pretix/presale/views/user.py:56
msgid ""

View File

@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-12-05 13:34+0000\n"
"PO-Revision-Date: 2019-12-25 01:00+0000\n"
"Last-Translator: Abdullah <abdullah.gumaijan@gmail.com>\n"
"PO-Revision-Date: 2019-11-18 09:44+0000\n"
"Last-Translator: Raphael Michel <michel@rami.io>\n"
"Language-Team: Arabic <https://translate.pretix.eu/projects/pretix/pretix-js/"
"ar/>\n"
"Language: ar\n"
@@ -92,22 +92,28 @@ msgstr "حدث خطأ من نوع {كود}."
msgid ""
"We currently cannot reach the server, but we keep trying. Last error code: "
"{code}"
msgstr "لم نستطع معالجة طلبك، ولكننا نواصل المحاولة. رمز الخطأ : {code}"
msgstr ""
"نحن في الوقت الراهن لا يمكن أن تصل إلى الخادم، ولكننا نواصل المحاولة. رمز "
"الخطأ نشاط: {كود}"
#: pretix/static/pretixbase/js/asynctask.js:125
#: pretix/static/pretixcontrol/js/ui/mail.js:21
#, fuzzy
#| msgid "The request took to long. Please try again."
msgid "The request took too long. Please try again."
msgstr "استغرقت العملية فترة طويلة، الرجاء المحاولة مرة أخرى."
msgstr "استغرق بناء على طلب لفترة طويلة. حاول مرة اخرى."
#: pretix/static/pretixbase/js/asynctask.js:150
#: pretix/static/pretixcontrol/js/ui/mail.js:26
msgid ""
"We currently cannot reach the server. Please try again. Error code: {code}"
msgstr "لم نستطع معالجة طلبك، ولكننا نواصل المحاولة. رمز الخطأ : {code}"
msgstr ""
"نحن في الوقت الراهن لا يمكن أن تصل إلى الخادم. حاول مرة اخرى. رمز الخطأ: "
"{كود}"
#: pretix/static/pretixbase/js/asynctask.js:171
msgid "We are processing your request …"
msgstr "نقوم بمعالجة طلبك "
msgstr "نحن معالجة طلبك ..."
#: pretix/static/pretixbase/js/asynctask.js:179
msgid ""
@@ -115,8 +121,8 @@ msgid ""
"than one minute, please check your internet connection and then reload this "
"page and try again."
msgstr ""
"يجري الآن معالجة طلبك، اذا أخذت العملية أكثر من دقيقة، يرجى التحقق من اتصالك "
"بالإنترنت ثم حاول مرة أخرى."
"نحن نرسل حاليا طلبك إلى الخادم. إذا كان هذا يأخذ دقيقة تعد من واحد، يرجى "
"التحقق من اتصالك بالإنترنت ثم إعادة تحميل هذه الصفحة وحاول مرة أخرى."
#: pretix/static/pretixbase/js/asynctask.js:216
#: pretix/static/pretixcontrol/js/ui/main.js:34

View File

@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-12-05 13:33+0000\n"
"PO-Revision-Date: 2020-01-08 03:00+0000\n"
"Last-Translator: oocf <oswaldocerna@gmail.com>\n"
"PO-Revision-Date: 2019-11-22 10:01+0000\n"
"Last-Translator: Carolina Fernández <cfermart@gmail.com>\n"
"Language-Team: Spanish <https://translate.pretix.eu/projects/pretix/pretix/"
"es/>\n"
"Language: es\n"
@@ -3168,7 +3168,7 @@ msgstr ""
#: pretix/base/models/vouchers.py:178
msgid "Specific seat"
msgstr "Asiento especifico"
msgstr ""
#: pretix/base/models/vouchers.py:182
#: pretix/control/templates/pretixcontrol/vouchers/index.html:114
@@ -5928,7 +5928,7 @@ msgstr ""
#: pretix/control/forms/event.py:105
msgid "Grant access to team"
msgstr "Dar acceso al equipo"
msgstr ""
#: pretix/control/forms/event.py:106
msgid ""
@@ -6319,7 +6319,7 @@ msgstr ""
#: pretix/control/forms/event.py:518
msgid "Social media image"
msgstr "Imagen de redes sociales"
msgstr ""
#: pretix/control/forms/event.py:521
msgid ""
@@ -19645,11 +19645,11 @@ msgstr "Italiano"
#: pretix/settings.py:413
msgid "Russian"
msgstr "Ruso"
msgstr ""
#: pretix/settings.py:414
msgid "Latvian"
msgstr "Letón"
msgstr ""
#: pretix/settings.py:415
msgid "Chinese (simplified)"

View File

@@ -8,10 +8,10 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-12-05 13:34+0000\n"
"PO-Revision-Date: 2019-12-20 19:00+0000\n"
"Last-Translator: Patrick Arminio <patrick.arminio@gmail.com>\n"
"Language-Team: Italian <https://translate.pretix.eu/projects/pretix/"
"pretix-js/it/>\n"
"PO-Revision-Date: 2019-09-13 18:00+0000\n"
"Last-Translator: Gianmarco Palumbo <pal_gm@hotmail.it>\n"
"Language-Team: Italian <https://translate.pretix.eu/projects/pretix/pretix-"
"js/it/>\n"
"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -160,11 +160,11 @@ msgstr "Oggetto testo"
#: pretix/static/pretixcontrol/js/ui/editor.js:426
msgid "Barcode area"
msgstr "Area codice a barra"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/editor.js:428
msgid "Powered by pretix"
msgstr "Realizzato con pretix"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/editor.js:430
msgid "Object"
@@ -172,7 +172,7 @@ msgstr "Oggetto"
#: pretix/static/pretixcontrol/js/ui/editor.js:434
msgid "Ticket design"
msgstr "Design biglietto"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/editor.js:687
msgid "Saving failed."
@@ -233,7 +233,7 @@ msgstr "Clicca per chiudere"
#: pretix/static/pretixcontrol/js/ui/main.js:749
msgid "You have unsaved changes!"
msgstr "Hai cambiamenti non salvati!"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/orderchange.js:24
msgid "Calculating default price…"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-12-05 13:33+0000\n"
"PO-Revision-Date: 2020-01-09 22:00+0000\n"
"PO-Revision-Date: 2019-11-19 15:55+0000\n"
"Last-Translator: Maarten van den Berg <maartenberg1@gmail.com>\n"
"Language-Team: Dutch (informal) <https://translate.pretix.eu/projects/pretix/"
"pretix/nl_Informal/>\n"
@@ -192,8 +192,10 @@ msgid "Circular dependency between questions detected."
msgstr "Kringafhankelijkheid tussen vragen gedetecteerd."
#: pretix/api/serializers/item.py:271 pretix/control/forms/item.py:86
#, fuzzy
#| msgid "This question will be asked during check-in."
msgid "This type of question cannot be asked during check-in."
msgstr "Deze soort vraag kan niet bij het inchecken worden gesteld."
msgstr "Deze vraag zal bij het inchecken worden gesteld."
#: pretix/api/serializers/organizer.py:43 pretix/control/forms/organizer.py:363
msgid ""
@@ -2396,8 +2398,10 @@ msgid "Country code (ISO 3166-1 alpha-2)"
msgstr "Landcode (ISO 3166-1 alpha-2)"
#: pretix/base/models/items.py:991
#, fuzzy
#| msgid "Line number"
msgid "Phone number"
msgstr "Telefoonnummer"
msgstr "Regelnummer"
#: pretix/base/models/items.py:1002 pretix/base/models/items.py:1056
#: pretix/control/forms/item.py:43
@@ -3145,7 +3149,7 @@ msgstr ""
#: pretix/base/models/vouchers.py:178
msgid "Specific seat"
msgstr "Specifieke zitplaats"
msgstr ""
#: pretix/base/models/vouchers.py:182
#: pretix/control/templates/pretixcontrol/vouchers/index.html:114
@@ -3212,12 +3216,12 @@ msgstr ""
"Het is op dit moment niet mogelijk om vouchers voor add-onproducten te maken."
#: pretix/base/models/vouchers.py:245 pretix/base/models/vouchers.py:338
#, fuzzy
#| msgid "You cannot select a quota and a specific product at the same time."
msgid ""
"You need to select a specific product or quota if this voucher should "
"reserve tickets."
msgstr ""
"Je moet een specifiek product of quotum kiezen als er kaartjes moeten worden "
"gereserveerd voor deze voucher."
msgstr "Je kan niet tegelijk een quotum en een specifiek product selecteren."
#: pretix/base/models/vouchers.py:255
#, python-format
@@ -3254,13 +3258,16 @@ msgid "A voucher with this code already exists."
msgstr "Er bestaat al een voucher met deze code."
#: pretix/base/models/vouchers.py:355
#, fuzzy
#| msgid "You need to select a specific seat."
msgid "You need to choose a date if you select a seat."
msgstr "Je moet een datum kiezen als je een stoel kiest."
msgstr "Je moet een specifieke stoel kiezen."
#: pretix/base/models/vouchers.py:364
#, python-brace-format
#, fuzzy, python-brace-format
#| msgid "The selected date does not exist in this event series."
msgid "The specified seat ID \"{id}\" does not exist for this event."
msgstr "De gekozen stoel met nummer \"{id}\" bestaat niet voor dit evenement."
msgstr "De geselecteerde datum bestaat niet in deze evenementenreeks."
#: pretix/base/models/vouchers.py:368
#, python-brace-format
@@ -3268,27 +3275,31 @@ msgid ""
"The seat \"{id}\" is currently unavailable (blocked, already sold or a "
"different voucher)."
msgstr ""
"De stoel \"{id}\" is momenteel niet beschikbaar (geblokkeerd, al verkocht of "
"toegewezen aan een andere voucher)."
#: pretix/base/models/vouchers.py:373
#, fuzzy
#| msgid "You need to select a specific seat."
msgid "You need to choose a specific product if you select a seat."
msgstr "Je moet een specifiek product kiezen als je een stoel kiest."
msgstr "Je moet een specifieke stoel kiezen."
#: pretix/base/models/vouchers.py:376
#, fuzzy
#| msgid "This gift card can only be used in test mode."
msgid "Seat-specific vouchers can only be used once."
msgstr ""
"Vouchers voor een specifieke stoel kunnen maar één keer worden gebruikt."
msgstr "Deze cadeaubon kan alleen in de testmodus worden gebruikt."
#: pretix/base/models/vouchers.py:379
#, python-brace-format
#, fuzzy, python-brace-format
#| msgid "You need to choose exactly one option from this category."
#| msgid_plural "You need to choose %(min_count)s options from this category."
msgid "You need to choose the product \"{prod}\" for this seat."
msgstr "Je moet het product \"{prod}\" kiezen voor deze stoel."
msgstr "Je moet precies één optie kiezen uit deze categorie."
#: pretix/base/models/vouchers.py:382
#, python-brace-format
#, fuzzy, python-brace-format
#| msgid "The identifier \"{}\" is already used for a different option."
msgid "The seat \"{id}\" is already sold or currently blocked."
msgstr "De stoel \"{id}\" is al verkocht of geblokkeerd."
msgstr "Het kenmerk \"{}\" wordt al voor een andere optie gebruikt."
#: pretix/base/models/waitinglist.py:37
msgid "On waiting list since"
@@ -3570,14 +3581,12 @@ msgstr ""
#: pretix/base/payment.py:291
msgid "Restrict to specific sales channels"
msgstr "Beperken tot specifieke verkoopkanalen"
msgstr ""
#: pretix/base/payment.py:299
msgid ""
"Only allow the usage of this payment provider in the following sales channels"
msgstr ""
"Sta het gebruik van deze betalingsprovider alleen toe voor de volgende "
"verkoopkanalen"
#: pretix/base/payment.py:331
msgctxt "invoice"
@@ -3761,8 +3770,10 @@ msgid "Ticket code (barcode content)"
msgstr "Kaartjescode (waarde van QR-code)"
#: pretix/base/pdf.py:57
#, fuzzy
#| msgid "Order position"
msgid "Order position number"
msgstr "Plaatsnummer van bestelling"
msgstr "Besteld product"
#: pretix/base/pdf.py:62 pretix/control/forms/event.py:1563
#: pretix/control/templates/pretixcontrol/items/index.html:33
@@ -4168,8 +4179,11 @@ msgid "This voucher is not valid for this product."
msgstr "Deze voucher is niet geldig voor dit product."
#: pretix/base/services/cart.py:84
#, fuzzy
#| msgctxt "subevent"
#| msgid "This voucher is not valid for this event date."
msgid "This voucher is not valid for this seat."
msgstr "Deze voucher is niet geldig voor deze stoel."
msgstr "Deze voucher is niet geldig voor deze evenementsdatum."
#: pretix/base/services/cart.py:86
msgid "Your voucher is valid for a product that is currently not for sale."
@@ -4666,22 +4680,28 @@ msgstr ""
"contact op met de organisator van het evenement voor meer informatie."
#: pretix/base/services/seating.py:35 pretix/base/services/seating.py:86
#, python-format
#, fuzzy, python-format
#| msgid ""
#| "You can not change the plan since seat \"{}\" is not present in the new "
#| "plan and is already sold."
msgid ""
"You can not change the plan since seat \"%s\" is not present in the new plan "
"and is already sold."
msgstr ""
"Je kan de plattegrond niet veranderen, omdat stoel \"%s\" niet aanwezig is "
"Je kan de plattegrond niet veranderen, omdat stoel \"{}\" niet aanwezig is "
"in de nieuwe plattegrond, en deze stoel al is verkocht."
#: pretix/base/services/seating.py:89
#, python-format
#, fuzzy, python-format
#| msgid ""
#| "You can not change the plan since seat \"{}\" is not present in the new "
#| "plan and is already sold."
msgid ""
"You can not change the plan since seat \"%s\" is not present in the new plan "
"and is already used in a voucher."
msgstr ""
"Je kan de plattegrond niet veranderen, omdat stoel \"%s\" niet aanwezig is "
"in de nieuwe plattegrond, en deze stoel al is gebruikt voor een voucher."
"Je kan de plattegrond niet veranderen, omdat stoel \"{}\" niet aanwezig is "
"in de nieuwe plattegrond, en deze stoel al is verkocht."
#: pretix/base/services/shredder.py:71
msgid ""
@@ -5761,7 +5781,7 @@ msgstr ""
#: pretix/control/forms/event.py:105
msgid "Grant access to team"
msgstr "Geef toegang aan team"
msgstr ""
#: pretix/control/forms/event.py:106
msgid ""
@@ -5769,13 +5789,10 @@ msgid ""
"have permission to edit all events under this organizer. Please select one "
"of your existing teams that will be granted access to this event."
msgstr ""
"Je kan evenementen aanmaken voor deze organisator, maar je hebt geen "
"toestemming om alle evenementen van deze organisator te bewerken. Geef één "
"van de teams waar je in zit toegang tot dit evenement."
#: pretix/control/forms/event.py:111
msgid "Create a new team for this event with me as the only member"
msgstr "Maak een nieuw team voor dit evenement aan met mij als het enige lid"
msgstr ""
#: pretix/control/forms/event.py:153 pretix/control/forms/event.py:291
msgid ""
@@ -6135,7 +6152,7 @@ msgstr ""
#: pretix/control/forms/event.py:518
msgid "Social media image"
msgstr "Social media-afbeelding"
msgstr ""
#: pretix/control/forms/event.py:521
msgid ""
@@ -6145,17 +6162,10 @@ msgid ""
"preview, so we recommend to make sure it still looks good only the center "
"square is shown. If you do not fill this, we will use the logo given above."
msgstr ""
"Deze afbeelding zal als voorvertoning worden gebruikt als je links naar je "
"kaartjeswinkel op sociale media plaatst. Facebook raadt aan om een "
"afbeeldingsgrootte van 1200 bij 630 pixels te gebruiken, maar sommige "
"platforms zoals WhatsApp en Reddit tonen alleen een vierkante voorvertoning. "
"We raden aan om je afbeelding zo te ontwerpen zodat hij er nog steeds goed "
"uitziet als alleen het middelste vierkant wordt getoond. Als je hier geen "
"afbeelding uploadt zullen we het logo dat hierboven is geüpload gebruiken."
#: pretix/control/forms/event.py:532
msgid "Help text of the email field"
msgstr "Helptekst van het e-mailveld"
msgstr ""
#: pretix/control/forms/event.py:538
msgid "End of presale text"
@@ -7502,7 +7512,7 @@ msgstr ""
#: pretix/control/forms/item.py:428
msgid "Shown independently of other products"
msgstr "Toon onafhankelijk van andere producten"
msgstr ""
#: pretix/control/forms/item.py:507
#, python-format
@@ -7801,7 +7811,7 @@ msgstr ""
#: pretix/control/forms/organizer.py:216
msgid "Allow creating a new team during event creation"
msgstr "Sta het aanmaken van nieuwe teams bij het aanmaken van evenementen toe"
msgstr ""
#: pretix/control/forms/organizer.py:217
msgid ""
@@ -7810,11 +7820,6 @@ msgid ""
"allows users to create an event-specified team on-the-fly, even when they do "
"not have \"Can change teams and permissions\" permission."
msgstr ""
"Gebruikers die geen toegang hebben tot alle evenementen onder deze "
"organisator moeten een van hun teams selecteren om toegang te geven aan hun "
"aangemaakte evenement. Deze instelling staat gebruikers toe om een nieuw "
"team aan te maken tijdens het aanmaken van een evenement, zelfs als de "
"gebruikers niet de permissie \"Kan teams en machtigingen aanpassen\" hebben."
#: pretix/control/forms/organizer.py:244
msgid "We strongly suggest to use a shade of red."
@@ -7972,7 +7977,7 @@ msgstr "Je wijzigingen konden niet worden opgeslagen. Zie onder voor details."
#: pretix/control/forms/vouchers.py:120
msgid "Specific seat ID"
msgstr "Specifiek stoelnummer"
msgstr ""
#: pretix/control/forms/vouchers.py:155
msgid "Invalid product selected."
@@ -8065,7 +8070,7 @@ msgstr "Het aantal keren dat ELKE van deze vouchers kan worden gebruikt."
#: pretix/control/forms/vouchers.py:293
msgid "Specific seat IDs"
msgstr "Specifieke stoelnummers"
msgstr ""
#: pretix/control/forms/vouchers.py:307
msgid "CSV input needs to contain a header row in the first line."
@@ -8110,8 +8115,10 @@ msgid ""
msgstr "Je genereerde {codes} vouchers, maar hebt {recp} ontvangers opgegeven."
#: pretix/control/forms/vouchers.py:361
#, fuzzy
#| msgid "You need to specify either a quota or a product."
msgid "You need to specify as many seats as voucher codes."
msgstr "Je moet evenveel stoelnummers als vouchercodes opgeven."
msgstr "Je moet een quotum of een product opgeven."
#: pretix/control/logdisplay.py:30
msgid "The order has been changed:"
@@ -8435,9 +8442,10 @@ msgid "Payment {local_id} has been canceled."
msgstr "Betaling {local_id} is geannuleerd."
#: pretix/control/logdisplay.py:228
#, python-brace-format
#, fuzzy, python-brace-format
#| msgid "Payment {local_id} has failed."
msgid "Cancelling payment {local_id} has failed."
msgstr "Het annuleren van betaling {local_id} is mislukt."
msgstr "Betaling {local_id} is mislukt."
#: pretix/control/logdisplay.py:229
#, python-brace-format
@@ -11088,13 +11096,16 @@ msgstr ""
"het alleen binnen een bepaalde tijd beschikbaar moet zijn."
#: pretix/control/templates/pretixcontrol/item/base.html:29
#, fuzzy
#| msgid ""
#| "This product is currently not being sold since you configured below that "
#| "it should only be available in a certain timeframe."
msgid ""
"This product is currently not being shown since you configured below that it "
"should only be visible if a certain other quota is already sold out."
msgstr ""
"Dit product wordt momenteel niet getoond, omdat je hieronder hebt ingesteld "
"dat het product alleen moet worden getoond wanneer een bepaald ander quotum "
"al is uitverkocht."
"Dit product is momenteel niet te koop, omdat je hieronder hebt ingesteld dat "
"het alleen binnen een bepaalde tijd beschikbaar moet zijn."
#: pretix/control/templates/pretixcontrol/item/create.html:23
msgid "Quota settings"
@@ -11728,7 +11739,7 @@ msgstr "Beheer je eigen applicaties"
#: pretix/control/templates/pretixcontrol/oauth/authorized.html:18
msgid "Permissions"
msgstr "Permissies"
msgstr "Rechten"
#: pretix/control/templates/pretixcontrol/oauth/authorized.html:59
msgid "No applications have access to your pretix account."
@@ -12409,11 +12420,12 @@ msgid "Create a new gift card"
msgstr "Nieuwe cadeaubon aanmaken"
#: pretix/control/templates/pretixcontrol/order/refund_choose.html:103
#, fuzzy
#| msgid "This gift card is not accepted by this event organizer."
msgid ""
"The gift card can be used to buy tickets for all events of this organizer."
msgstr ""
"Deze cadeaubon kan worden gebruikt om kaartjes te kopen voor alle "
"evenementen van deze organisator."
"Deze cadeaubon wordt niet geaccepteerd door de organisator van dit evenement."
#: pretix/control/templates/pretixcontrol/order/refund_choose.html:111
msgid "Manual refund"
@@ -15070,15 +15082,17 @@ msgstr ""
"Je kan de terugbetaling hieronder als voltooid aanmerken."
#: pretix/control/views/orders.py:848
#, fuzzy
#| msgid "The gift card has been created and can now be used."
msgid ""
"A new gift card was created. You can now send the user their gift card code."
msgstr ""
"De cadeaubon is aangemaakt. Je kan de cadeauboncode nu naar de gebruiker "
"sturen."
msgstr "De cadeaubon is aangemaakt en kan nu worden gebruikt."
#: pretix/control/views/orders.py:855
#, fuzzy
#| msgid "Gift card code"
msgid "Your gift card code"
msgstr "Je cadeauboncode"
msgstr "Cadeauboncode"
#: pretix/control/views/orders.py:856
#, python-brace-format
@@ -15092,14 +15106,6 @@ msgid ""
"\n"
"Your {event} team"
msgstr ""
"Hallo,\n"
"\n"
"We hebben je {amount} voor je bestelling terugbetaald.\n"
"\n"
"Je kan de cadeauboncode {giftcard} gebruiken om te betalen voor toekomstige "
"bestellingen in onze winkel.\n"
"\n"
"De organisatie van {event}"
#: pretix/control/views/orders.py:866
msgid "The refunds you selected do not match the selected total refund amount."
@@ -16239,8 +16245,6 @@ msgid ""
"Negative amount but refund can't be logged, please create manual refund "
"first."
msgstr ""
"Negatief bedrag maar terugbetaling kan niet worden opgeslagen, maak eerst "
"een handmatige terugbetaling aan."
#: pretix/plugins/banktransfer/views.py:90
msgid "The order is already marked as paid."
@@ -19010,9 +19014,11 @@ msgstr ""
"weer kaartjes beschikbaar zijn."
#: pretix/presale/views/widget.py:243
#, fuzzy
#| msgid "<a %(a_attr)s>event ticketing powered by pretix</a>"
msgctxt "widget"
msgid "event ticketing powered by pretix"
msgstr "kaartverkoop mogelijk gemaakt door pretix"
msgstr "<a %(a_attr)s>kaartverkoop mogelijk gemaakt door pretix</a>"
#: pretix/presale/views/widget.py:258
msgid "This ticket shop is currently disabled."
@@ -19085,11 +19091,11 @@ msgstr "Italiaans"
#: pretix/settings.py:413
msgid "Russian"
msgstr "Russisch"
msgstr ""
#: pretix/settings.py:414
msgid "Latvian"
msgstr "Lets"
msgstr ""
#: pretix/settings.py:415
msgid "Chinese (simplified)"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-12-05 13:34+0000\n"
"PO-Revision-Date: 2019-12-16 04:00+0000\n"
"PO-Revision-Date: 2019-08-03 22:00+0000\n"
"Last-Translator: Maarten van den Berg <maartenberg1@gmail.com>\n"
"Language-Team: Dutch (informal) <https://translate.pretix.eu/projects/pretix/"
"pretix-js/nl_Informal/>\n"
@@ -97,6 +97,8 @@ msgstr ""
#: pretix/static/pretixbase/js/asynctask.js:125
#: pretix/static/pretixcontrol/js/ui/mail.js:21
#, fuzzy
#| msgid "The request took to long. Please try again."
msgid "The request took too long. Please try again."
msgstr "De aanvraag duurde te lang, probeer het alsjeblieft opnieuw."
@@ -231,7 +233,7 @@ msgstr "Klik om te sluiten"
#: pretix/static/pretixcontrol/js/ui/main.js:749
msgid "You have unsaved changes!"
msgstr "Je hebt nog niet opgeslagen wijzigingen!"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/orderchange.js:24
msgid "Calculating default price…"

View File

@@ -3,14 +3,7 @@
{% load static %}
{% block title %}{% trans "Import bank data" %}{% endblock %}
{% block content %}
<h1>
{% trans "Import bank data" %}
{% if runningimport %}
<small>{% trans "Import currently running…" %}</small>
{% else %}
<small>{% blocktrans trimmed with date=lastimport.created|date:"SHORT_DATETIME_FORMAT" %}Last import: {{ date }}{% endblocktrans %}</small>
{% endif %}
</h1>
<h1>{% trans "Import bank data" %}</h1>
{% block inner %}
{% endblock %}
{% endblock %}

View File

@@ -3,16 +3,7 @@
{% load static %}
{% block title %}{% trans "Import bank data" %}{% endblock %}
{% block content %}
<h1>
{% trans "Import bank data" %}
{% if runningimport %}
<small>{% trans "Import currently running…" %}</small>
{% else %}
<small>{% blocktrans trimmed with date=lastimport.created|date:"SHORT_DATETIME_FORMAT" %}Last import:
{{ date }}{% endblocktrans %}</small>
{% endif %}
</h1>
<h1>{% trans "Import bank data" %}</h1>
{% block inner %}
{% endblock %}
{% endblock %}

View File

@@ -481,25 +481,7 @@ class ImportView(ListView):
if not self.request.event.has_subevents and self.request.event.settings.get('payment_term_last'):
if now() > self.request.event.payment_term_last:
ctx['no_more_payments'] = True
ctx['lastimport'] = BankImportJob.objects.filter(
state=BankImportJob.STATE_COMPLETED,
organizer=self.request.organizer,
event=self.request.event
).order_by('created').last()
ctx['runningimport'] = BankImportJob.objects.filter(
state__in=[BankImportJob.STATE_PENDING, BankImportJob.STATE_RUNNING],
event=self.request.event
).order_by('created').last()
else:
ctx['lastimport'] = BankImportJob.objects.filter(
state=BankImportJob.STATE_COMPLETED,
organizer=self.request.organizer,
event__isnull=True
).order_by('created').last()
ctx['runningimport'] = BankImportJob.objects.filter(
state__in=[BankImportJob.STATE_PENDING, BankImportJob.STATE_RUNNING],
event__isnull=True
).order_by('created').last()
ctx['basetpl'] = 'pretixplugins/banktransfer/import_base_organizer.html'
ctx['organizer'] = self.request.organizer
return ctx

View File

@@ -513,12 +513,9 @@ class OrderTaxListReport(ListExporter):
)
tax_rates = sorted(tax_rates)
headers = [
_('Order code'), _('Order date'),
_('Company'), _('Name'),
_('Country'), _('VAT ID'), _('Status'), _('Payment date'), _('Order total'),
yield [
_('Order code'), _('Order date'), _('Status'), _('Payment date'), _('Order total'),
] + sum(([str(t) + ' % ' + _('Gross'), str(t) + ' % ' + _('Tax')] for t in tax_rates), [])
yield headers
op_date = OrderPayment.objects.filter(
order=OuterRef('order'),
@@ -534,8 +531,7 @@ class OrderTaxListReport(ListExporter):
order__event=self.event,
).annotate(payment_date=Subquery(op_date, output_field=models.DateTimeField())).values(
'order__code', 'order__datetime', 'payment_date', 'order__total', 'tax_rate', 'order__status',
'order__id', 'order__invoice_address__name_cached', 'order__invoice_address__company',
'order__invoice_address__country', 'order__invoice_address__vat_id'
'order__id'
).annotate(prices=Sum('price'), tax_values=Sum('tax_value')).order_by(
'order__datetime' if form_data['sort'] == 'datetime' else 'payment_date',
'order__datetime',
@@ -561,10 +557,6 @@ class OrderTaxListReport(ListExporter):
row = [
op['order__code'],
date_format(op['order__datetime'].astimezone(tz), "SHORT_DATE_FORMAT"),
op['order__invoice_address__company'],
op['order__invoice_address__name_cached'],
op['order__invoice_address__country'],
op['order__invoice_address__vat_id'],
status_labels[op['order__status']],
date_format(op['payment_date'], "SHORT_DATE_FORMAT") if op['payment_date'] else '',
round_decimal(op['order__total'], self.event.currency),
@@ -573,21 +565,21 @@ class OrderTaxListReport(ListExporter):
for i, rate in enumerate(tax_rates):
odata = fee_sum_cache.get((op['order__id'], rate))
if odata:
row[9 + 2 * i] = odata['grosssum'] or 0
row[10 + 2 * i] = odata['taxsum'] or 0
row[5 + 2 * i] = odata['grosssum'] or 0
row[6 + 2 * i] = odata['taxsum'] or 0
tax_sums[rate] += odata['taxsum'] or 0
price_sums[rate] += odata['grosssum'] or 0
i = tax_rates.index(op['tax_rate'])
row[9 + 2 * i] = round_decimal(row[9 + 2 * i] + op['prices'], self.event.currency)
row[10 + 2 * i] = round_decimal(row[10 + 2 * i] + op['tax_values'], self.event.currency)
row[5 + 2 * i] = round_decimal(row[5 + 2 * i] + op['prices'], self.event.currency)
row[6 + 2 * i] = round_decimal(row[6 + 2 * i] + op['tax_values'], self.event.currency)
tax_sums[op['tax_rate']] += op['tax_values']
price_sums[op['tax_rate']] += op['prices']
if row:
yield row
yield [
_('Total'), '', '', '', '', '', '', '', ''
_('Total'), '', '', '', ''
] + sum(([
round_decimal(price_sums.get(t) or Decimal('0.00'), self.event.currency),
round_decimal(tax_sums.get(t) or Decimal('0.00'), self.event.currency)

View File

@@ -213,7 +213,7 @@ As with all plugin signals, the ``sender`` keyword argument will contain the eve
"""
front_page_top = EventPluginSignal(
providing_args=["request", "subevent"]
providing_args=[]
)
"""
This signal is sent out to display additional information on the frontpage above the list
@@ -236,7 +236,7 @@ receivers are expected to return HTML.
"""
front_page_bottom = EventPluginSignal(
providing_args=["request", "subevent"]
providing_args=[]
)
"""
This signal is sent out to display additional information on the frontpage below the list
@@ -246,17 +246,6 @@ As with all plugin signals, the ``sender`` keyword argument will contain the eve
receivers are expected to return HTML.
"""
front_page_bottom_widget = EventPluginSignal(
providing_args=["request", "subevent"]
)
"""
This signal is sent out to display additional information on the frontpage below the list
of products if the front page is shown in the widget.
As with all plugin signals, the ``sender`` keyword argument will contain the event. The
receivers are expected to return HTML.
"""
checkout_all_optional = EventPluginSignal(
providing_args=['request']
)

View File

@@ -101,7 +101,7 @@
{% for form in forms %}
{% if form.pos.item != pos.item %}
{# Add-Ons #}
<legend>+ {{ form.pos.item.name }}{% if form.pos.variation %} {{ form.pos.variation.value }}{% endif %}</legend>
<legend>+ {{ form.pos.item }}</legend>
{% endif %}
{% bootstrap_form form layout="checkout" %}
{% endfor %}

View File

@@ -252,7 +252,7 @@
<div class="col-md-3 col-xs-6 col-md-offset-3 price">
<strong>{{ cart.total|money:event.currency }}</strong>
{% if editable and vouchers_exist and not cart.all_with_voucher %}
{% if editable and show_vouchers and not cart.all_with_voucher %}
<br>
<a class="js-only apply-voucher-toggle" href="#">
<span class="fa fa-tag"></span> {% trans "Redeem a voucher" %}
@@ -273,4 +273,4 @@
{% endif %}
</div>
<div class="clearfix"></div>
</div>
</div>

View File

@@ -8,15 +8,14 @@
{% for tup in items_by_category %}
<section>
{% if tup.0 %}
<h3 id="category-{{ tup.0.id }}">{{ tup.0.name }}</h3>
<h3>{{ tup.0.name }}</h3>
{% if tup.0.description %}
<p>{{ tup.0.description|localize|rich_text }}</p>
{% endif %}
{% endif %}
{% for item in tup.1 %}
{% if item.has_variations %}
<details class="item-with-variations" {% if event.settings.show_variations_expanded %}open{% endif %}
id="item-{{ item.id }}">
<details class="item-with-variations" {% if event.settings.show_variations_expanded %}open{% endif %}>
<summary class="row-fluid product-row headline">
<div class="col-md-8 col-xs-12">
{% if item.picture %}
@@ -172,7 +171,7 @@
</div>
</details>
{% else %}
<div class="row-fluid product-row simple" id="item-{{ item.id }}">
<div class="row-fluid product-row simple">
<div class="col-md-8 col-xs-12">
{% if item.picture %}
<a href="{{ item.picture.url }}" class="productpicture"

View File

@@ -206,7 +206,7 @@
</div>
{% eventsignal event "pretix.presale.signals.front_page_top" request=request subevent=subevent %}
{% eventsignal event "pretix.presale.signals.front_page_top" %}
{% endif %}
{% if ev.presale_is_running or event.settings.show_items_outside_presale_period %}
@@ -269,7 +269,7 @@
</section>
{% endif %}
{% if not cart_namespace %}
{% eventsignal event "pretix.presale.signals.front_page_bottom" subevent=subevent request=request %}
{% eventsignal event "pretix.presale.signals.front_page_bottom" %}
<section class="front-page">
<h3>{% trans "If you already ordered a ticket" %}</h3>
<div>
@@ -290,7 +290,5 @@
<div class="clearfix"></div>
</div>
</section>
{% else %}
{% eventsignal event "pretix.presale.signals.front_page_bottom_widget" subevent=subevent request=request %}
{% endif %}
{% endblock %}

View File

@@ -30,7 +30,7 @@
<input type="hidden" name="subevent" value="{{ subevent.id|default_if_none:"" }}" />
<input type="hidden" name="_voucher_code" value="{{ voucher.code }}">
{% if seating_available %}
{% if voucher.seating_available %}
{% if event.has_subevents %}
{% eventsignal event "pretix.presale.signals.render_seating_plan" request=request subevent=subevent voucher=voucher %}
{% else %}

View File

@@ -338,8 +338,7 @@ class CartApplyVoucher(EventViewMixin, CartActionMixin, AsyncAction, View):
def post(self, request, *args, **kwargs):
if 'voucher' in request.POST:
return self.do(self.request.event.id, request.POST.get('voucher'), get_or_create_cart_id(self.request),
translation.get_language(), request.sales_channel.identifier)
return self.do(self.request.event.id, request.POST.get('voucher'), get_or_create_cart_id(self.request), translation.get_language())
else:
if 'ajax' in self.request.GET or 'ajax' in self.request.POST:
return JsonResponse({
@@ -363,8 +362,7 @@ class CartRemove(EventViewMixin, CartActionMixin, AsyncAction, View):
def post(self, request, *args, **kwargs):
if 'id' in request.POST:
return self.do(self.request.event.id, request.POST.get('id'), get_or_create_cart_id(self.request),
translation.get_language(), request.sales_channel.identifier)
return self.do(self.request.event.id, request.POST.get('id'), get_or_create_cart_id(self.request), translation.get_language())
else:
if 'ajax' in self.request.GET or 'ajax' in self.request.POST:
return JsonResponse({
@@ -384,8 +382,7 @@ class CartClear(EventViewMixin, CartActionMixin, AsyncAction, View):
return _('Your cart is now empty.')
def post(self, request, *args, **kwargs):
return self.do(self.request.event.id, get_or_create_cart_id(self.request), translation.get_language(),
request.sales_channel.identifier)
return self.do(self.request.event.id, get_or_create_cart_id(self.request), translation.get_language())
@method_decorator(allow_cors_if_namespaced, 'dispatch')
@@ -459,7 +456,6 @@ class RedeemView(NoSearchIndexViewMixin, EventViewMixin, TemplateView):
context['items_by_category'] = item_group_by_category(items)
context['subevent'] = self.subevent
context['seating_available'] = self.voucher.seating_available(self.subevent)
context['new_tab'] = (
'require_cookie' in self.request.GET and

View File

@@ -306,13 +306,6 @@ class EventIndex(EventViewMixin, EventListMixin, CartMixin, TemplateView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Show voucher option if an event is selected and vouchers exist
vouchers_exist = self.request.event.cache.get('vouchers_exist')
if vouchers_exist is None:
vouchers_exist = self.request.event.vouchers.exists()
self.request.event.cache.set('vouchers_exist', vouchers_exist)
if not self.request.event.has_subevents or self.subevent:
# Fetch all items
items, display_add_to_cart = get_grouped_items(self.request.event, self.subevent,
@@ -323,11 +316,14 @@ class EventIndex(EventViewMixin, EventListMixin, CartMixin, TemplateView):
context['items_by_category'] = item_group_by_category(items)
context['display_add_to_cart'] = display_add_to_cart
# Show voucher option if an event is selected and vouchers exist
vouchers_exist = self.request.event.cache.get('vouchers_exist')
if vouchers_exist is None:
vouchers_exist = self.request.event.vouchers.exists()
self.request.event.cache.set('vouchers_exist', vouchers_exist)
context['show_vouchers'] = vouchers_exist
context['vouchers_exist'] = vouchers_exist
else:
context['show_vouchers'] = False
context['vouchers_exist'] = vouchers_exist
context['ev'] = self.subevent or self.request.event
context['subevent'] = self.subevent

View File

@@ -30,9 +30,7 @@ from pretix.base.services.invoices import (
generate_invoice, invoice_pdf, invoice_pdf_task, invoice_qualified,
)
from pretix.base.services.mail import SendMailException
from pretix.base.services.orders import (
OrderError, cancel_order, change_payment_provider,
)
from pretix.base.services.orders import cancel_order, change_payment_provider
from pretix.base.services.tickets import generate, invalidate_cache
from pretix.base.signals import (
allow_ticket_download, order_modified, register_ticket_outputs,
@@ -206,8 +204,7 @@ class OrderDetails(EventViewMixin, OrderDetailMixin, CartMixin, TicketPageMixin,
ctx['can_pay'] = True
break
if lp and lp.state not in (OrderPayment.PAYMENT_STATE_CONFIRMED, OrderPayment.PAYMENT_STATE_REFUNDED,
OrderPayment.PAYMENT_STATE_CANCELED):
if lp and lp.state not in (OrderPayment.PAYMENT_STATE_CONFIRMED, OrderPayment.PAYMENT_STATE_REFUNDED):
ctx['last_payment'] = self.order.payments.last()
pp = lp.payment_provider
@@ -785,20 +782,23 @@ class OrderDownloadMixin:
def get(self, request, *args, **kwargs):
if not self.output or not self.output.is_enabled:
return self.error(OrderError(_('You requested an invalid ticket output type.')))
return self.error(_('You requested an invalid ticket output type.'))
if 'async_id' in request.GET and settings.HAS_CELERY:
return self.get_result(request)
return self.post(request, *args, **kwargs)
ct = self.get_last_ct()
if ct:
return self.success(ct)
return self.http_method_not_allowed(request)
def post(self, request, *args, **kwargs):
if not self.output or not self.output.is_enabled:
return self.error(OrderError(_('You requested an invalid ticket output type.')))
return self.error(_('You requested an invalid ticket output type.'))
if not self.order or ('position' in kwargs and not self.order_position):
raise Http404(_('Unknown order code or not authorized to access this order.'))
if not self.order.ticket_download_available:
return self.error(OrderError(_('Ticket download is not (yet) enabled for this order.')))
return self.error(_('Ticket download is not (yet) enabled for this order.'))
if 'position' in kwargs and not self.order_position.generate_ticket:
return self.error(OrderError(_('Ticket download is not enabled for this product.')))
return self.error(_('Ticket download is not enabled for this product.'))
ct = self.get_last_ct()
if ct:
@@ -855,7 +855,6 @@ class OrderDownloadMixin:
@method_decorator(xframe_options_exempt, 'dispatch')
class OrderDownload(OrderDownloadMixin, EventViewMixin, OrderDetailMixin, AsyncAction, View):
task = generate
known_errortypes = ['OrderError']
def get_error_url(self):
return self.get_order_url()

View File

@@ -134,9 +134,6 @@ CSRF_TRUSTED_ORIGINS = [urlparse(SITE_URL).hostname]
TRUST_X_FORWARDED_FOR = config.get('pretix', 'trust_x_forwarded_for', fallback=False)
if config.get('pretix', 'trust_x_forwarded_proto', fallback=False):
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
PRETIX_PLUGINS_DEFAULT = config.get('pretix', 'plugins_default',
fallback='pretix.plugins.sendmail,pretix.plugins.statistics,pretix.plugins.checkinlists,pretix.plugins.autocheckin')
PRETIX_PLUGINS_EXCLUDE = config.get('pretix', 'plugins_exclude', fallback='').split(',')

View File

@@ -117,15 +117,10 @@ var editor = {
}
if (o.type === "textarea") {
var col = (new fabric.Color(o.getFill()))._source;
var bottom = editor.pdf_viewport.height - o.height - top;
if (o.downward) {
bottom = editor.pdf_viewport.height - top;
}
d.push({
type: "textarea",
locale: $("#pdf-info-locale").val(),
left: editor._px2mm(left).toFixed(2),
bottom: editor._px2mm(bottom).toFixed(2),
bottom: editor._px2mm(editor.pdf_viewport.height - o.height - top).toFixed(2),
fontsize: editor._px2pt(o.getFontSize()).toFixed(1),
color: col,
//lineheight: o.lineHeight,
@@ -133,10 +128,8 @@ var editor = {
bold: o.fontWeight === 'bold',
italic: o.fontStyle === 'italic',
width: editor._px2mm(o.width).toFixed(2),
downward: o.downward || false,
content: o.content,
text: o.text,
rotation: o.angle,
align: o.textAlign,
});
} else if (o.type === "barcodearea") {
@@ -178,27 +171,16 @@ var editor = {
o.setFontWeight(d.bold ? 'bold' : 'normal');
o.setFontStyle(d.italic ? 'italic' : 'normal');
o.setWidth(editor._mm2px(d.width));
o.downward = d.downward || false;
o.content = d.content;
o.setTextAlign(d.align);
if (d.rotation) {
o.rotate(d.rotation);
}
if (d.content === "other") {
o.setText(d.text);
} else {
o.setText(editor._get_text_sample(d.content));
}
if (d.locale) {
// The data format allows to set the locale per text field but we currently only expose a global field
$("#pdf-info-locale").val(d.locale);
}
}
var new_top = editor.pdf_viewport.height - editor._mm2px(d.bottom) - (o.height * o.scaleY);
if (o.downward) {
new_top = editor.pdf_viewport.height - editor._mm2px(d.bottom);
}
o.set('left', editor._mm2px(d.left));
o.set('top', new_top);
o.setCoords();
@@ -281,7 +263,6 @@ var editor = {
editor.fabric.on('object:selected', editor._update_toolbox);
editor.fabric.on('object:moving', editor._update_toolbox_values);
editor.fabric.on('object:modified', editor._update_toolbox_values);
editor.fabric.on('object:rotating', editor._update_toolbox_values);
editor.fabric.on('object:scaling', editor._update_toolbox_values);
editor._update_toolbox();
@@ -336,12 +317,8 @@ var editor = {
return;
}
}
var bottom = editor.pdf_viewport.height - o.height * o.scaleY - o.top;
if (o.downward) {
bottom = editor.pdf_viewport.height - o.top;
}
$("#toolbox-position-x").val(editor._px2mm(o.left).toFixed(2));
$("#toolbox-position-y").val(editor._px2mm(bottom).toFixed(2));
$("#toolbox-position-y").val(editor._px2mm(editor.pdf_viewport.height - o.height * o.scaleY - o.top).toFixed(2));
if (o.type === "barcodearea") {
$("#toolbox-squaresize").val(editor._px2mm(o.height * o.scaleY).toFixed(2));
@@ -356,12 +333,10 @@ var editor = {
$("#toolbox-fontfamily").val(o.fontFamily);
$("#toolbox").find("button[data-action=bold]").toggleClass('active', o.fontWeight === 'bold');
$("#toolbox").find("button[data-action=italic]").toggleClass('active', o.fontStyle === 'italic');
$("#toolbox").find("button[data-action=downward]").toggleClass('active', o.downward || false);
$("#toolbox").find("button[data-action=left]").toggleClass('active', o.textAlign === 'left');
$("#toolbox").find("button[data-action=center]").toggleClass('active', o.textAlign === 'center');
$("#toolbox").find("button[data-action=right]").toggleClass('active', o.textAlign === 'right');
$("#toolbox-textwidth").val(editor._px2mm(o.width).toFixed(2));
$("#toolbox-textrotation").val((o.angle || 0.0).toFixed(1));
if (o.type === "textarea") {
$("#toolbox-content").val(o.content);
$("#toolbox-content-other").toggle($("#toolbox-content").val() === "other");
@@ -384,11 +359,6 @@ var editor = {
}
var new_top = editor.pdf_viewport.height - editor._mm2px($("#toolbox-position-y").val()) - o.height * o.scaleY;
if (o.type === "textarea" || o.type === "text") {
if ($("#toolbox").find("button[data-action=downward]").is('.active')) {
new_top = editor.pdf_viewport.height - editor._mm2px($("#toolbox-position-y").val());
}
}
o.set('left', editor._mm2px($("#toolbox-position-x").val()));
o.set('top', new_top);
@@ -429,8 +399,6 @@ var editor = {
o.setTextAlign(align);
}
o.setWidth(editor._mm2px($("#toolbox-textwidth").val()));
o.downward = $("#toolbox").find("button[data-action=downward]").is('.active');
o.rotate(parseFloat($("#toolbox-textrotation").val()));
$("#toolbox-content-other").toggle($("#toolbox-content").val() === "other");
o.content = $("#toolbox-content").val();
if ($("#toolbox-content").val() === "other") {
@@ -479,14 +447,13 @@ var editor = {
left: 100,
top: 100,
width: editor._mm2px(50),
lockRotation: false,
lockRotation: true,
fontFamily: 'Open Sans',
lineHeight: 1,
content: 'item',
editable: false,
fontSize: editor._pt2px(13)
});
text.downward = true;
text.setControlsVisibility({
'tr': false,
'tl': false,
@@ -496,7 +463,7 @@ var editor = {
'mb': false,
'mr': true,
'ml': true,
'mtr': true
'mtr': false
});
editor.fabric.add(text);
editor._create_savepoint();
@@ -612,9 +579,6 @@ var editor = {
_on_keydown: function (e) {
var step = e.shiftKey ? editor._mm2px(10) : editor._mm2px(1);
var thing = editor.fabric.getActiveObject() ? editor.fabric.getActiveObject() : editor.fabric.getActiveGroup();
if ($("#source-container").is(':visible')) {
return true;
}
switch (e.keyCode) {
case 38: /* Up arrow */
thing.set('top', thing.get('top') - step);

View File

@@ -311,7 +311,7 @@ var form_handlers = function (el) {
dependency.closest('.form-group').find('input[name=' + dependency.attr("name") + ']').on("dp.change", update);
});
$("select[name$=state]:not([data-static])").each(function () {
$("select[name$=state]").each(function () {
var dependent = $(this),
counter = 0,
dependency = $(this).closest("form").find('select[name$=country]'),

View File

@@ -37,9 +37,9 @@ babel
django-i18nfield>=1.4.0
django-hijack>=2.1.10,<2.2.0
jsonschema
openpyxl==2.6.* # Bump to 3.0 after python 3.5 is dropped
openpyxl
django-oauth-toolkit==1.2.*
oauthlib==3.1.*
oauthlib==2.1.*
django-jsonfallback>=2.1.2
psycopg2-binary
# Stripe
@@ -63,4 +63,4 @@ redis==3.2.*
django-phonenumber-field==3.0.*
phonenumberslite==8.10.*
python-bidi==0.4.* # Support for arabic in reportlab
arabic-reshaper==2.0.15 # Support for Aabic in reportlab
arabic-reshaper==2.0.15 # Support for Aabic in reportlab

View File

@@ -42,7 +42,6 @@ except:
class CustomBuild(build):
def run(self):
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "pretix.settings")
os.environ.setdefault("PRETIX_IGNORE_CONFLICTS", "True")
import django
django.setup()
from django.conf import settings
@@ -143,9 +142,9 @@ setup(
'django-localflavor',
'jsonschema',
'django-hijack>=2.1.10,<2.2.0',
'openpyxl==2.6.*',
'openpyxl',
'django-oauth-toolkit==1.2.*',
'oauthlib==3.1.*',
'oauthlib==2.1.*',
'urllib3==1.24.*', # required by current requests
'django-phonenumber-field==3.0.*',
'phonenumberslite==8.10.*',

View File

@@ -38,7 +38,6 @@ def event(organizer, meta_prop):
is_public=True
)
e.meta_values.create(property=meta_prop, value="Conference")
e.settings.timezone = 'Europe/Berlin'
return e

View File

@@ -4,15 +4,12 @@ from decimal import Decimal
from unittest import mock
import pytest
from django.dispatch import receiver
from django.utils.timezone import now
from django_scopes import scopes_disabled
from pytz import UTC
from pretix.base.channels import SalesChannel
from pretix.base.models import Question, SeatingPlan
from pretix.base.models.orders import CartPosition
from pretix.base.signals import register_sales_channels
@pytest.fixture
@@ -52,21 +49,6 @@ def quota(event, item):
return q
class FoobarSalesChannel(SalesChannel):
identifier = "bar"
verbose_name = "Foobar"
icon = "home"
testmode_supported = False
unlimited_items_per_order = True
@receiver(register_sales_channels, dispatch_uid="test_cart_register_sales_channels")
def base_sales_channels(sender, **kwargs):
return (
FoobarSalesChannel(),
)
TEST_CARTPOSITION_RES = {
'id': 1,
'cart_id': 'aaa@api',
@@ -181,7 +163,6 @@ CARTPOS_CREATE_PAYLOAD = {
'subevent': None,
'expires': '2018-06-11T10:00:00Z',
'includes_tax': True,
'sales_channel': 'web',
'answers': []
}
@@ -686,23 +667,6 @@ def test_cartpos_create_with_blocked_seat(token_client, organizer, event, item,
assert resp.data == ['The selected seat "A1" is not available.']
@pytest.mark.django_db
def test_cartpos_create_with_blocked_seat_allowed(token_client, organizer, event, item, quota, seat, question):
seat.blocked = True
seat.save()
res = copy.deepcopy(CARTPOS_CREATE_PAYLOAD)
res['item'] = item.pk
res['seat'] = seat.seat_guid
res['sales_channel'] = 'bar'
event.settings.seating_allow_blocked_seats_for_channel = ['bar']
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/cartpositions/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 201
@pytest.mark.django_db
def test_cartpos_create_with_used_seat(token_client, organizer, event, item, quota, seat, question):
CartPosition.objects.create(

View File

@@ -76,7 +76,6 @@ TEST_EVENT_RES = {
"seating_plan": None,
"seat_category_mapping": {},
"meta_data": {"type": "Conference"},
'timezone': 'Europe/Berlin',
'plugins': [
'pretix.plugins.banktransfer',
'pretix.plugins.ticketoutputpdf'
@@ -175,8 +174,7 @@ def test_event_create(token_client, organizer, event, meta_prop):
"slug": "2030",
"meta_data": {
meta_prop.name: "Conference"
},
"timezone": "Europe/Amsterdam"
}
},
format='json'
)
@@ -187,7 +185,6 @@ def test_event_create(token_client, organizer, event, meta_prop):
property__name=meta_prop.name, value="Conference"
).exists()
assert organizer.events.get(slug="2030").plugins == settings.PRETIX_PLUGINS_DEFAULT
assert organizer.events.get(slug="2030").settings.timezone == "Europe/Amsterdam"
resp = token_client.post(
'/api/v1/organizers/{}/events/'.format(organizer.slug),
@@ -294,8 +291,7 @@ def test_event_create_with_clone(token_client, organizer, event, meta_prop):
},
"plugins": [
"pretix.plugins.ticketoutputpdf"
],
"timezone": "Europe/Vienna"
]
},
format='json'
)
@@ -309,7 +305,6 @@ def test_event_create_with_clone(token_client, organizer, event, meta_prop):
assert organizer.events.get(slug="2030").meta_values.filter(
property__name=meta_prop.name, value="Conference"
).exists()
assert cloned_event.settings.timezone == "Europe/Vienna"
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/clone/'.format(organizer.slug, event.slug),

View File

@@ -99,18 +99,6 @@ def test_giftcard_patch(token_client, organizer, event, giftcard):
assert not giftcard.testmode
@pytest.mark.django_db
def test_giftcard_patch_min_value(token_client, organizer, event, giftcard):
resp = token_client.patch(
'/api/v1/organizers/{}/giftcards/{}/'.format(organizer.slug, giftcard.pk),
{
'value': '-10.00',
},
format='json'
)
assert resp.status_code == 400
@pytest.mark.django_db
def test_giftcard_transact(token_client, organizer, event, giftcard):
resp = token_client.post(
@@ -125,21 +113,6 @@ def test_giftcard_transact(token_client, organizer, event, giftcard):
assert giftcard.value == Decimal('33.00')
@pytest.mark.django_db
def test_giftcard_transact_min_zero(token_client, organizer, event, giftcard):
resp = token_client.post(
'/api/v1/organizers/{}/giftcards/{}/transact/'.format(organizer.slug, giftcard.pk),
{
'value': '-100.00',
},
format='json'
)
assert resp.status_code == 409
assert resp.data == {'value': ['The gift card does not have sufficient credit for this operation.']}
giftcard.refresh_from_db()
assert giftcard.value == Decimal('23.00')
@pytest.mark.django_db
def test_giftcard_no_deletion(token_client, organizer, event, giftcard):
resp = token_client.delete(

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