forked from CGM_Public/pretix_original
Compare commits
185 Commits
release/3.
...
cart-vouch
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc298c4202 | ||
|
|
8822d572f5 | ||
|
|
1c99e01af9 | ||
|
|
66183e805e | ||
|
|
d33c9332c6 | ||
|
|
2284def607 | ||
|
|
15c25a5a0d | ||
|
|
cf5ac6af4b | ||
|
|
2a929200b5 | ||
|
|
3f77d34026 | ||
|
|
a395b24b80 | ||
|
|
984ef60099 | ||
|
|
5b6f0df963 | ||
|
|
509c7d98cc | ||
|
|
3bd4959efe | ||
|
|
4faaa8e521 | ||
|
|
0e8832fd54 | ||
|
|
4faa76d9c7 | ||
|
|
8d1f9bf0f3 | ||
|
|
4afef62cbd | ||
|
|
3d5cfdd9c7 | ||
|
|
b3b1d09690 | ||
|
|
381ecd6d75 | ||
|
|
a12fea71e5 | ||
|
|
a6dd6ac537 | ||
|
|
c3041aa8c4 | ||
|
|
e275677a0a | ||
|
|
fff14c31ba | ||
|
|
a74bde60eb | ||
|
|
12b9d23efb | ||
|
|
afec39ce57 | ||
|
|
4ae22c4a1e | ||
|
|
a928d8c781 | ||
|
|
27b7a756fb | ||
|
|
7252167cc7 | ||
|
|
d6ac337bde | ||
|
|
aa9d3ab614 | ||
|
|
186fb851e6 | ||
|
|
97f7507dad | ||
|
|
e4582dd9ed | ||
|
|
090d5bfe94 | ||
|
|
b4e98e21a1 | ||
|
|
4571abe496 | ||
|
|
f86520af37 | ||
|
|
1a5b8a2e07 | ||
|
|
dddf91d3bf | ||
|
|
f88a09a78c | ||
|
|
adebcc31d4 | ||
|
|
5645a07cdc | ||
|
|
1d732e9675 | ||
|
|
61de010bbb | ||
|
|
22eb9d3493 | ||
|
|
4e8bda0c96 | ||
|
|
0c83e5a50c | ||
|
|
745d7f74a1 | ||
|
|
8c86169d3f | ||
|
|
3c87272fdc | ||
|
|
f1142560f6 | ||
|
|
d46278f04f | ||
|
|
098b7363e6 | ||
|
|
11d0c37415 | ||
|
|
99607aa74a | ||
|
|
d44b75388e | ||
|
|
531c8aedc2 | ||
|
|
0474651070 | ||
|
|
fd7ad3cb16 | ||
|
|
d5b932e0d9 | ||
|
|
5462e256ac | ||
|
|
46d4d97c13 | ||
|
|
8b5241d520 | ||
|
|
ee06844b0d | ||
|
|
d4a491fc1b | ||
|
|
bb3348022f | ||
|
|
a10082a46f | ||
|
|
ef555eaff1 | ||
|
|
31adc37599 | ||
|
|
1d02764be2 | ||
|
|
f80f2e9bf9 | ||
|
|
f5e7e0e309 | ||
|
|
bbc70447a2 | ||
|
|
530632d624 | ||
|
|
370130f047 | ||
|
|
14575693b8 | ||
|
|
436dcc68f2 | ||
|
|
b49e02d988 | ||
|
|
037644421b | ||
|
|
654faa5a10 | ||
|
|
53a22e0e88 | ||
|
|
f8a080d180 | ||
|
|
b013737d70 | ||
|
|
24d6816dac | ||
|
|
2eb92bef9b | ||
|
|
8d5b4d190c | ||
|
|
50dabb5b26 | ||
|
|
84fb25e4d9 | ||
|
|
ee4f75c2fb | ||
|
|
1e63eb743c | ||
|
|
09477e1431 | ||
|
|
2be49d8cab | ||
|
|
808ccfee75 | ||
|
|
485766e247 | ||
|
|
bab27f5ab6 | ||
|
|
26141da1b3 | ||
|
|
4c6cf63d68 | ||
|
|
24fa251b05 | ||
|
|
d65eaf7b95 | ||
|
|
e24a7f3887 | ||
|
|
ec66b20798 | ||
|
|
2c3c9a4ba1 | ||
|
|
4c8e1cef7f | ||
|
|
180963c787 | ||
|
|
ee96fb2ca1 | ||
|
|
5563259b11 | ||
|
|
e7b86e0deb | ||
|
|
067c3b1abc | ||
|
|
3fff04bcda | ||
|
|
150ebc7ddb | ||
|
|
2f906bae5c | ||
|
|
82d1927fe7 | ||
|
|
cde87b437a | ||
|
|
9cbf3490cf | ||
|
|
56cfdfc5b3 | ||
|
|
8564d20183 | ||
|
|
c3155ad16d | ||
|
|
7fce062c2a | ||
|
|
05311fda54 | ||
|
|
0396c851ed | ||
|
|
1d2094f5ea | ||
|
|
44b8531614 | ||
|
|
5e9610eecf | ||
|
|
1c40351b27 | ||
|
|
3a4196c087 | ||
|
|
080b5f02cb | ||
|
|
21c052918c | ||
|
|
9515249098 | ||
|
|
a0dd495cc3 | ||
|
|
46ce9904cd | ||
|
|
600310da0f | ||
|
|
3b306de1bb | ||
|
|
a2c1c69d7e | ||
|
|
f79df47b78 | ||
|
|
1703f3d636 | ||
|
|
580ae7a2e9 | ||
|
|
34703cf6de | ||
|
|
1999495638 | ||
|
|
00798d9207 | ||
|
|
ac2ff6c2aa | ||
|
|
35ac2f0d2b | ||
|
|
3432bb089a | ||
|
|
69b03b24a1 | ||
|
|
520dd3e254 | ||
|
|
d98fce3594 | ||
|
|
aa5ae8b2bd | ||
|
|
b148182240 | ||
|
|
339d7f06ed | ||
|
|
b876293453 | ||
|
|
54091b9721 | ||
|
|
06a744ea2d | ||
|
|
c24ab642ce | ||
|
|
2ab3e1d420 | ||
|
|
0e9d2cfc10 | ||
|
|
63a46d0156 | ||
|
|
6896682dd1 | ||
|
|
384e7f8fc1 | ||
|
|
d403b3d433 | ||
|
|
552e523229 | ||
|
|
95ca6da74a | ||
|
|
bce8879804 | ||
|
|
a05cce8af7 | ||
|
|
dd6f1ee70f | ||
|
|
ad91ed4a30 | ||
|
|
e3c3154469 | ||
|
|
54cd38bbaa | ||
|
|
8dbba1cfd3 | ||
|
|
0bf3ee1a6f | ||
|
|
da7e1dee3e | ||
|
|
df3cc1499f | ||
|
|
2d066e3b5e | ||
|
|
25cfa8973c | ||
|
|
3f7f04ed21 | ||
|
|
88b2f4738a | ||
|
|
ea71b1f04e | ||
|
|
03a06b6997 | ||
|
|
1cd319357d | ||
|
|
4c653aa4f1 |
@@ -28,6 +28,7 @@ payment_provider_text string Text to be prin
|
||||
payment information
|
||||
footer_text string Text to be printed in the page footer area
|
||||
lines list of objects The actual invoice contents
|
||||
├ position integer Number of the line within an invoice.
|
||||
├ description string Text representing the invoice line (e.g. product name)
|
||||
├ gross_value money (string) Price including taxes
|
||||
├ tax_value money (string) Tax amount included
|
||||
@@ -63,6 +64,11 @@ internal_reference string Customer's refe
|
||||
The attribute ``internal_reference`` has been added.
|
||||
|
||||
|
||||
.. versionchanged:: 3.4
|
||||
|
||||
The attribute ``lines.number`` has been added.
|
||||
|
||||
|
||||
Endpoints
|
||||
---------
|
||||
|
||||
@@ -107,6 +113,7 @@ Endpoints
|
||||
"footer_text": "Big Events LLC - Registration No. 123456 - VAT ID: EU0987654321",
|
||||
"lines": [
|
||||
{
|
||||
"position": 1,
|
||||
"description": "Budget Ticket",
|
||||
"gross_value": "23.00",
|
||||
"tax_value": "0.00",
|
||||
@@ -171,6 +178,7 @@ Endpoints
|
||||
"footer_text": "Big Events LLC - Registration No. 123456 - VAT ID: EU0987654321",
|
||||
"lines": [
|
||||
{
|
||||
"position": 1,
|
||||
"description": "Budget Ticket",
|
||||
"gross_value": "23.00",
|
||||
"tax_value": "0.00",
|
||||
|
||||
@@ -845,6 +845,14 @@ Creating orders
|
||||
* ``description``
|
||||
* ``internal_type``
|
||||
* ``tax_rule``
|
||||
* ``_treat_value_as_percentage`` (Optional convenience flag. If set to ``true``, your ``value`` parameter will
|
||||
be treated as a percentage and the fee will be calculated using that percentage and the sum of all product
|
||||
prices. Note that this will not include other fees and is calculated once during order generation and will not
|
||||
be respected automatically when the order changes later.)
|
||||
* ``_split_taxes_like_products`` (Optional convenience flag. If set to ``true``, your ``tax_rule`` will be ignored
|
||||
and the fee will be taxed like the products in the order. If the products have multiple tax rates, multiple fees
|
||||
will be generated with weights adjusted to the net price of the products. Note that this will be calculated once
|
||||
during order generation and is not respected automatically when the order changes later.)
|
||||
|
||||
* ``force`` (optional). If set to ``true``, quotas will be ignored.
|
||||
* ``send_mail`` (optional). If set to ``true``, the same emails will be sent as for a regular order. Defaults to
|
||||
|
||||
@@ -31,6 +31,7 @@ type string The expected ty
|
||||
* ``H`` – time
|
||||
* ``W`` – date and time
|
||||
* ``CC`` – country code (ISO 3666-1 alpha-2)
|
||||
* ``TEL`` – telephone number
|
||||
required boolean If ``true``, the question needs to be filled out.
|
||||
position integer An integer, used for sorting
|
||||
items list of integers List of item IDs this question is assigned to.
|
||||
|
||||
@@ -38,6 +38,7 @@ quota integer An ID of a quot
|
||||
attached either to a specific product or to all
|
||||
products within one quota or it can be available
|
||||
for all items without restriction.
|
||||
seat string ``seat_guid`` attribute of a specific seat (or ``null``)
|
||||
tag string A string that is used for grouping vouchers
|
||||
comment string An internal comment on the voucher
|
||||
subevent integer ID of the date inside an event series this voucher belongs to (or ``null``).
|
||||
@@ -53,6 +54,10 @@ show_hidden_items boolean Only if set to
|
||||
|
||||
The attribute ``show_hidden_items`` has been added.
|
||||
|
||||
.. versionchanged:: 3.4
|
||||
|
||||
The attribute ``seat`` has been added.
|
||||
|
||||
Endpoints
|
||||
---------
|
||||
|
||||
@@ -96,7 +101,8 @@ Endpoints
|
||||
"quota": null,
|
||||
"tag": "testvoucher",
|
||||
"comment": "",
|
||||
"subevent": null
|
||||
"seat": null,
|
||||
"subevent": null,
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -162,6 +168,7 @@ Endpoints
|
||||
"quota": null,
|
||||
"tag": "testvoucher",
|
||||
"comment": "",
|
||||
"seat": null,
|
||||
"subevent": null
|
||||
}
|
||||
|
||||
@@ -225,6 +232,7 @@ Endpoints
|
||||
"quota": null,
|
||||
"tag": "testvoucher",
|
||||
"comment": "",
|
||||
"seat": null,
|
||||
"subevent": null
|
||||
}
|
||||
|
||||
@@ -352,6 +360,7 @@ Endpoints
|
||||
"quota": null,
|
||||
"tag": "testvoucher",
|
||||
"comment": "",
|
||||
"seat": null,
|
||||
"subevent": null
|
||||
}
|
||||
|
||||
|
||||
12
doc/contents.rst
Normal file
12
doc/contents.rst
Normal file
@@ -0,0 +1,12 @@
|
||||
Table of contents
|
||||
=================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 3
|
||||
|
||||
user/index
|
||||
admin/index
|
||||
api/index
|
||||
development/index
|
||||
plugins/index
|
||||
|
||||
@@ -20,13 +20,13 @@ Order events
|
||||
There are multiple signals that will be sent out in the ordering cycle:
|
||||
|
||||
.. automodule:: pretix.base.signals
|
||||
:members: validate_cart, validate_cart_addons, validate_order, order_fee_calculation, order_paid, order_placed, order_canceled, order_expired, order_modified, order_changed, order_approved, order_denied, order_fee_type_name, allow_ticket_download, order_split, order_gracefully_delete
|
||||
:members: validate_cart, validate_cart_addons, validate_order, order_fee_calculation, order_paid, order_placed, order_canceled, order_expired, order_modified, order_changed, order_approved, order_denied, order_fee_type_name, allow_ticket_download, order_split, order_gracefully_delete, invoice_line_text
|
||||
|
||||
Frontend
|
||||
--------
|
||||
|
||||
.. automodule:: pretix.presale.signals
|
||||
: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
|
||||
: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
|
||||
@@ -49,8 +49,8 @@ Backend
|
||||
|
||||
.. automodule:: pretix.control.signals
|
||||
:members: nav_event, html_head, html_page_start, quota_detail_html, nav_topbar, nav_global, nav_organizer, nav_event_settings,
|
||||
order_info, event_settings_widget, oauth_application_registered, order_position_buttons, subevent_forms, item_formsets
|
||||
|
||||
order_info, event_settings_widget, oauth_application_registered, order_position_buttons, subevent_forms,
|
||||
item_formsets, order_search_filter_q
|
||||
|
||||
.. automodule:: pretix.base.signals
|
||||
:members: logentry_display, logentry_object_link, requiredaction_display, timeline_events
|
||||
@@ -78,3 +78,6 @@ Ticket designs
|
||||
|
||||
.. automodule:: pretix.base.signals
|
||||
:members: layout_text_variables
|
||||
|
||||
.. automodule:: pretix.plugins.ticketoutputpdf.signals
|
||||
:members: override_layout
|
||||
|
||||
@@ -62,6 +62,8 @@ The provider class
|
||||
|
||||
.. autoattribute:: is_enabled
|
||||
|
||||
.. autoattribute:: priority
|
||||
|
||||
.. autoattribute:: settings_form_fields
|
||||
|
||||
.. automethod:: settings_form_clean
|
||||
@@ -108,10 +110,14 @@ The provider class
|
||||
|
||||
.. automethod:: execute_refund
|
||||
|
||||
.. automethod:: refund_control_render
|
||||
|
||||
.. automethod:: api_payment_details
|
||||
|
||||
.. automethod:: shred_payment_info
|
||||
|
||||
.. automethod:: cancel_payment
|
||||
|
||||
.. autoattribute:: is_implicit
|
||||
|
||||
.. autoattribute:: is_meta
|
||||
|
||||
@@ -1,12 +1 @@
|
||||
Table of contents
|
||||
=================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 3
|
||||
|
||||
user/index
|
||||
admin/index
|
||||
api/index
|
||||
development/index
|
||||
plugins/index
|
||||
|
||||
.. include:: contents.rst
|
||||
@@ -256,6 +256,11 @@ This works for the pretix Button as well. Currently, the following attributes ar
|
||||
naming scheme such as ``name-title`` or ``name-given-name`` (see above). ``country`` expects a two-character
|
||||
country code.
|
||||
|
||||
* If ``data-fix="true"`` is given, the user will not be able to change the other given values later. This currently
|
||||
only works for the order email address as well as the invoice address. Attendee-level fields and questions can
|
||||
always be modified. Note that this is not a security feature and can easily be overridden by users, so do not rely
|
||||
on this for authentication.
|
||||
|
||||
Any configured pretix plugins might understand more data fields. For example, if the appropriate plugins on pretix
|
||||
Hosted or pretix Enterprise are active, you can pass the following fields:
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
__version__ = "3.3.0"
|
||||
__version__ = "3.5.0.dev0"
|
||||
|
||||
@@ -122,9 +122,6 @@ class ItemSerializer(I18nAwareModelSerializer):
|
||||
'show_quota_left', 'hidden_if_available', 'allow_waitinglist', 'issue_giftcard')
|
||||
read_only_fields = ('has_variations', 'picture')
|
||||
|
||||
def get_serializer_context(self):
|
||||
return {"has_variations": self.kwargs['has_variations']}
|
||||
|
||||
def validate(self, data):
|
||||
data = super().validate(data)
|
||||
if self.instance and ('addons' in data or 'variations' in data or 'bundles' in data):
|
||||
@@ -270,6 +267,9 @@ class QuestionSerializer(I18nAwareModelSerializer):
|
||||
seen_ids.add(dep.pk)
|
||||
dep = dep.dependency_question
|
||||
|
||||
if full_data.get('ask_during_checkin') and full_data.get('type') in Question.ASK_DURING_CHECKIN_UNSUPPORTED:
|
||||
raise ValidationError(_('This type of question cannot be asked during check-in.'))
|
||||
|
||||
Question.clean_items(event, full_data.get('items'))
|
||||
return data
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import json
|
||||
from collections import Counter
|
||||
from collections import Counter, defaultdict
|
||||
from decimal import Decimal
|
||||
|
||||
import pycountry
|
||||
@@ -14,10 +14,11 @@ from rest_framework.reverse import reverse
|
||||
|
||||
from pretix.api.serializers.i18n import I18nAwareModelSerializer
|
||||
from pretix.base.channels import get_all_sales_channels
|
||||
from pretix.base.decimal import round_decimal
|
||||
from pretix.base.i18n import language
|
||||
from pretix.base.models import (
|
||||
Checkin, Invoice, InvoiceAddress, InvoiceLine, Item, ItemVariation, Order,
|
||||
OrderPosition, Question, QuestionAnswer, Seat, SubEvent, Voucher,
|
||||
OrderPosition, Question, QuestionAnswer, Seat, SubEvent, TaxRule, Voucher,
|
||||
)
|
||||
from pretix.base.models.orders import (
|
||||
CartPosition, OrderFee, OrderPayment, OrderRefund,
|
||||
@@ -477,9 +478,13 @@ class AnswerCreateSerializer(I18nAwareModelSerializer):
|
||||
|
||||
|
||||
class OrderFeeCreateSerializer(I18nAwareModelSerializer):
|
||||
_treat_value_as_percentage = serializers.BooleanField(default=False, required=False)
|
||||
_split_taxes_like_products = serializers.BooleanField(default=False, required=False)
|
||||
|
||||
class Meta:
|
||||
model = OrderFee
|
||||
fields = ('fee_type', 'value', 'description', 'internal_type', 'tax_rule')
|
||||
fields = ('fee_type', 'value', 'description', 'internal_type', 'tax_rule',
|
||||
'_treat_value_as_percentage', '_split_taxes_like_products')
|
||||
|
||||
def validate_tax_rule(self, tr):
|
||||
if tr and tr.event != self.context['event']:
|
||||
@@ -863,13 +868,48 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
|
||||
for cp in delete_cps:
|
||||
cp.delete()
|
||||
|
||||
order.total = sum([p.price for p in order.positions.all()])
|
||||
for fee_data in fees_data:
|
||||
f = OrderFee(**fee_data)
|
||||
f.order = order
|
||||
f._calculate_tax()
|
||||
f.save()
|
||||
is_percentage = fee_data.pop('_treat_value_as_percentage', False)
|
||||
if is_percentage:
|
||||
fee_data['value'] = round_decimal(order.total * (fee_data['value'] / Decimal('100.00')),
|
||||
self.context['event'].currency)
|
||||
is_split_taxes = fee_data.pop('_split_taxes_like_products', False)
|
||||
|
||||
order.total = sum([p.price for p in order.positions.all()]) + sum([f.value for f in order.fees.all()])
|
||||
if is_split_taxes:
|
||||
d = defaultdict(lambda: Decimal('0.00'))
|
||||
trz = TaxRule.zero()
|
||||
for p in pos_map.values():
|
||||
tr = p.tax_rule
|
||||
d[tr] += p.price - p.tax_value
|
||||
|
||||
base_values = sorted([tuple(t) for t in d.items()], key=lambda t: (t[0] or trz).rate)
|
||||
sum_base = sum(t[1] for t in base_values)
|
||||
fee_values = [(t[0], round_decimal(fee_data['value'] * t[1] / sum_base, self.context['event'].currency))
|
||||
for t in base_values]
|
||||
sum_fee = sum(t[1] for t in fee_values)
|
||||
|
||||
# If there are rounding differences, we fix them up, but always leaning to the benefit of the tax
|
||||
# authorities
|
||||
if sum_fee > fee_data['value']:
|
||||
fee_values[0] = (fee_values[0][0], fee_values[0][1] + (fee_data['value'] - sum_fee))
|
||||
elif sum_fee < fee_data['value']:
|
||||
fee_values[-1] = (fee_values[-1][0], fee_values[-1][1] + (fee_data['value'] - sum_fee))
|
||||
|
||||
for tr, val in fee_values:
|
||||
fee_data['tax_rule'] = tr
|
||||
fee_data['value'] = val
|
||||
f = OrderFee(**fee_data)
|
||||
f.order = order
|
||||
f._calculate_tax()
|
||||
f.save()
|
||||
else:
|
||||
f = OrderFee(**fee_data)
|
||||
f.order = order
|
||||
f._calculate_tax()
|
||||
f.save()
|
||||
|
||||
order.total += sum([f.value for f in order.fees.all()])
|
||||
order.save(update_fields=['total'])
|
||||
|
||||
if order.total == Decimal('0.00') and validated_data.get('status') == Order.STATUS_PAID and not payment_provider:
|
||||
@@ -905,10 +945,25 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
|
||||
return order
|
||||
|
||||
|
||||
class LinePositionField(serializers.IntegerField):
|
||||
"""
|
||||
Internally, the position field is stored starting at 0, but for the API, starting at 1 makes it
|
||||
more consistent with other models
|
||||
"""
|
||||
|
||||
def to_representation(self, value):
|
||||
return super().to_representation(value) + 1
|
||||
|
||||
def to_internal_value(self, data):
|
||||
return super().to_internal_value(data) - 1
|
||||
|
||||
|
||||
class InlineInvoiceLineSerializer(I18nAwareModelSerializer):
|
||||
position = LinePositionField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = InvoiceLine
|
||||
fields = ('description', 'gross_value', 'tax_value', 'tax_rate', 'tax_name')
|
||||
fields = ('position', 'description', 'gross_value', 'tax_value', 'tax_rate', 'tax_name')
|
||||
|
||||
|
||||
class InvoiceSerializer(I18nAwareModelSerializer):
|
||||
|
||||
@@ -2,15 +2,23 @@ from rest_framework import serializers
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
from pretix.api.serializers.i18n import I18nAwareModelSerializer
|
||||
from pretix.base.models import Voucher
|
||||
from pretix.base.models import Seat, Voucher
|
||||
|
||||
|
||||
class VoucherListSerializer(serializers.ListSerializer):
|
||||
def create(self, validated_data):
|
||||
codes = set()
|
||||
seats = set()
|
||||
errs = []
|
||||
err = False
|
||||
for voucher_data in validated_data:
|
||||
if voucher_data.get('seat') and (voucher_data.get('seat'), voucher_data.get('subevent')) in seats:
|
||||
err = True
|
||||
errs.append({'code': ['Duplicate seat ID in request.']})
|
||||
continue
|
||||
else:
|
||||
seats.add((voucher_data.get('seat'), voucher_data.get('subevent')))
|
||||
|
||||
if voucher_data['code'] in codes:
|
||||
err = True
|
||||
errs.append({'code': ['Duplicate voucher code in request.']})
|
||||
@@ -22,12 +30,19 @@ class VoucherListSerializer(serializers.ListSerializer):
|
||||
return super().create(validated_data)
|
||||
|
||||
|
||||
class SeatGuidField(serializers.CharField):
|
||||
def to_representation(self, val: Seat):
|
||||
return val.seat_guid
|
||||
|
||||
|
||||
class VoucherSerializer(I18nAwareModelSerializer):
|
||||
seat = SeatGuidField(allow_null=True, required=False)
|
||||
|
||||
class Meta:
|
||||
model = Voucher
|
||||
fields = ('id', 'code', 'max_usages', 'redeemed', 'valid_until', 'block_quota',
|
||||
'allow_ignore_quota', 'price_mode', 'value', 'item', 'variation', 'quota',
|
||||
'tag', 'comment', 'subevent', 'show_hidden_items')
|
||||
'tag', 'comment', 'subevent', 'show_hidden_items', 'seat')
|
||||
read_only_fields = ('id', 'redeemed')
|
||||
list_serializer_class = VoucherListSerializer
|
||||
|
||||
@@ -39,7 +54,8 @@ class VoucherSerializer(I18nAwareModelSerializer):
|
||||
|
||||
Voucher.clean_item_properties(
|
||||
full_data, self.context.get('event'),
|
||||
full_data.get('quota'), full_data.get('item'), full_data.get('variation')
|
||||
full_data.get('quota'), full_data.get('item'), full_data.get('variation'),
|
||||
block_quota=full_data.get('block_quota')
|
||||
)
|
||||
Voucher.clean_subevent(
|
||||
full_data, self.context.get('event')
|
||||
@@ -61,4 +77,10 @@ class VoucherSerializer(I18nAwareModelSerializer):
|
||||
)
|
||||
Voucher.clean_voucher_code(full_data, self.context.get('event'), self.instance.pk if self.instance else None)
|
||||
|
||||
if full_data.get('seat'):
|
||||
data['seat'] = Voucher.clean_seat_id(
|
||||
full_data, full_data.get('item'), full_data.get('quota'), self.context.get('event'),
|
||||
self.instance.pk if self.instance else None
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
@@ -133,6 +133,7 @@ class EventViewSet(viewsets.ModelViewSet):
|
||||
|
||||
def perform_create(self, serializer):
|
||||
serializer.save(organizer=self.request.organizer)
|
||||
serializer.instance.set_defaults()
|
||||
serializer.instance.log_action(
|
||||
'pretix.event.added',
|
||||
user=self.request.user,
|
||||
|
||||
@@ -62,7 +62,6 @@ class ItemViewSet(ConditionalListView, viewsets.ModelViewSet):
|
||||
def get_serializer_context(self):
|
||||
ctx = super().get_serializer_context()
|
||||
ctx['event'] = self.request.event
|
||||
ctx['has_variations'] = self.request.data.get('has_variations')
|
||||
return ctx
|
||||
|
||||
def perform_update(self, serializer):
|
||||
|
||||
@@ -48,7 +48,7 @@ from pretix.base.services.orders import (
|
||||
from pretix.base.services.pricing import get_price
|
||||
from pretix.base.services.tickets import generate
|
||||
from pretix.base.signals import (
|
||||
order_modified, order_placed, register_ticket_outputs,
|
||||
order_modified, order_paid, order_placed, register_ticket_outputs,
|
||||
)
|
||||
from pretix.base.templatetags.money import money_filter
|
||||
|
||||
@@ -173,9 +173,26 @@ class OrderViewSet(viewsets.ModelViewSet):
|
||||
amount=ps
|
||||
)
|
||||
except OrderPayment.DoesNotExist:
|
||||
order.payments.filter(state__in=(OrderPayment.PAYMENT_STATE_PENDING,
|
||||
OrderPayment.PAYMENT_STATE_CREATED)) \
|
||||
.update(state=OrderPayment.PAYMENT_STATE_CANCELED)
|
||||
for p in order.payments.filter(state__in=(OrderPayment.PAYMENT_STATE_PENDING,
|
||||
OrderPayment.PAYMENT_STATE_CREATED)):
|
||||
try:
|
||||
with transaction.atomic():
|
||||
p.payment_provider.cancel_payment(p)
|
||||
order.log_action('pretix.event.order.payment.canceled', {
|
||||
'local_id': p.local_id,
|
||||
'provider': p.provider,
|
||||
}, user=self.request.user if self.request.user.is_authenticated else None, auth=self.request.auth)
|
||||
except PaymentException as e:
|
||||
order.log_action(
|
||||
'pretix.event.order.payment.canceled.failed',
|
||||
{
|
||||
'local_id': p.local_id,
|
||||
'provider': p.provider,
|
||||
'error': str(e)
|
||||
},
|
||||
user=self.request.user if self.request.user.is_authenticated else None,
|
||||
auth=self.request.auth
|
||||
)
|
||||
p = order.payments.create(
|
||||
state=OrderPayment.PAYMENT_STATE_CREATED,
|
||||
provider='manual',
|
||||
@@ -448,6 +465,8 @@ class OrderViewSet(viewsets.ModelViewSet):
|
||||
|
||||
with language(order.locale):
|
||||
order_placed.send(self.request.event, order=order)
|
||||
if order.status == Order.STATUS_PAID:
|
||||
order_paid.send(self.request.event, order=order)
|
||||
|
||||
gen_invoice = invoice_qualified(order) and (
|
||||
(order.event.settings.get('invoice_generate') == 'True') or
|
||||
@@ -896,13 +915,16 @@ class PaymentViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
if payment.state not in (OrderPayment.PAYMENT_STATE_PENDING, OrderPayment.PAYMENT_STATE_CREATED):
|
||||
return Response({'detail': 'Invalid state of payment'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
with transaction.atomic():
|
||||
payment.state = OrderPayment.PAYMENT_STATE_CANCELED
|
||||
payment.save()
|
||||
payment.order.log_action('pretix.event.order.payment.canceled', {
|
||||
'local_id': payment.local_id,
|
||||
'provider': payment.provider,
|
||||
}, user=self.request.user if self.request.user.is_authenticated else None, auth=self.request.auth)
|
||||
try:
|
||||
with transaction.atomic():
|
||||
payment.payment_provider.cancel_payment(payment)
|
||||
payment.order.log_action('pretix.event.order.payment.canceled', {
|
||||
'local_id': payment.local_id,
|
||||
'provider': payment.provider,
|
||||
}, user=self.request.user if self.request.user.is_authenticated else None, auth=self.request.auth)
|
||||
except PaymentException as e:
|
||||
return Response({'detail': 'External error: {}'.format(str(e))},
|
||||
status=status.HTTP_400_BAD_REQUEST)
|
||||
return self.retrieve(request, [], **kwargs)
|
||||
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ class VoucherViewSet(viewsets.ModelViewSet):
|
||||
write_permission = 'can_change_vouchers'
|
||||
|
||||
def get_queryset(self):
|
||||
return self.request.event.vouchers.all()
|
||||
return self.request.event.vouchers.select_related('seat').all()
|
||||
|
||||
def _predict_quota_check(self, data, instance):
|
||||
# This method predicts if Voucher.clean_quota_needs_checking
|
||||
|
||||
@@ -42,6 +42,25 @@ class SalesChannel:
|
||||
"""
|
||||
return True
|
||||
|
||||
@property
|
||||
def payment_restrictions_supported(self) -> bool:
|
||||
"""
|
||||
If this property is ``True``, organizers can restrict the usage of payment providers to this sales channel.
|
||||
|
||||
Example: pretixPOS provides its own sales channel, ignores the configured payment providers completely and
|
||||
handles payments locally. Therefor, this property should be set to ``False`` for the pretixPOS sales channel as
|
||||
the event organizer cannot restrict the usage of any payment provider through the backend.
|
||||
"""
|
||||
return True
|
||||
|
||||
@property
|
||||
def unlimited_items_per_order(self) -> bool:
|
||||
"""
|
||||
If this property is ``True``, purchases made using this sales channel are not limited to the maximum amount of
|
||||
items defined in the event settings.
|
||||
"""
|
||||
return False
|
||||
|
||||
|
||||
def get_all_sales_channels():
|
||||
global _ALL_CHANNELS
|
||||
|
||||
@@ -278,6 +278,8 @@ class OrderListExporter(MultiSheetListExporter):
|
||||
]
|
||||
if self.event.has_subevents:
|
||||
headers.append(pgettext('subevent', 'Date'))
|
||||
headers.append(_('Start date'))
|
||||
headers.append(_('End date'))
|
||||
headers += [
|
||||
_('Product'),
|
||||
_('Variation'),
|
||||
@@ -323,7 +325,12 @@ class OrderListExporter(MultiSheetListExporter):
|
||||
order.datetime.astimezone(tz).strftime('%Y-%m-%d'),
|
||||
]
|
||||
if self.event.has_subevents:
|
||||
row.append(op.subevent)
|
||||
row.append(op.subevent.name)
|
||||
row.append(op.subevent.date_from.astimezone(self.event.timezone).strftime('%Y-%m-%d %H:%M:%S'))
|
||||
if op.subevent.date_to:
|
||||
row.append(op.subevent.date_to.astimezone(self.event.timezone).strftime('%Y-%m-%d %H:%M:%S'))
|
||||
else:
|
||||
row.append('')
|
||||
row += [
|
||||
str(op.item),
|
||||
str(op.variation) if op.variation else '',
|
||||
|
||||
@@ -9,6 +9,7 @@ import pycountry
|
||||
import pytz
|
||||
import vat_moss.errors
|
||||
import vat_moss.id
|
||||
from babel import localedata
|
||||
from django import forms
|
||||
from django.contrib import messages
|
||||
from django.core.exceptions import ValidationError
|
||||
@@ -21,11 +22,17 @@ from django.utils.translation import (
|
||||
)
|
||||
from django_countries import countries
|
||||
from django_countries.fields import Country, CountryField
|
||||
from phonenumber_field.formfields import PhoneNumberField
|
||||
from phonenumber_field.phonenumber import PhoneNumber
|
||||
from phonenumber_field.widgets import PhoneNumberPrefixWidget
|
||||
from phonenumbers import NumberParseException
|
||||
from phonenumbers.data import _COUNTRY_CODE_TO_REGION_CODE
|
||||
|
||||
from pretix.base.forms.widgets import (
|
||||
BusinessBooleanRadio, DatePickerWidget, SplitDateTimePickerWidget,
|
||||
TimePickerWidget, UploadedFileWidget,
|
||||
)
|
||||
from pretix.base.i18n import language
|
||||
from pretix.base.models import InvoiceAddress, Question, QuestionOption
|
||||
from pretix.base.models.tax import EU_COUNTRIES, cc_to_vat_prefix
|
||||
from pretix.base.settings import (
|
||||
@@ -41,6 +48,9 @@ from pretix.presale.signals import question_form_fields
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
REQUIRED_NAME_PARTS = ['given_name', 'family_name', 'full_name']
|
||||
|
||||
|
||||
class NamePartsWidget(forms.MultiWidget):
|
||||
widget = forms.TextInput
|
||||
autofill_map = {
|
||||
@@ -91,15 +101,21 @@ class NamePartsWidget(forms.MultiWidget):
|
||||
except (IndexError, TypeError):
|
||||
widget_value = None
|
||||
if id_:
|
||||
final_attrs = dict(
|
||||
these_attrs = dict(
|
||||
final_attrs,
|
||||
id='%s_%s' % (id_, i),
|
||||
title=self.scheme['fields'][i][1],
|
||||
placeholder=self.scheme['fields'][i][1],
|
||||
)
|
||||
final_attrs['autocomplete'] = (self.attrs.get('autocomplete', '') + ' ' + self.autofill_map.get(self.scheme['fields'][i][0], 'off')).strip()
|
||||
final_attrs['data-size'] = self.scheme['fields'][i][2]
|
||||
output.append(widget.render(name + '_%s' % i, widget_value, final_attrs, renderer=renderer))
|
||||
if self.scheme['fields'][i][0] in REQUIRED_NAME_PARTS:
|
||||
if self.field.required:
|
||||
these_attrs['required'] = 'required'
|
||||
these_attrs.pop('data-no-required-attr', None)
|
||||
these_attrs['autocomplete'] = (self.attrs.get('autocomplete', '') + ' ' + self.autofill_map.get(self.scheme['fields'][i][0], 'off')).strip()
|
||||
these_attrs['data-size'] = self.scheme['fields'][i][2]
|
||||
else:
|
||||
these_attrs = final_attrs
|
||||
output.append(widget.render(name + '_%s' % i, widget_value, these_attrs, renderer=renderer))
|
||||
return mark_safe(self.format_output(output))
|
||||
|
||||
def format_output(self, rendered_widgets) -> str:
|
||||
@@ -159,13 +175,45 @@ class NamePartsFormField(forms.MultiValueField):
|
||||
|
||||
def clean(self, value) -> dict:
|
||||
value = super().clean(value)
|
||||
if self.one_required and (not value or not any(v for v in value)):
|
||||
if self.one_required and (not value or not any(v for v in value.values())):
|
||||
raise forms.ValidationError(self.error_messages['required'], code='required')
|
||||
if self.one_required:
|
||||
for k, v in value.items():
|
||||
if k in REQUIRED_NAME_PARTS and not v:
|
||||
raise forms.ValidationError(self.error_messages['required'], code='required')
|
||||
if self.require_all_fields and not all(v for v in value):
|
||||
raise forms.ValidationError(self.error_messages['incomplete'], code='required')
|
||||
return value
|
||||
|
||||
|
||||
class WrappedPhoneNumberPrefixWidget(PhoneNumberPrefixWidget):
|
||||
def render(self, name, value, attrs=None, renderer=None):
|
||||
output = super().render(name, value, attrs, renderer)
|
||||
return mark_safe(self.format_output(output))
|
||||
|
||||
def format_output(self, rendered_widgets) -> str:
|
||||
return '<div class="nameparts-form-group">%s</div>' % ''.join(rendered_widgets)
|
||||
|
||||
|
||||
def guess_country(event):
|
||||
# Try to guess the initial country from either the country of the merchant
|
||||
# or the locale. This will hopefully save at least some users some scrolling :)
|
||||
locale = get_language()
|
||||
country = event.settings.invoice_address_from_country
|
||||
if not country:
|
||||
valid_countries = countries.countries
|
||||
if '-' in locale:
|
||||
parts = locale.split('-')
|
||||
if parts[1].upper() in valid_countries:
|
||||
country = Country(parts[1].upper())
|
||||
elif parts[0].upper() in valid_countries:
|
||||
country = Country(parts[0].upper())
|
||||
else:
|
||||
if locale in valid_countries:
|
||||
country = Country(locale.upper())
|
||||
return country
|
||||
|
||||
|
||||
class BaseQuestionsForm(forms.Form):
|
||||
"""
|
||||
This form class is responsible for asking order-related questions. This includes
|
||||
@@ -315,6 +363,32 @@ class BaseQuestionsForm(forms.Form):
|
||||
initial=dateutil.parser.parse(initial.answer).astimezone(tz) if initial and initial.answer else None,
|
||||
widget=SplitDateTimePickerWidget(time_format=get_format_without_seconds('TIME_INPUT_FORMATS')),
|
||||
)
|
||||
elif q.type == Question.TYPE_PHONENUMBER:
|
||||
babel_locale = 'en'
|
||||
# Babel, and therefore django-phonenumberfield, do not support our custom locales such das de_Informal
|
||||
if localedata.exists(get_language()):
|
||||
babel_locale = get_language()
|
||||
elif localedata.exists(get_language()[:2]):
|
||||
babel_locale = get_language()[:2]
|
||||
with language(babel_locale):
|
||||
default_country = guess_country(event)
|
||||
default_prefix = None
|
||||
for prefix, values in _COUNTRY_CODE_TO_REGION_CODE.items():
|
||||
if str(default_country) in values:
|
||||
default_prefix = prefix
|
||||
try:
|
||||
initial = PhoneNumber().from_string(initial.answer) if initial else "+{}.".format(default_prefix)
|
||||
except NumberParseException:
|
||||
initial = None
|
||||
field = PhoneNumberField(
|
||||
label=label, required=required,
|
||||
help_text=help_text,
|
||||
# We now exploit an implementation detail in PhoneNumberPrefixWidget to allow us to pass just
|
||||
# a country code but no number as an initial value. It's a bit hacky, but should be stable for
|
||||
# the future.
|
||||
initial=initial,
|
||||
widget=WrappedPhoneNumberPrefixWidget()
|
||||
)
|
||||
field.question = q
|
||||
if answers:
|
||||
# Cache the answer object for later use
|
||||
@@ -420,23 +494,7 @@ class BaseInvoiceAddressForm(forms.ModelForm):
|
||||
|
||||
kwargs.setdefault('initial', {})
|
||||
if not kwargs.get('instance') or not kwargs['instance'].country:
|
||||
# Try to guess the initial country from either the country of the merchant
|
||||
# or the locale. This will hopefully save at least some users some scrolling :)
|
||||
locale = get_language()
|
||||
country = event.settings.invoice_address_from_country
|
||||
if not country:
|
||||
valid_countries = countries.countries
|
||||
if '-' in locale:
|
||||
parts = locale.split('-')
|
||||
if parts[1].upper() in valid_countries:
|
||||
country = Country(parts[1].upper())
|
||||
elif parts[0].upper() in valid_countries:
|
||||
country = Country(parts[0].upper())
|
||||
else:
|
||||
if locale in valid_countries:
|
||||
country = Country(locale.upper())
|
||||
|
||||
kwargs['initial']['country'] = country
|
||||
kwargs['initial']['country'] = guess_country(self.event)
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
if not event.settings.invoice_address_vatid:
|
||||
|
||||
@@ -457,19 +457,8 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
id='normal')
|
||||
]
|
||||
|
||||
def _get_story(self, doc):
|
||||
has_taxes = any(il.tax_value for il in self.invoice.lines.all())
|
||||
|
||||
story = [
|
||||
NextPageTemplate('FirstPage'),
|
||||
Paragraph(pgettext('invoice', 'Invoice')
|
||||
if not self.invoice.is_cancellation
|
||||
else pgettext('invoice', 'Cancellation'),
|
||||
self.stylesheet['Heading1']),
|
||||
Spacer(1, 5 * mm),
|
||||
NextPageTemplate('OtherPages'),
|
||||
]
|
||||
|
||||
def _get_intro(self):
|
||||
story = []
|
||||
if self.invoice.internal_reference:
|
||||
story.append(Paragraph(
|
||||
pgettext('invoice', 'Customer reference: {reference}').format(reference=self.invoice.internal_reference),
|
||||
@@ -478,7 +467,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
|
||||
if self.invoice.invoice_to_vat_id:
|
||||
story.append(Paragraph(
|
||||
pgettext('invoice', 'Customer VAT ID') + ':<br />' +
|
||||
pgettext('invoice', 'Customer VAT ID') + ': ' +
|
||||
bleach.clean(self.invoice.invoice_to_vat_id, tags=[]).replace("\n", "<br />\n"),
|
||||
self.stylesheet['Normal']
|
||||
))
|
||||
@@ -494,6 +483,25 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
story.append(Paragraph(self.invoice.introductory_text, self.stylesheet['Normal']))
|
||||
story.append(Spacer(1, 10 * mm))
|
||||
|
||||
return story
|
||||
|
||||
def _get_story(self, doc):
|
||||
has_taxes = any(il.tax_value for il in self.invoice.lines.all())
|
||||
|
||||
story = [
|
||||
NextPageTemplate('FirstPage'),
|
||||
Paragraph(
|
||||
(
|
||||
pgettext('invoice', 'Tax Invoice') if str(self.invoice.invoice_from_country) == 'AU'
|
||||
else pgettext('invoice', 'Invoice')
|
||||
) if not self.invoice.is_cancellation else pgettext('invoice', 'Cancellation'),
|
||||
self.stylesheet['Heading1']
|
||||
),
|
||||
Spacer(1, 5 * mm),
|
||||
NextPageTemplate('OtherPages'),
|
||||
]
|
||||
story += self._get_intro()
|
||||
|
||||
taxvalue_map = defaultdict(Decimal)
|
||||
grossvalue_map = defaultdict(Decimal)
|
||||
|
||||
|
||||
19
src/pretix/base/migrations/0140_voucher_seat.py
Normal file
19
src/pretix/base/migrations/0140_voucher_seat.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 2.2.4 on 2019-11-14 11:49
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('pretixbase', '0139_auto_20191019_1317'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='voucher',
|
||||
name='seat',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='vouchers', to='pretixbase.Seat'),
|
||||
),
|
||||
]
|
||||
18
src/pretix/base/migrations/0141_seat_sorting_rank.py
Normal file
18
src/pretix/base/migrations/0141_seat_sorting_rank.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 2.2.4 on 2019-11-22 11:42
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('pretixbase', '0140_voucher_seat'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='seat',
|
||||
name='sorting_rank',
|
||||
field=models.BigIntegerField(default=0),
|
||||
),
|
||||
]
|
||||
@@ -3,6 +3,7 @@ import uuid
|
||||
from collections import OrderedDict
|
||||
from datetime import datetime, time, timedelta
|
||||
from operator import attrgetter
|
||||
from urllib.parse import urljoin
|
||||
|
||||
import pytz
|
||||
from django.conf import settings
|
||||
@@ -11,7 +12,7 @@ from django.core.files.storage import default_storage
|
||||
from django.core.mail import get_connection
|
||||
from django.core.validators import RegexValidator
|
||||
from django.db import models
|
||||
from django.db.models import Exists, OuterRef, Prefetch, Q, Subquery
|
||||
from django.db.models import Exists, F, OuterRef, Prefetch, Q, Subquery
|
||||
from django.template.defaultfilters import date as _date
|
||||
from django.utils.crypto import get_random_string
|
||||
from django.utils.functional import cached_property
|
||||
@@ -26,6 +27,7 @@ from pretix.base.validators import EventSlugBanlistValidator
|
||||
from pretix.helpers.database import GroupConcat
|
||||
from pretix.helpers.daterange import daterange
|
||||
from pretix.helpers.json import safe_string
|
||||
from pretix.helpers.thumb import get_thumbnail
|
||||
|
||||
from ..settings import settings_hierarkey
|
||||
from .organizer import Organizer, Team
|
||||
@@ -145,10 +147,13 @@ class EventMixin:
|
||||
"@context": "http://schema.org",
|
||||
"@type": "Event", "location": {
|
||||
"@type": "Place",
|
||||
"address": str(self.location)
|
||||
"address": str(self.location),
|
||||
},
|
||||
"name": str(self.name)
|
||||
"name": str(self.name),
|
||||
}
|
||||
img = getattr(self, 'event', self).social_image
|
||||
if img:
|
||||
eventdict['image'] = img
|
||||
|
||||
if self.settings.show_times:
|
||||
eventdict["startDate"] = self.date_from.isoformat()
|
||||
@@ -358,9 +363,40 @@ class Event(EventMixin, LoggedModel):
|
||||
def __str__(self):
|
||||
return str(self.name)
|
||||
|
||||
def set_defaults(self):
|
||||
"""
|
||||
This will be called after event creation, but only if the event was not created by copying an existing one.
|
||||
This way, we can use this to introduce new default settings to pretix that do not affect existing events.
|
||||
"""
|
||||
self.settings.invoice_renderer = 'modern1'
|
||||
self.settings.invoice_include_expire_date = True
|
||||
|
||||
@property
|
||||
def free_seats(self):
|
||||
def social_image(self):
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
|
||||
img = None
|
||||
logo_file = self.settings.get('logo_image', as_type=str, default='')[7:]
|
||||
og_file = self.settings.get('og_image', as_type=str, default='')[7:]
|
||||
if og_file:
|
||||
img = get_thumbnail(og_file, '1200').thumb.url
|
||||
elif logo_file:
|
||||
img = get_thumbnail(logo_file, '5000x120').thumb.url
|
||||
if img:
|
||||
return urljoin(build_absolute_uri(self, 'presale:event.index'), img)
|
||||
|
||||
def free_seats(self, ignore_voucher=None):
|
||||
from .orders import CartPosition, Order, OrderPosition
|
||||
from .vouchers import Voucher
|
||||
vqs = Voucher.objects.filter(
|
||||
event=self,
|
||||
seat_id=OuterRef('pk'),
|
||||
redeemed__lt=F('max_usages'),
|
||||
).filter(
|
||||
Q(valid_until__isnull=True) | Q(valid_until__gte=now())
|
||||
)
|
||||
if ignore_voucher:
|
||||
vqs = vqs.exclude(pk=ignore_voucher.pk)
|
||||
return self.seats.annotate(
|
||||
has_order=Exists(
|
||||
OrderPosition.objects.filter(
|
||||
@@ -375,8 +411,11 @@ class Event(EventMixin, LoggedModel):
|
||||
seat_id=OuterRef('pk'),
|
||||
expires__gte=now()
|
||||
)
|
||||
),
|
||||
has_voucher=Exists(
|
||||
vqs
|
||||
)
|
||||
).filter(has_order=False, has_cart=False, blocked=False)
|
||||
).filter(has_order=False, has_cart=False, has_voucher=False, blocked=False)
|
||||
|
||||
@property
|
||||
def presale_has_ended(self):
|
||||
@@ -632,7 +671,9 @@ class Event(EventMixin, LoggedModel):
|
||||
pp = p(self)
|
||||
providers[pp.identifier] = pp
|
||||
|
||||
self._cached_payment_providers = OrderedDict(sorted(providers.items(), key=lambda v: str(v[1].verbose_name)))
|
||||
self._cached_payment_providers = OrderedDict(sorted(
|
||||
providers.items(), key=lambda v: (-v[1].priority, str(v[1].verbose_name))
|
||||
))
|
||||
return self._cached_payment_providers
|
||||
|
||||
def get_html_mail_renderer(self):
|
||||
@@ -965,9 +1006,19 @@ class SubEvent(EventMixin, LoggedModel):
|
||||
def __str__(self):
|
||||
return '{} - {}'.format(self.name, self.get_date_range_display())
|
||||
|
||||
@property
|
||||
def free_seats(self):
|
||||
def free_seats(self, ignore_voucher=None):
|
||||
from .orders import CartPosition, Order, OrderPosition
|
||||
from .vouchers import Voucher
|
||||
vqs = Voucher.objects.filter(
|
||||
event_id=self.event_id,
|
||||
subevent=self,
|
||||
seat_id=OuterRef('pk'),
|
||||
redeemed__lt=F('max_usages'),
|
||||
).filter(
|
||||
Q(valid_until__isnull=True) | Q(valid_until__gte=now())
|
||||
)
|
||||
if ignore_voucher:
|
||||
vqs = vqs.exclude(pk=ignore_voucher.pk)
|
||||
return self.seats.annotate(
|
||||
has_order=Exists(
|
||||
OrderPosition.objects.filter(
|
||||
@@ -984,8 +1035,11 @@ class SubEvent(EventMixin, LoggedModel):
|
||||
seat_id=OuterRef('pk'),
|
||||
expires__gte=now()
|
||||
)
|
||||
),
|
||||
has_voucher=Exists(
|
||||
vqs
|
||||
)
|
||||
).filter(has_order=False, has_cart=False, blocked=False)
|
||||
).filter(has_order=False, has_cart=False, blocked=False, has_voucher=False)
|
||||
|
||||
@cached_property
|
||||
def settings(self):
|
||||
|
||||
@@ -10,10 +10,10 @@ from pretix.base.banlist import banned
|
||||
from pretix.base.models import LoggedModel
|
||||
|
||||
|
||||
def gen_giftcard_secret():
|
||||
def gen_giftcard_secret(length):
|
||||
charset = list('ABCDEFGHJKLMNPQRSTUVWXYZ3789')
|
||||
while True:
|
||||
code = get_random_string(length=settings.ENTROPY['giftcard_secret'], allowed_chars=charset)
|
||||
code = get_random_string(length=length, allowed_chars=charset)
|
||||
if not banned(code) and not GiftCard.objects.filter(secret=code).exists():
|
||||
return code
|
||||
|
||||
@@ -48,7 +48,6 @@ class GiftCard(LoggedModel):
|
||||
)
|
||||
secret = models.CharField(
|
||||
max_length=190,
|
||||
default=gen_giftcard_secret,
|
||||
db_index=True,
|
||||
verbose_name=_('Gift card code'),
|
||||
)
|
||||
@@ -69,6 +68,12 @@ class GiftCard(LoggedModel):
|
||||
def accepted_by(self, organizer):
|
||||
return self.issuer == organizer or GiftCardAcceptance.objects.filter(issuer=self.issuer, collector=organizer).exists()
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.secret:
|
||||
self.secret = gen_giftcard_secret(self.issuer.settings.giftcard_length)
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
class Meta:
|
||||
unique_together = (('secret', 'issuer'),)
|
||||
|
||||
|
||||
@@ -175,8 +175,6 @@ def filter_available(qs, channel='web', voucher=None, allow_addons=False):
|
||||
q &= Q(pk=voucher.item_id)
|
||||
elif voucher.quota_id:
|
||||
q &= Q(quotas__in=[voucher.quota_id])
|
||||
else:
|
||||
return qs.none()
|
||||
if not voucher or not voucher.show_hidden_items:
|
||||
q &= Q(hide_without_voucher=False)
|
||||
|
||||
@@ -977,6 +975,7 @@ class Question(LoggedModel):
|
||||
TYPE_TIME = "H"
|
||||
TYPE_DATETIME = "W"
|
||||
TYPE_COUNTRYCODE = "CC"
|
||||
TYPE_PHONENUMBER = "TEL"
|
||||
TYPE_CHOICES = (
|
||||
(TYPE_NUMBER, _("Number")),
|
||||
(TYPE_STRING, _("Text (one line)")),
|
||||
@@ -989,8 +988,10 @@ class Question(LoggedModel):
|
||||
(TYPE_TIME, _("Time")),
|
||||
(TYPE_DATETIME, _("Date and time")),
|
||||
(TYPE_COUNTRYCODE, _("Country code (ISO 3166-1 alpha-2)")),
|
||||
(TYPE_PHONENUMBER, _("Phone number")),
|
||||
)
|
||||
UNLOCALIZED_TYPES = [TYPE_DATE, TYPE_TIME, TYPE_DATETIME]
|
||||
ASK_DURING_CHECKIN_UNSUPPORTED = [TYPE_FILE, TYPE_PHONENUMBER]
|
||||
|
||||
event = models.ForeignKey(
|
||||
Event,
|
||||
|
||||
@@ -30,6 +30,8 @@ from django_countries.fields import Country, CountryField
|
||||
from django_scopes import ScopedManager, scopes_disabled
|
||||
from i18nfield.strings import LazyI18nString
|
||||
from jsonfallback.fields import FallbackJSONField
|
||||
from phonenumber_field.phonenumber import PhoneNumber
|
||||
from phonenumbers import NumberParseException
|
||||
|
||||
from pretix.base.banlist import banned
|
||||
from pretix.base.decimal import round_decimal
|
||||
@@ -922,6 +924,11 @@ class QuestionAnswer(models.Model):
|
||||
return self.answer
|
||||
elif self.question.type == Question.TYPE_COUNTRYCODE and self.answer:
|
||||
return Country(self.answer).name or self.answer
|
||||
elif self.question.type == Question.TYPE_PHONENUMBER and self.answer:
|
||||
try:
|
||||
return PhoneNumber.from_string(self.answer).as_international
|
||||
except NumberParseException:
|
||||
return self.answer
|
||||
else:
|
||||
return self.answer
|
||||
|
||||
@@ -1213,6 +1220,7 @@ class OrderPayment(models.Model):
|
||||
"""
|
||||
return self.order.event.get_payment_providers(cached=True).get(self.provider)
|
||||
|
||||
@transaction.atomic()
|
||||
def _mark_paid(self, force, count_waitinglist, user, auth, ignore_date=False, overpaid=False):
|
||||
from pretix.base.signals import order_paid
|
||||
can_be_paid = self.order._can_be_paid(count_waitinglist=count_waitinglist, ignore_date=ignore_date, force=force)
|
||||
@@ -1281,7 +1289,7 @@ class OrderPayment(models.Model):
|
||||
'provider': self.provider,
|
||||
}, user=user, auth=auth)
|
||||
|
||||
if self.order.status == Order.STATUS_PAID:
|
||||
if self.order.status in (Order.STATUS_PAID, Order.STATUS_CANCELED):
|
||||
return
|
||||
|
||||
payment_sum = self.order.payments.filter(
|
||||
@@ -2045,6 +2053,13 @@ class InvoiceAddress(models.Model):
|
||||
self.name_parts = {}
|
||||
super().save(**kwargs)
|
||||
|
||||
@property
|
||||
def is_empty(self):
|
||||
return (
|
||||
not self.name_cached and not self.company and not self.street and not self.zipcode and not self.city
|
||||
and not self.internal_reference and not self.beneficiary
|
||||
)
|
||||
|
||||
@property
|
||||
def state_name(self):
|
||||
sd = pycountry.subdivisions.get(code='{}-{}'.format(self.country, self.state))
|
||||
|
||||
@@ -5,6 +5,7 @@ import jsonschema
|
||||
from django.contrib.staticfiles import finders
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
from django.db.models import F, Q
|
||||
from django.utils.deconstruct import deconstructible
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext, ugettext_lazy as _
|
||||
@@ -39,7 +40,7 @@ class SeatingPlan(LoggedModel):
|
||||
layout = models.TextField(validators=[SeatingPlanLayoutValidator()])
|
||||
|
||||
Category = namedtuple('Categrory', 'name')
|
||||
RawSeat = namedtuple('Seat', 'name guid number row category zone')
|
||||
RawSeat = namedtuple('Seat', 'name guid number row category zone sorting_rank')
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
@@ -59,16 +60,36 @@ class SeatingPlan(LoggedModel):
|
||||
]
|
||||
|
||||
def iter_all_seats(self):
|
||||
for z in self.layout_data['zones']:
|
||||
for r in z['rows']:
|
||||
for s in r['seats']:
|
||||
# This returns all seats in a plan and assignes each of them a rank. The rank is used for sorting lists of
|
||||
# seats later. The rank does not say anything about the *quality* of a seat, and is only meant as a heuristic
|
||||
# to make it easier for humas to process lists of seats. The current algorithm assumes that there are less
|
||||
# than 10'000 zones, less than 10'000 rows in every zone and less than 10'000 seats in every row.
|
||||
# Respectively, no row/seat numbers may be numeric with a value of 10'000 or more. The resulting ranks
|
||||
# *will* have gaps. We chose this way over just sorting the seats and continuously enumerating them as an
|
||||
# optimization, because this way we do not need to update the rank of very seat if we change a plan a little.
|
||||
for zi, z in enumerate(self.layout_data['zones']):
|
||||
for ri, r in enumerate(z['rows']):
|
||||
try:
|
||||
row_rank = int(r['row_number'])
|
||||
except ValueError:
|
||||
row_rank = ri
|
||||
for si, s in enumerate(r['seats']):
|
||||
try:
|
||||
seat_rank = int(s['seat_number'])
|
||||
except ValueError:
|
||||
seat_rank = si
|
||||
rank = (
|
||||
10000 * 10000 * zi + 10000 * row_rank + seat_rank
|
||||
)
|
||||
|
||||
yield self.RawSeat(
|
||||
number=s['seat_number'],
|
||||
guid=s['seat_guid'],
|
||||
name='{} {}'.format(r['row_number'], s['seat_number']), # TODO: Zone? Variable scheme?
|
||||
row=r['row_number'],
|
||||
zone=z['name'],
|
||||
category=s['category']
|
||||
category=s['category'],
|
||||
sorting_rank=rank
|
||||
)
|
||||
|
||||
|
||||
@@ -97,6 +118,10 @@ class Seat(models.Model):
|
||||
seat_guid = models.CharField(max_length=190, db_index=True)
|
||||
product = models.ForeignKey('Item', null=True, blank=True, related_name='seats', on_delete=models.CASCADE)
|
||||
blocked = models.BooleanField(default=False)
|
||||
sorting_rank = models.BigIntegerField(default=0)
|
||||
|
||||
class Meta:
|
||||
ordering = ['sorting_rank', 'seat_guid']
|
||||
|
||||
def __str__(self):
|
||||
parts = []
|
||||
@@ -110,15 +135,21 @@ class Seat(models.Model):
|
||||
return self.name
|
||||
return ', '.join(parts)
|
||||
|
||||
def is_available(self, ignore_cart=None, ignore_orderpos=None):
|
||||
def is_available(self, ignore_cart=None, ignore_orderpos=None, ignore_voucher_id=None):
|
||||
from .orders import Order
|
||||
|
||||
if self.blocked:
|
||||
return False
|
||||
opqs = self.orderposition_set.filter(order__status__in=[Order.STATUS_PENDING, Order.STATUS_PAID])
|
||||
cpqs = self.cartposition_set.filter(expires__gte=now())
|
||||
if ignore_cart:
|
||||
vqs = self.vouchers.filter(
|
||||
Q(Q(valid_until__isnull=True) | Q(valid_until__gte=now())) &
|
||||
Q(redeemed__lt=F('max_usages'))
|
||||
)
|
||||
if ignore_cart and ignore_cart is not True:
|
||||
cpqs = cpqs.exclude(pk=ignore_cart.pk)
|
||||
if ignore_orderpos:
|
||||
opqs = opqs.exclude(pk=ignore_orderpos.pk)
|
||||
return not opqs.exists() and not cpqs.exists()
|
||||
if ignore_voucher_id:
|
||||
vqs = vqs.exclude(pk=ignore_voucher_id)
|
||||
return not opqs.exists() and (ignore_cart is True or not cpqs.exists()) and not vqs.exists()
|
||||
|
||||
@@ -198,8 +198,14 @@ class TaxRule(LoggedModel):
|
||||
rate=self.rate, name=self.name
|
||||
)
|
||||
|
||||
@property
|
||||
def _custom_rules(self):
|
||||
if not self.custom_rules:
|
||||
return []
|
||||
return json.loads(self.custom_rules)
|
||||
|
||||
def get_matching_rule(self, invoice_address):
|
||||
rules = json.loads(self.custom_rules)
|
||||
rules = self._custom_rules
|
||||
if invoice_address:
|
||||
for r in rules:
|
||||
if r['country'] == 'EU' and str(invoice_address.country) not in EU_COUNTRIES:
|
||||
@@ -216,7 +222,7 @@ class TaxRule(LoggedModel):
|
||||
return {'action': 'vat'}
|
||||
|
||||
def is_reverse_charge(self, invoice_address):
|
||||
if self.custom_rules:
|
||||
if self._custom_rules:
|
||||
rule = self.get_matching_rule(invoice_address)
|
||||
return rule['action'] == 'reverse'
|
||||
|
||||
@@ -238,7 +244,7 @@ class TaxRule(LoggedModel):
|
||||
return False
|
||||
|
||||
def tax_applicable(self, invoice_address):
|
||||
if self.custom_rules:
|
||||
if self._custom_rules:
|
||||
rule = self.get_matching_rule(invoice_address)
|
||||
return rule.get('action', 'vat') == 'vat'
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ from django.utils.translation import pgettext_lazy, ugettext_lazy as _
|
||||
from django_scopes import ScopedManager, scopes_disabled
|
||||
|
||||
from pretix.base.banlist import banned
|
||||
from pretix.base.models import SeatCategoryMapping
|
||||
from pretix.base.models import Seat, SeatCategoryMapping
|
||||
|
||||
from ..decimal import round_decimal
|
||||
from .base import LoggedModel
|
||||
@@ -171,6 +171,12 @@ class Voucher(LoggedModel):
|
||||
"If enabled, the voucher is valid for any product affected by this quota."
|
||||
)
|
||||
)
|
||||
seat = models.ForeignKey(
|
||||
Seat, related_name='vouchers',
|
||||
null=True, blank=True,
|
||||
on_delete=models.PROTECT,
|
||||
verbose_name=_("Specific seat"),
|
||||
)
|
||||
tag = models.CharField(
|
||||
max_length=255,
|
||||
verbose_name=_("Tag"),
|
||||
@@ -211,11 +217,12 @@ class Voucher(LoggedModel):
|
||||
self.event,
|
||||
self.quota,
|
||||
self.item,
|
||||
self.variation
|
||||
self.variation,
|
||||
seats_given=bool(self.seat)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def clean_item_properties(data, event, quota, item, variation):
|
||||
def clean_item_properties(data, event, quota, item, variation, block_quota=False, seats_given=False):
|
||||
if quota:
|
||||
if quota.event != event:
|
||||
raise ValidationError(_('You cannot select a quota that belongs to a different event.'))
|
||||
@@ -234,8 +241,12 @@ class Voucher(LoggedModel):
|
||||
'Otherwise it might be unclear which quotas to block.'))
|
||||
if item.category and item.category.is_addon:
|
||||
raise ValidationError(_('It is currently not possible to create vouchers for add-on products.'))
|
||||
else:
|
||||
raise ValidationError(_('You need to specify either a quota or a product.'))
|
||||
elif block_quota:
|
||||
raise ValidationError(_('You need to select a specific product or quota if this voucher should reserve '
|
||||
'tickets.'))
|
||||
elif variation:
|
||||
raise ValidationError(_('You cannot select a variation without having selected a product that provides '
|
||||
'variations.'))
|
||||
|
||||
@staticmethod
|
||||
def clean_max_usages(data, redeemed):
|
||||
@@ -324,7 +335,8 @@ class Voucher(LoggedModel):
|
||||
elif item and not item.has_variations:
|
||||
avail = item.check_quotas(ignored_quotas=old_quotas, subevent=data.get('subevent'))
|
||||
else:
|
||||
raise ValidationError(_('You need to specify either a quota or a product.'))
|
||||
raise ValidationError(_('You need to select a specific product or quota if this voucher should reserve '
|
||||
'tickets.'))
|
||||
|
||||
if avail[0] != Quota.AVAILABILITY_OK or (avail[1] is not None and avail[1] < cnt):
|
||||
raise ValidationError(_('You cannot create a voucher that blocks quota as the selected product or '
|
||||
@@ -335,6 +347,42 @@ class Voucher(LoggedModel):
|
||||
if 'code' in data and Voucher.objects.filter(Q(code__iexact=data['code']) & Q(event=event) & ~Q(pk=pk)).exists():
|
||||
raise ValidationError(_('A voucher with this code already exists.'))
|
||||
|
||||
@staticmethod
|
||||
def clean_seat_id(data, item, quota, event, pk):
|
||||
try:
|
||||
if event.has_subevents:
|
||||
if not data.get('subevent'):
|
||||
raise ValidationError(_('You need to choose a date if you select a seat.'))
|
||||
seat = event.seats.select_related('product').get(
|
||||
seat_guid=data.get('seat'), subevent=data.get('subevent')
|
||||
)
|
||||
else:
|
||||
seat = event.seats.select_related('product').get(
|
||||
seat_guid=data.get('seat')
|
||||
)
|
||||
except Seat.DoesNotExist:
|
||||
raise ValidationError(_('The specified seat ID "{id}" does not exist for this event.').format(
|
||||
id=data.get('seat')))
|
||||
|
||||
if not seat.is_available(ignore_voucher_id=pk, ignore_cart=True):
|
||||
raise ValidationError(_('The seat "{id}" is currently unavailable (blocked, already sold or a '
|
||||
'different voucher).').format(
|
||||
id=seat.seat_guid))
|
||||
|
||||
if quota:
|
||||
raise ValidationError(_('You need to choose a specific product if you select a seat.'))
|
||||
|
||||
if data.get('max_usages', 1) > 1:
|
||||
raise ValidationError(_('Seat-specific vouchers can only be used once.'))
|
||||
|
||||
if item and seat.product != item:
|
||||
raise ValidationError(_('You need to choose the product "{prod}" for this seat.').format(prod=seat.product))
|
||||
|
||||
if not seat.is_available(ignore_voucher_id=pk):
|
||||
raise ValidationError(_('The seat "{id}" is already sold or currently blocked.').format(id=seat.seat_guid))
|
||||
|
||||
return seat
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.code = self.code.upper()
|
||||
super().save(*args, **kwargs)
|
||||
@@ -367,7 +415,9 @@ class Voucher(LoggedModel):
|
||||
return item.quotas.filter(pk=self.quota_id).exists()
|
||||
if self.item_id and not self.variation_id:
|
||||
return self.item_id == item.pk
|
||||
return (self.item_id == item.pk) and (variation and self.variation_id == variation.pk)
|
||||
if self.item_id:
|
||||
return (self.item_id == item.pk) and (variation and self.variation_id == variation.pk)
|
||||
return True
|
||||
|
||||
def is_active(self):
|
||||
"""
|
||||
|
||||
@@ -20,6 +20,7 @@ from django_countries import Countries
|
||||
from i18nfield.forms import I18nFormField, I18nTextarea, I18nTextInput
|
||||
from i18nfield.strings import LazyI18nString
|
||||
|
||||
from pretix.base.channels import get_all_sales_channels
|
||||
from pretix.base.forms import PlaceholderValidator
|
||||
from pretix.base.models import (
|
||||
CartPosition, Event, GiftCard, InvoiceAddress, Order, OrderPayment,
|
||||
@@ -62,12 +63,11 @@ class BasePaymentProvider:
|
||||
def __str__(self):
|
||||
return self.identifier
|
||||
|
||||
@property
|
||||
def is_implicit(self) -> bool:
|
||||
def is_implicit(self, request: HttpRequest) -> bool:
|
||||
"""
|
||||
Returns whether or whether not this payment provider is an "implicit" payment provider that will
|
||||
*always* and unconditionally be used if is_allowed() returns True and does not require any input.
|
||||
This is intended to be used by the FreePaymentProvider, which skips the payment choice page.
|
||||
This is intended to be used by the FreeOrderProvider, which skips the payment choice page.
|
||||
By default, this returns ``False``. Please do not set this if you don't know exactly what you are doing.
|
||||
"""
|
||||
return False
|
||||
@@ -83,6 +83,14 @@ class BasePaymentProvider:
|
||||
"""
|
||||
return False
|
||||
|
||||
@property
|
||||
def priority(self) -> int:
|
||||
"""
|
||||
Returns a priority that is used for sorting payment providers. Higher priority means higher up in the list.
|
||||
Default to 100. Providers with same priority are sorted alphabetically.
|
||||
"""
|
||||
return 100
|
||||
|
||||
@property
|
||||
def is_enabled(self) -> bool:
|
||||
"""
|
||||
@@ -278,8 +286,21 @@ class BasePaymentProvider:
|
||||
required=False,
|
||||
disabled=not self.event.settings.invoice_address_required
|
||||
)),
|
||||
('_restrict_to_sales_channels',
|
||||
forms.MultipleChoiceField(
|
||||
label=_('Restrict to specific sales channels'),
|
||||
choices=(
|
||||
(c.identifier, c.verbose_name) for c in get_all_sales_channels().values()
|
||||
if c.payment_restrictions_supported
|
||||
),
|
||||
initial=['web'],
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
help_text=_(
|
||||
'Only allow the usage of this payment provider in the following sales channels'),
|
||||
))
|
||||
])
|
||||
d['_restricted_countries']._as_type = list
|
||||
d['_restrict_to_sales_channels']._as_type = list
|
||||
return d
|
||||
|
||||
def settings_form_clean(self, cleaned_data):
|
||||
@@ -391,7 +412,7 @@ class BasePaymentProvider:
|
||||
|
||||
The default implementation checks for the _availability_date setting to be either unset or in the future
|
||||
and for the _total_max and _total_min requirements to be met. It also checks the ``_restrict_countries``
|
||||
setting.
|
||||
and ``_restrict_to_sales_channels`` setting.
|
||||
|
||||
:param total: The total value without the payment method fee, after taxes.
|
||||
|
||||
@@ -432,6 +453,10 @@ class BasePaymentProvider:
|
||||
if str(ia.country) not in restricted_countries:
|
||||
return False
|
||||
|
||||
if hasattr(request, 'sales_channel') and request.sales_channel.identifier not in \
|
||||
self.settings.get('_restrict_to_sales_channels', as_type=list, default=['web']):
|
||||
return False
|
||||
|
||||
return timing and pricing
|
||||
|
||||
def payment_form_render(self, request: HttpRequest, total: Decimal) -> str:
|
||||
@@ -587,6 +612,9 @@ class BasePaymentProvider:
|
||||
if str(ia.country) not in restricted_countries:
|
||||
return False
|
||||
|
||||
if order.sales_channel not in self.settings.get('_restrict_to_sales_channels', as_type=list, default=['web']):
|
||||
return False
|
||||
|
||||
return self._is_still_available(order=order)
|
||||
|
||||
def payment_prepare(self, request: HttpRequest, payment: OrderPayment) -> Union[bool, str]:
|
||||
@@ -623,6 +651,19 @@ class BasePaymentProvider:
|
||||
"""
|
||||
return ''
|
||||
|
||||
def refund_control_render(self, request: HttpRequest, refund: OrderRefund) -> str:
|
||||
"""
|
||||
Will be called if the *event administrator* views the details of a refund.
|
||||
|
||||
It should return HTML code containing information regarding the current refund
|
||||
status and, if applicable, next steps.
|
||||
|
||||
The default implementation returns an empty string.
|
||||
|
||||
:param order: The order object
|
||||
"""
|
||||
return ''
|
||||
|
||||
def payment_refund_supported(self, payment: OrderPayment) -> bool:
|
||||
"""
|
||||
Will be called to check if the provider supports automatic refunding for this
|
||||
@@ -637,6 +678,17 @@ class BasePaymentProvider:
|
||||
"""
|
||||
return False
|
||||
|
||||
def cancel_payment(self, payment: OrderPayment):
|
||||
"""
|
||||
Will be called to cancel a payment. The default implementation just sets the payment state to canceled,
|
||||
but in some cases you might want to notify an external provider.
|
||||
|
||||
On success, you should set ``payment.state = OrderPayment.PAYMENT_STATE_CANCELED`` (or call the super method).
|
||||
On failure, you should raise a PaymentException.
|
||||
"""
|
||||
payment.state = OrderPayment.PAYMENT_STATE_CANCELED
|
||||
payment.save()
|
||||
|
||||
def execute_refund(self, refund: OrderRefund):
|
||||
"""
|
||||
Will be called to execute an refund. Note that refunds have an amount property and can be partial.
|
||||
@@ -765,8 +817,7 @@ class ManualPayment(BasePaymentProvider):
|
||||
return _('In test mode, you can just manually mark this order as paid in the backend after it has been '
|
||||
'created.')
|
||||
|
||||
@property
|
||||
def is_implicit(self):
|
||||
def is_implicit(self, request: HttpRequest):
|
||||
return 'pretix.plugins.manualpayment' not in self.event.plugins
|
||||
|
||||
def is_allowed(self, request: HttpRequest, total: Decimal=None):
|
||||
@@ -871,7 +922,10 @@ class OffsettingProvider(BasePaymentProvider):
|
||||
provider='offsetting',
|
||||
info=json.dumps({'orders': [refund.order.code]})
|
||||
)
|
||||
p.confirm(ignore_date=True)
|
||||
try:
|
||||
p.confirm(ignore_date=True)
|
||||
except Quota.QuotaExceededException:
|
||||
pass
|
||||
|
||||
@property
|
||||
def settings_form_fields(self) -> dict:
|
||||
@@ -895,6 +949,7 @@ class OffsettingProvider(BasePaymentProvider):
|
||||
class GiftCardPayment(BasePaymentProvider):
|
||||
identifier = "giftcard"
|
||||
verbose_name = _("Gift card")
|
||||
priority = 10
|
||||
|
||||
@property
|
||||
def settings_form_fields(self):
|
||||
@@ -923,6 +978,20 @@ class GiftCardPayment(BasePaymentProvider):
|
||||
def checkout_confirm_render(self, request) -> str:
|
||||
return get_template('pretixcontrol/giftcards/checkout_confirm.html').render({})
|
||||
|
||||
def refund_control_render(self, request, refund) -> str:
|
||||
from .models import GiftCard
|
||||
|
||||
if 'gift_card' in refund.info_data:
|
||||
gc = GiftCard.objects.get(pk=refund.info_data.get('gift_card'))
|
||||
template = get_template('pretixcontrol/giftcards/payment.html')
|
||||
|
||||
ctx = {
|
||||
'request': request,
|
||||
'event': self.event,
|
||||
'gc': gc,
|
||||
}
|
||||
return template.render(ctx)
|
||||
|
||||
def payment_control_render(self, request, payment) -> str:
|
||||
from .models import GiftCard
|
||||
|
||||
@@ -1080,7 +1149,7 @@ class GiftCardPayment(BasePaymentProvider):
|
||||
@transaction.atomic()
|
||||
def execute_refund(self, refund: OrderRefund):
|
||||
from .models import GiftCard
|
||||
gc = GiftCard.objects.get(pk=refund.payment.info_data.get('gift_card'))
|
||||
gc = GiftCard.objects.get(pk=refund.info_data.get('gift_card') or refund.payment.info_data.get('gift_card'))
|
||||
trans = gc.transactions.create(
|
||||
value=refund.amount,
|
||||
order=refund.order,
|
||||
|
||||
@@ -10,6 +10,8 @@ from functools import partial
|
||||
from io import BytesIO
|
||||
|
||||
import bleach
|
||||
from arabic_reshaper import ArabicReshaper
|
||||
from bidi.algorithm import get_display
|
||||
from django.conf import settings
|
||||
from django.contrib.staticfiles import finders
|
||||
from django.dispatch import receiver
|
||||
@@ -449,6 +451,16 @@ class Renderer:
|
||||
tags=["br"], attributes={}, styles=[], strip=True
|
||||
)
|
||||
)
|
||||
|
||||
# reportlab does not support RTL, ligature-heavy scripts like Arabic. Therefore, we use ArabicReshaper
|
||||
# to resolve all ligatures and python-bidi to switch RTL texts.
|
||||
configuration = {
|
||||
'delete_harakat': True,
|
||||
'support_ligatures': False,
|
||||
}
|
||||
reshaper = ArabicReshaper(configuration=configuration)
|
||||
text = "<br/>".join(get_display(reshaper.reshape(l)) for l in text.split("<br/>"))
|
||||
|
||||
p = Paragraph(text, style=style)
|
||||
p.wrapOn(canvas, float(o['width']) * mm, 1000 * mm)
|
||||
# p_size = p.wrap(float(o['width']) * mm, 1000 * mm)
|
||||
|
||||
@@ -103,11 +103,18 @@ class RelativeDateWrapper:
|
||||
else:
|
||||
timeparts = parts[2].split(':')
|
||||
time = datetime.time(hour=int(timeparts[0]), minute=int(timeparts[1]), second=int(timeparts[2]))
|
||||
data = RelativeDate(
|
||||
days_before=int(parts[1] or 0),
|
||||
base_date_name=parts[3],
|
||||
time=time
|
||||
)
|
||||
try:
|
||||
data = RelativeDate(
|
||||
days_before=int(parts[1] or 0),
|
||||
base_date_name=parts[3],
|
||||
time=time
|
||||
)
|
||||
except ValueError:
|
||||
data = RelativeDate(
|
||||
days_before=0,
|
||||
base_date_name=parts[3],
|
||||
time=time
|
||||
)
|
||||
else:
|
||||
data = parser.parse(input)
|
||||
return RelativeDateWrapper(data)
|
||||
|
||||
@@ -12,9 +12,10 @@ from django.utils.timezone import make_aware, now
|
||||
from django.utils.translation import pgettext_lazy, ugettext as _
|
||||
from django_scopes import scopes_disabled
|
||||
|
||||
from pretix.base.channels import get_all_sales_channels
|
||||
from pretix.base.i18n import language
|
||||
from pretix.base.models import (
|
||||
CartPosition, Event, InvoiceAddress, Item, ItemBundle, ItemVariation, Seat,
|
||||
CartPosition, Event, InvoiceAddress, Item, ItemVariation, Seat,
|
||||
SeatCategoryMapping, Voucher,
|
||||
)
|
||||
from pretix.base.models.event import SubEvent
|
||||
@@ -80,6 +81,10 @@ error_messages = {
|
||||
'cart if you want to use it for a different product.'),
|
||||
'voucher_expired': _('This voucher is expired.'),
|
||||
'voucher_invalid_item': _('This voucher is not valid for this product.'),
|
||||
'voucher_invalid_seat': _('This voucher is not valid for this seat.'),
|
||||
'voucher_no_match': _('We did not find any position in your cart that we could use this voucher for. If you want '
|
||||
'to add something new to your cart using that voucher, you can do so with the voucher '
|
||||
'redemption option on the bottom of the page.'),
|
||||
'voucher_item_not_available': _(
|
||||
'Your voucher is valid for a product that is currently not for sale.'),
|
||||
'voucher_invalid_subevent': pgettext_lazy('subevent', 'This voucher is not valid for this event date.'),
|
||||
@@ -105,10 +110,12 @@ class CartManager:
|
||||
AddOperation = namedtuple('AddOperation', ('count', 'item', 'variation', 'price', 'voucher', 'quotas',
|
||||
'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'))
|
||||
order = {
|
||||
RemoveOperation: 10,
|
||||
VoucherOperation: 15,
|
||||
ExtendOperation: 20,
|
||||
AddOperation: 30
|
||||
}
|
||||
@@ -217,13 +224,14 @@ class CartManager:
|
||||
})
|
||||
|
||||
def _check_max_cart_size(self):
|
||||
cartsize = self.positions.filter(addon_to__isnull=True).count()
|
||||
cartsize += sum([op.count for op in self._operations if isinstance(op, self.AddOperation) and not op.addon_to])
|
||||
cartsize -= len([1 for op in self._operations if isinstance(op, self.RemoveOperation) if
|
||||
not op.position.addon_to_id])
|
||||
if cartsize > int(self.event.settings.max_items_per_order):
|
||||
# TODO: i18n plurals
|
||||
raise CartError(_(error_messages['max_items']) % (self.event.settings.max_items_per_order,))
|
||||
if not get_all_sales_channels()[self._sales_channel].unlimited_items_per_order:
|
||||
cartsize = self.positions.filter(addon_to__isnull=True).count()
|
||||
cartsize += sum([op.count for op in self._operations if isinstance(op, self.AddOperation) and not op.addon_to])
|
||||
cartsize -= len([1 for op in self._operations if isinstance(op, self.RemoveOperation) if
|
||||
not op.position.addon_to_id])
|
||||
if cartsize > int(self.event.settings.max_items_per_order):
|
||||
# TODO: i18n plurals
|
||||
raise CartError(_(error_messages['max_items']) % (self.event.settings.max_items_per_order,))
|
||||
|
||||
def _check_item_constraints(self, op):
|
||||
if isinstance(op, self.AddOperation) or isinstance(op, self.ExtendOperation):
|
||||
@@ -252,6 +260,9 @@ class CartManager:
|
||||
if op.voucher and not op.voucher.applies_to(op.item, op.variation):
|
||||
raise CartError(error_messages['voucher_invalid_item'])
|
||||
|
||||
if op.voucher and op.voucher.seat and op.voucher.seat != op.seat:
|
||||
raise CartError(error_messages['voucher_invalid_seat'])
|
||||
|
||||
if op.voucher and op.voucher.subevent_id and op.voucher.subevent_id != op.subevent.pk:
|
||||
raise CartError(error_messages['voucher_invalid_subevent'])
|
||||
|
||||
@@ -358,10 +369,10 @@ class CartManager:
|
||||
cp.item.requires_seat = cp.requires_seat
|
||||
|
||||
if cp.is_bundled:
|
||||
try:
|
||||
bundle = cp.addon_to.item.bundles.get(bundled_item=cp.item, bundled_variation=cp.variation)
|
||||
bundle = cp.addon_to.item.bundles.filter(bundled_item=cp.item, bundled_variation=cp.variation).first()
|
||||
if bundle:
|
||||
price = bundle.designated_price or 0
|
||||
except ItemBundle.DoesNotExist:
|
||||
else:
|
||||
price = cp.price
|
||||
|
||||
changed_prices[cp.pk] = price
|
||||
@@ -413,6 +424,58 @@ class CartManager:
|
||||
self._operations.append(op)
|
||||
return err
|
||||
|
||||
def apply_voucher(self, voucher_code: str):
|
||||
if self._operations:
|
||||
raise CartError('Applying a voucher to the whole cart should not be combined with other operations.')
|
||||
try:
|
||||
voucher = self.event.vouchers.get(code__iexact=voucher_code.strip())
|
||||
except Voucher.DoesNotExist:
|
||||
raise CartError(error_messages['voucher_invalid'])
|
||||
voucher_use_diff = Counter()
|
||||
ops = []
|
||||
|
||||
if not voucher.is_active():
|
||||
raise CartError(error_messages['voucher_expired'])
|
||||
|
||||
for p in self.positions:
|
||||
if p.voucher_id:
|
||||
continue
|
||||
|
||||
if not voucher.applies_to(p.item, p.variation):
|
||||
continue
|
||||
|
||||
if voucher.seat and voucher.seat != p.seat:
|
||||
continue
|
||||
|
||||
if voucher.subevent_id and voucher.subevent_id != p.subevent_id:
|
||||
continue
|
||||
|
||||
if p.is_bundled:
|
||||
continue
|
||||
|
||||
bundled_sum = Decimal('0.00')
|
||||
if not p.addon_to_id:
|
||||
for bundledp in p.addons.all():
|
||||
if bundledp.is_bundled:
|
||||
bundledprice = bundledp.price
|
||||
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)))
|
||||
|
||||
# If there are not enough voucher usages left for the full cart, let's apply them in the order that benefits
|
||||
# the user the most.
|
||||
ops.sort(key=lambda k: k[0], reverse=True)
|
||||
self._operations += [k[1] for k in ops]\
|
||||
|
||||
if not voucher_use_diff:
|
||||
raise CartError(error_messages['voucher_no_match'])
|
||||
self._voucher_use_diff += voucher_use_diff
|
||||
|
||||
def add_new_items(self, items: List[dict]):
|
||||
# Fetch items from the database
|
||||
self._update_items_cache([i['item'] for i in items], [i['variation'] for i in items])
|
||||
@@ -423,7 +486,7 @@ class CartManager:
|
||||
|
||||
for i in items:
|
||||
if self.event.has_subevents:
|
||||
if not i.get('subevent'):
|
||||
if not i.get('subevent') or int(i.get('subevent')) not in self._subevents_cache:
|
||||
raise CartError(error_messages['subevent_required'])
|
||||
subevent = self._subevents_cache[int(i.get('subevent'))]
|
||||
else:
|
||||
@@ -756,7 +819,7 @@ class CartManager:
|
||||
self._operations.sort(key=lambda a: self.order[type(a)])
|
||||
seats_seen = set()
|
||||
|
||||
for op in self._operations:
|
||||
for iop, op in enumerate(self._operations):
|
||||
if isinstance(op, self.RemoveOperation):
|
||||
if op.position.expires > self.now_dt:
|
||||
for q in op.position.quotas:
|
||||
@@ -824,7 +887,7 @@ class CartManager:
|
||||
available_count = 0
|
||||
|
||||
if isinstance(op, self.AddOperation):
|
||||
if op.seat and not op.seat.is_available():
|
||||
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']
|
||||
|
||||
@@ -872,7 +935,8 @@ 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):
|
||||
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()
|
||||
op.position.delete()
|
||||
@@ -889,6 +953,19 @@ class CartManager:
|
||||
op.position.delete()
|
||||
else:
|
||||
raise AssertionError("ExtendOperation cannot affect more than one item")
|
||||
elif isinstance(op, self.VoucherOperation):
|
||||
if vouchers_ok[op.voucher] < 1:
|
||||
if iop == 0:
|
||||
raise CartError(error_messages['voucher_redeemed'])
|
||||
else:
|
||||
# We fail silently if we could only apply the voucher to part of the cart, since that might
|
||||
# be expected
|
||||
continue
|
||||
|
||||
op.position.price = op.price.gross
|
||||
op.position.voucher = op.voucher
|
||||
op.position.save()
|
||||
vouchers_ok[op.voucher] -= 1
|
||||
|
||||
for p in new_cart_positions:
|
||||
if getattr(p, '_answers', None):
|
||||
@@ -1053,6 +1130,26 @@ def add_items_to_cart(self, event: int, items: List[dict], cart_id: str=None, lo
|
||||
raise CartError(error_messages['busy'])
|
||||
|
||||
|
||||
@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') -> None:
|
||||
"""
|
||||
Removes a list of items from a user's cart.
|
||||
:param event: The event ID in question
|
||||
:param voucher: A voucher code
|
||||
:param session: Session ID of a guest
|
||||
"""
|
||||
with language(locale):
|
||||
try:
|
||||
try:
|
||||
cm = CartManager(event=event, cart_id=cart_id)
|
||||
cm.apply_voucher(voucher)
|
||||
cm.commit()
|
||||
except LockTimeoutException:
|
||||
self.retry()
|
||||
except (MaxRetriesExceededError, LockTimeoutException):
|
||||
raise CartError(error_messages['busy'])
|
||||
|
||||
|
||||
@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') -> None:
|
||||
"""
|
||||
|
||||
@@ -13,6 +13,7 @@ from django.db import transaction
|
||||
from django.db.models import Count
|
||||
from django.dispatch import receiver
|
||||
from django.utils import timezone
|
||||
from django.utils.formats import date_format
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import pgettext, ugettext as _
|
||||
from django_countries.fields import Country
|
||||
@@ -20,11 +21,13 @@ from django_scopes import scope, scopes_disabled
|
||||
from i18nfield.strings import LazyI18nString
|
||||
|
||||
from pretix.base.i18n import language
|
||||
from pretix.base.models import Invoice, InvoiceAddress, InvoiceLine, Order
|
||||
from pretix.base.models import (
|
||||
Invoice, InvoiceAddress, InvoiceLine, Order, OrderFee,
|
||||
)
|
||||
from pretix.base.models.tax import EU_CURRENCIES
|
||||
from pretix.base.services.tasks import TransactionAwareTask
|
||||
from pretix.base.settings import GlobalSettingsObject
|
||||
from pretix.base.signals import periodic_task
|
||||
from pretix.base.signals import invoice_line_text, periodic_task
|
||||
from pretix.celery_app import app
|
||||
from pretix.helpers.database import rolledback_transaction
|
||||
from pretix.helpers.models import modelcopy
|
||||
@@ -50,11 +53,17 @@ def build_invoice(invoice: Invoice) -> Invoice:
|
||||
footer = invoice.event.settings.get('invoice_footer_text', as_type=LazyI18nString)
|
||||
if lp and lp.payment_provider:
|
||||
if 'payment' in inspect.signature(lp.payment_provider.render_invoice_text).parameters:
|
||||
payment = lp.payment_provider.render_invoice_text(invoice.order, lp)
|
||||
payment = str(lp.payment_provider.render_invoice_text(invoice.order, lp))
|
||||
else:
|
||||
payment = lp.payment_provider.render_invoice_text(invoice.order)
|
||||
payment = str(lp.payment_provider.render_invoice_text(invoice.order))
|
||||
else:
|
||||
payment = ""
|
||||
if invoice.event.settings.invoice_include_expire_date and invoice.order.status == Order.STATUS_PENDING:
|
||||
if payment:
|
||||
payment += "<br />"
|
||||
payment += pgettext("invoice", "Please complete your payment before {expire_date}.").format(
|
||||
expire_date=date_format(invoice.order.expires, "SHORT_DATE_FORMAT")
|
||||
)
|
||||
|
||||
invoice.introductory_text = str(introductory).replace('\n', '<br />')
|
||||
invoice.additional_text = str(additional).replace('\n', '<br />')
|
||||
@@ -139,6 +148,9 @@ def build_invoice(invoice: Invoice) -> Invoice:
|
||||
desc = " + " + desc
|
||||
if invoice.event.settings.invoice_attendee_name and p.attendee_name:
|
||||
desc += "<br />" + pgettext("invoice", "Attendee: {name}").format(name=p.attendee_name)
|
||||
for recv, resp in invoice_line_text.send(sender=invoice.event, position=p):
|
||||
if resp:
|
||||
desc += "<br/>" + resp
|
||||
|
||||
for answ in p.answers.all():
|
||||
if not answ.question.print_on_invoice:
|
||||
@@ -174,9 +186,12 @@ def build_invoice(invoice: Invoice) -> Invoice:
|
||||
|
||||
offset = len(positions)
|
||||
for i, fee in enumerate(invoice.order.fees.all()):
|
||||
fee_title = _(fee.get_fee_type_display())
|
||||
if fee.description:
|
||||
fee_title += " - " + fee.description
|
||||
if fee.fee_type == OrderFee.FEE_TYPE_OTHER and fee.description:
|
||||
fee_title = fee.description
|
||||
else:
|
||||
fee_title = _(fee.get_fee_type_display())
|
||||
if fee.description:
|
||||
fee_title += " - " + fee.description
|
||||
InvoiceLine.objects.create(
|
||||
position=i + offset,
|
||||
invoice=invoice,
|
||||
@@ -203,7 +218,7 @@ def build_cancellation(invoice: Invoice):
|
||||
|
||||
|
||||
def generate_cancellation(invoice: Invoice, trigger_pdf=True):
|
||||
if invoice.refered.exists():
|
||||
if invoice.canceled:
|
||||
raise ValueError("Invoice should not be canceled twice.")
|
||||
cancellation = modelcopy(invoice)
|
||||
cancellation.pk = None
|
||||
|
||||
@@ -14,9 +14,12 @@ import requests
|
||||
from bs4 import BeautifulSoup
|
||||
from celery import chain
|
||||
from django.conf import settings
|
||||
from django.core.mail import EmailMultiAlternatives, get_connection
|
||||
from django.core.mail import (
|
||||
EmailMultiAlternatives, SafeMIMEMultipart, get_connection,
|
||||
)
|
||||
from django.core.mail.message import SafeMIMEText
|
||||
from django.template.loader import get_template
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import pgettext, ugettext as _
|
||||
from django_scopes import scope, scopes_disabled
|
||||
from i18nfield.strings import LazyI18nString
|
||||
|
||||
@@ -232,16 +235,32 @@ def mail(email: str, subject: str, template: Union[str, LazyI18nString],
|
||||
chain(*task_chain).apply_async()
|
||||
|
||||
|
||||
class CustomEmail(EmailMultiAlternatives):
|
||||
def _create_mime_attachment(self, content, mimetype):
|
||||
"""
|
||||
Convert the content, mimetype pair into a MIME attachment object.
|
||||
|
||||
If the mimetype is message/rfc822, content may be an
|
||||
email.Message or EmailMessage object, as well as a str.
|
||||
"""
|
||||
basetype, subtype = mimetype.split('/', 1)
|
||||
if basetype == 'multipart' and isinstance(content, SafeMIMEMultipart):
|
||||
return content
|
||||
return super()._create_mime_attachment(content, mimetype)
|
||||
|
||||
|
||||
@app.task(base=TransactionAwareTask, bind=True)
|
||||
def mail_send_task(self, *args, to: List[str], subject: str, body: str, html: str, sender: str,
|
||||
event: int=None, position: int=None, headers: dict=None, bcc: List[str]=None,
|
||||
invoices: List[int]=None, order: int=None, attach_tickets=False, user=None,
|
||||
attach_ical=False) -> bool:
|
||||
email = EmailMultiAlternatives(subject, body, sender, to=to, bcc=bcc, headers=headers)
|
||||
email = CustomEmail(subject, body, sender, to=to, bcc=bcc, headers=headers)
|
||||
if html is not None:
|
||||
html_message = SafeMIMEMultipart(_subtype='related', encoding=settings.DEFAULT_CHARSET)
|
||||
html_with_cid, cid_images = replace_images_with_cid_paths(html)
|
||||
email = attach_cid_images(email, cid_images, verify_ssl=True)
|
||||
email.attach_alternative(html_with_cid, "text/html")
|
||||
html_message.attach(SafeMIMEText(html_with_cid, 'html', settings.DEFAULT_CHARSET))
|
||||
attach_cid_images(html_message, cid_images, verify_ssl=True)
|
||||
email.attach_alternative(html_message, "multipart/related")
|
||||
|
||||
if user:
|
||||
user = User.objects.get(pk=user)
|
||||
@@ -261,11 +280,12 @@ def mail_send_task(self, *args, to: List[str], subject: str, body: str, html: st
|
||||
for inv in invoices:
|
||||
if inv.file:
|
||||
try:
|
||||
email.attach(
|
||||
'{}.pdf'.format(inv.number),
|
||||
inv.file.file.read(),
|
||||
'application/pdf'
|
||||
)
|
||||
with language(inv.order.locale):
|
||||
email.attach(
|
||||
pgettext('invoice', 'Invoice {num}').format(num=inv.number).replace(' ', '_') + '.pdf',
|
||||
inv.file.file.read(),
|
||||
'application/pdf'
|
||||
)
|
||||
except:
|
||||
logger.exception('Could not attach invoice to email')
|
||||
pass
|
||||
@@ -408,8 +428,6 @@ def attach_cid_images(msg, cid_images, verify_ssl=True):
|
||||
except:
|
||||
logger.exception("ERROR attaching CID image %s[%s]" % (cid, image))
|
||||
|
||||
return msg
|
||||
|
||||
|
||||
def encoder_linelength(msg):
|
||||
"""
|
||||
|
||||
@@ -7,9 +7,10 @@ from typing import List, Optional
|
||||
|
||||
from celery.exceptions import MaxRetriesExceededError
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
from django.db import transaction
|
||||
from django.db.models import Exists, F, Max, OuterRef, Q, Sum
|
||||
from django.db.models.functions import Greatest
|
||||
from django.db.models import Exists, F, Max, Min, OuterRef, Q, Sum
|
||||
from django.db.models.functions import Coalesce, Greatest
|
||||
from django.dispatch import receiver
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.timezone import make_aware, now
|
||||
@@ -289,7 +290,7 @@ def _cancel_order(order, user=None, send_mail: bool=True, api_token=None, device
|
||||
|
||||
if not order.cancel_allowed():
|
||||
raise OrderError(_('You cannot cancel this order.'))
|
||||
i = order.invoices.filter(is_cancellation=False).last()
|
||||
i = order.invoices.filter(is_cancellation=False, refered__isnull=True).last()
|
||||
if i:
|
||||
generate_cancellation(i)
|
||||
|
||||
@@ -359,6 +360,31 @@ def _cancel_order(order, user=None, send_mail: bool=True, api_token=None, device
|
||||
except SendMailException:
|
||||
logger.exception('Order canceled email could not be sent')
|
||||
|
||||
for p in order.payments.filter(state__in=(OrderPayment.PAYMENT_STATE_CREATED, OrderPayment.PAYMENT_STATE_PENDING)):
|
||||
try:
|
||||
with transaction.atomic():
|
||||
p.payment_provider.cancel_payment(p)
|
||||
order.log_action(
|
||||
'pretix.event.order.payment.canceled',
|
||||
{
|
||||
'local_id': p.local_id,
|
||||
'provider': p.provider,
|
||||
},
|
||||
user=user,
|
||||
auth=api_token or oauth_application or device
|
||||
)
|
||||
except PaymentException as e:
|
||||
order.log_action(
|
||||
'pretix.event.order.payment.canceled.failed',
|
||||
{
|
||||
'local_id': p.local_id,
|
||||
'provider': p.provider,
|
||||
'error': str(e)
|
||||
},
|
||||
user=user,
|
||||
auth=api_token or oauth_application or device
|
||||
)
|
||||
|
||||
order_canceled.send(order.event, order=order)
|
||||
return order.pk
|
||||
|
||||
@@ -484,9 +510,9 @@ def _check_positions(event: Event, now_dt: datetime, positions: List[CartPositio
|
||||
break
|
||||
|
||||
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) or cp.seat.blocked:
|
||||
# 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) or cp.seat.blocked:
|
||||
err = err or error_messages['seat_unavailable']
|
||||
cp.delete()
|
||||
continue
|
||||
@@ -832,14 +858,14 @@ def _perform_order(event: Event, payment_provider: str, position_ids: List[str],
|
||||
@receiver(signal=periodic_task)
|
||||
@scopes_disabled()
|
||||
def expire_orders(sender, **kwargs):
|
||||
eventcache = {}
|
||||
event_id = None
|
||||
expire = None
|
||||
|
||||
for o in Order.objects.filter(expires__lt=now(), status=Order.STATUS_PENDING,
|
||||
require_approval=False).select_related('event'):
|
||||
expire = eventcache.get(o.event.pk, None)
|
||||
if expire is None:
|
||||
require_approval=False).select_related('event').order_by('event_id'):
|
||||
if o.event_id != event_id:
|
||||
expire = o.event.settings.get('payment_term_expire_automatically', as_type=bool)
|
||||
eventcache[o.event.pk] = expire
|
||||
event_id = o.event_id
|
||||
if expire:
|
||||
mark_order_expired(o)
|
||||
|
||||
@@ -847,31 +873,35 @@ def expire_orders(sender, **kwargs):
|
||||
@receiver(signal=periodic_task)
|
||||
@scopes_disabled()
|
||||
def send_expiry_warnings(sender, **kwargs):
|
||||
eventcache = {}
|
||||
today = now().replace(hour=0, minute=0, second=0)
|
||||
days = None
|
||||
settings = None
|
||||
event_id = None
|
||||
|
||||
for o in Order.objects.filter(
|
||||
expires__gte=today, expiry_reminder_sent=False, status=Order.STATUS_PENDING,
|
||||
datetime__lte=now() - timedelta(hours=2), require_approval=False
|
||||
).only('pk'):
|
||||
with transaction.atomic():
|
||||
o = Order.objects.select_related('event').select_for_update().get(pk=o.pk)
|
||||
if o.status != Order.STATUS_PENDING or o.expiry_reminder_sent:
|
||||
# Race condition
|
||||
continue
|
||||
eventsettings = eventcache.get(o.event.pk, None)
|
||||
if eventsettings is None:
|
||||
eventsettings = o.event.settings
|
||||
eventcache[o.event.pk] = eventsettings
|
||||
).only('pk', 'event_id', 'expires').order_by('event_id'):
|
||||
if event_id != o.event_id:
|
||||
settings = o.event.settings
|
||||
days = cache.get_or_set('{}:{}:setting_mail_days_order_expire_warning'.format('event', o.event_id),
|
||||
default=lambda: settings.get('mail_days_order_expire_warning', as_type=int),
|
||||
timeout=3600)
|
||||
event_id = o.event_id
|
||||
|
||||
if days and (o.expires - today).days <= days:
|
||||
with transaction.atomic():
|
||||
o = Order.objects.select_related('event').select_for_update().get(pk=o.pk)
|
||||
if o.status != Order.STATUS_PENDING or o.expiry_reminder_sent:
|
||||
# Race condition
|
||||
continue
|
||||
|
||||
days = eventsettings.get('mail_days_order_expire_warning', as_type=int)
|
||||
if days and (o.expires - today).days <= days:
|
||||
with language(o.locale):
|
||||
o.expiry_reminder_sent = True
|
||||
o.save(update_fields=['expiry_reminder_sent'])
|
||||
email_template = eventsettings.mail_text_order_expire_warning
|
||||
email_template = settings.mail_text_order_expire_warning
|
||||
email_context = get_email_context(event=o.event, order=o)
|
||||
if eventsettings.payment_term_expire_automatically:
|
||||
if settings.payment_term_expire_automatically:
|
||||
email_subject = _('Your order is about to expire: %(code)s') % {'code': o.code}
|
||||
else:
|
||||
email_subject = _('Your order is pending payment: %(code)s') % {'code': o.code}
|
||||
@@ -889,54 +919,76 @@ def send_expiry_warnings(sender, **kwargs):
|
||||
@scopes_disabled()
|
||||
def send_download_reminders(sender, **kwargs):
|
||||
today = now().replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
qs = Order.objects.annotate(
|
||||
first_date=Coalesce(
|
||||
Min('all_positions__subevent__date_from'),
|
||||
F('event__date_from')
|
||||
)
|
||||
).filter(
|
||||
status=Order.STATUS_PAID,
|
||||
download_reminder_sent=False,
|
||||
datetime__lte=now() - timedelta(hours=2),
|
||||
first_date__gte=today,
|
||||
).only('pk', 'event_id').order_by('event_id')
|
||||
event_id = None
|
||||
days = None
|
||||
event = None
|
||||
|
||||
for e in Event.objects.filter(date_from__gte=today):
|
||||
for o in qs:
|
||||
if o.event_id != event_id:
|
||||
days = o.event.settings.get('mail_days_download_reminder', as_type=int)
|
||||
event = o.event
|
||||
event_id = o.event_id
|
||||
|
||||
days = e.settings.get('mail_days_download_reminder', as_type=int)
|
||||
if days is None:
|
||||
continue
|
||||
|
||||
reminder_date = (e.date_from - timedelta(days=days)).replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
|
||||
reminder_date = (o.first_date - timedelta(days=days)).replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
if now() < reminder_date:
|
||||
continue
|
||||
for o in e.orders.filter(status=Order.STATUS_PAID, download_reminder_sent=False, datetime__lte=now() - timedelta(hours=2)).only('pk'):
|
||||
with transaction.atomic():
|
||||
o = Order.objects.select_related('event').select_for_update().get(pk=o.pk)
|
||||
if o.download_reminder_sent:
|
||||
# Race condition
|
||||
continue
|
||||
if not all([r for rr, r in allow_ticket_download.send(e, order=o)]):
|
||||
continue
|
||||
|
||||
with language(o.locale):
|
||||
o.download_reminder_sent = True
|
||||
o.save(update_fields=['download_reminder_sent'])
|
||||
email_template = e.settings.mail_text_download_reminder
|
||||
email_context = get_email_context(event=e, order=o)
|
||||
email_subject = _('Your ticket is ready for download: %(code)s') % {'code': o.code}
|
||||
try:
|
||||
o.send_mail(
|
||||
email_subject, email_template, email_context,
|
||||
'pretix.event.order.email.download_reminder_sent',
|
||||
attach_tickets=True
|
||||
)
|
||||
except SendMailException:
|
||||
logger.exception('Reminder email could not be sent')
|
||||
with transaction.atomic():
|
||||
o = Order.objects.select_for_update().get(pk=o.pk)
|
||||
if o.download_reminder_sent:
|
||||
# Race condition
|
||||
continue
|
||||
if not all([r for rr, r in allow_ticket_download.send(event, order=o)]):
|
||||
continue
|
||||
|
||||
if e.settings.mail_send_download_reminder_attendee:
|
||||
for p in o.positions.all():
|
||||
if p.addon_to_id is None and p.attendee_email and p.attendee_email != o.email:
|
||||
email_template = e.settings.mail_text_download_reminder_attendee
|
||||
email_context = get_email_context(event=e, order=o, position=p)
|
||||
try:
|
||||
o.send_mail(
|
||||
email_subject, email_template, email_context,
|
||||
'pretix.event.order.email.download_reminder_sent',
|
||||
attach_tickets=True, position=p
|
||||
)
|
||||
except SendMailException:
|
||||
logger.exception('Reminder email could not be sent to attendee')
|
||||
with language(o.locale):
|
||||
o.download_reminder_sent = True
|
||||
o.save(update_fields=['download_reminder_sent'])
|
||||
email_template = event.settings.mail_text_download_reminder
|
||||
email_context = get_email_context(event=event, order=o)
|
||||
email_subject = _('Your ticket is ready for download: %(code)s') % {'code': o.code}
|
||||
try:
|
||||
o.send_mail(
|
||||
email_subject, email_template, email_context,
|
||||
'pretix.event.order.email.download_reminder_sent',
|
||||
attach_tickets=True
|
||||
)
|
||||
except SendMailException:
|
||||
logger.exception('Reminder email could not be sent')
|
||||
|
||||
if event.settings.mail_send_download_reminder_attendee:
|
||||
for p in o.positions.all():
|
||||
if p.subevent_id:
|
||||
reminder_date = (p.subevent.date_from - timedelta(days=days)).replace(
|
||||
hour=0, minute=0, second=0, microsecond=0
|
||||
)
|
||||
if now() < reminder_date:
|
||||
continue
|
||||
if p.addon_to_id is None and p.attendee_email and p.attendee_email != o.email:
|
||||
email_template = event.settings.mail_text_download_reminder_attendee
|
||||
email_context = get_email_context(event=event, order=o, position=p)
|
||||
try:
|
||||
o.send_mail(
|
||||
email_subject, email_template, email_context,
|
||||
'pretix.event.order.email.download_reminder_sent',
|
||||
attach_tickets=True, position=p
|
||||
)
|
||||
except SendMailException:
|
||||
logger.exception('Reminder email could not be sent to attendee')
|
||||
|
||||
|
||||
def notify_user_changed_order(order, user=None, auth=None):
|
||||
@@ -1183,20 +1235,49 @@ class OrderChangeManager:
|
||||
self.order.status = Order.STATUS_PAID
|
||||
self.order.save()
|
||||
elif self.open_payment:
|
||||
self.open_payment.state = OrderPayment.PAYMENT_STATE_CANCELED
|
||||
self.open_payment.save()
|
||||
self.order.log_action('pretix.event.order.payment.canceled', {
|
||||
'local_id': self.open_payment.local_id,
|
||||
'provider': self.open_payment.provider,
|
||||
}, user=self.user, auth=self.auth)
|
||||
try:
|
||||
with transaction.atomic():
|
||||
self.open_payment.payment_provider.cancel_payment(self.open_payment)
|
||||
self.order.log_action(
|
||||
'pretix.event.order.payment.canceled',
|
||||
{
|
||||
'local_id': self.open_payment.local_id,
|
||||
'provider': self.open_payment.provider,
|
||||
},
|
||||
user=self.user,
|
||||
auth=self.auth
|
||||
)
|
||||
except PaymentException as e:
|
||||
self.order.log_action(
|
||||
'pretix.event.order.payment.canceled.failed',
|
||||
{
|
||||
'local_id': self.open_payment.local_id,
|
||||
'provider': self.open_payment.provider,
|
||||
'error': str(e)
|
||||
},
|
||||
user=self.user,
|
||||
auth=self.auth
|
||||
)
|
||||
elif self.order.status in (Order.STATUS_PENDING, Order.STATUS_EXPIRED) and self._totaldiff > 0:
|
||||
if self.open_payment:
|
||||
self.open_payment.state = OrderPayment.PAYMENT_STATE_CANCELED
|
||||
self.open_payment.save()
|
||||
self.order.log_action('pretix.event.order.payment.canceled', {
|
||||
'local_id': self.open_payment.local_id,
|
||||
'provider': self.open_payment.provider,
|
||||
}, user=self.user, auth=self.auth)
|
||||
try:
|
||||
with transaction.atomic():
|
||||
self.open_payment.payment_provider.cancel_payment(self.open_payment)
|
||||
self.order.log_action('pretix.event.order.payment.canceled', {
|
||||
'local_id': self.open_payment.local_id,
|
||||
'provider': self.open_payment.provider,
|
||||
}, user=self.user, auth=self.auth)
|
||||
except PaymentException as e:
|
||||
self.order.log_action(
|
||||
'pretix.event.order.payment.canceled.failed',
|
||||
{
|
||||
'local_id': self.open_payment.local_id,
|
||||
'provider': self.open_payment.provider,
|
||||
'error': str(e)
|
||||
},
|
||||
user=self.user,
|
||||
auth=self.auth,
|
||||
)
|
||||
|
||||
def _check_paid_to_free(self):
|
||||
if self.order.total == 0 and (self._totaldiff < 0 or (self.split_order and self.split_order.total > 0)) and not self.order.require_approval:
|
||||
@@ -1726,8 +1807,22 @@ def change_payment_provider(order: Order, payment_provider, amount=None, new_pay
|
||||
|
||||
if open_payment and open_payment.state in (OrderPayment.PAYMENT_STATE_PENDING,
|
||||
OrderPayment.PAYMENT_STATE_CREATED):
|
||||
open_payment.state = OrderPayment.PAYMENT_STATE_CANCELED
|
||||
open_payment.save(update_fields=['state'])
|
||||
try:
|
||||
with transaction.atomic():
|
||||
open_payment.payment_provider.cancel_payment(open_payment)
|
||||
order.log_action('pretix.event.order.payment.canceled', {
|
||||
'local_id': open_payment.local_id,
|
||||
'provider': open_payment.provider,
|
||||
})
|
||||
except PaymentException as e:
|
||||
order.log_action(
|
||||
'pretix.event.order.payment.canceled.failed',
|
||||
{
|
||||
'local_id': open_payment.local_id,
|
||||
'provider': open_payment.provider,
|
||||
'error': str(e)
|
||||
},
|
||||
)
|
||||
|
||||
order.total = (order.positions.aggregate(sum=Sum('price'))['sum'] or 0) + (order.fees.aggregate(sum=Sum('value'))['sum'] or 0)
|
||||
order.save(update_fields=['total'])
|
||||
@@ -1753,7 +1848,7 @@ def change_payment_provider(order: Order, payment_provider, amount=None, new_pay
|
||||
|
||||
if recreate_invoices:
|
||||
i = order.invoices.filter(is_cancellation=False).last()
|
||||
if i and order.total != oldtotal:
|
||||
if i and order.total != oldtotal and not i.canceled:
|
||||
generate_cancellation(i)
|
||||
generate_invoice(order)
|
||||
|
||||
|
||||
@@ -1,32 +1,46 @@
|
||||
from django.db.models import Count
|
||||
from django.db.models import Count, Q
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from pretix.base.i18n import LazyLocaleException
|
||||
from pretix.base.models import CartPosition, Seat
|
||||
|
||||
|
||||
class SeatProtected(Exception):
|
||||
pass
|
||||
class SeatProtected(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 validate_plan_change(event, subevent, plan):
|
||||
current_taken_seats = set(
|
||||
event.seats.select_related('product')
|
||||
.annotate(has_op=Count('orderposition'))
|
||||
.filter(subevent=subevent, has_op=True)
|
||||
.values_list('seat_guid', flat=True)
|
||||
event.seats.select_related('product').annotate(
|
||||
has_op=Count('orderposition')
|
||||
).annotate(has_v=Count('vouchers')).filter(
|
||||
subevent=subevent,
|
||||
).filter(
|
||||
Q(has_v=True) | Q(has_op=True)
|
||||
).values_list('seat_guid', flat=True)
|
||||
)
|
||||
new_seats = {
|
||||
ss.guid for ss in plan.iter_all_seats()
|
||||
} if plan else set()
|
||||
leftovers = list(current_taken_seats - new_seats)
|
||||
if leftovers:
|
||||
raise SeatProtected(_('You can not change the plan since seat "{}" is not present in the new plan and is '
|
||||
'already sold.').format(leftovers[0]))
|
||||
raise SeatProtected(_('You can not change the plan since seat "%s" is not present in the new plan and is '
|
||||
'already sold.'), leftovers[0])
|
||||
|
||||
|
||||
def generate_seats(event, subevent, plan, mapping):
|
||||
current_seats = {}
|
||||
for s in event.seats.select_related('product').annotate(has_op=Count('orderposition')).filter(subevent=subevent):
|
||||
for s in event.seats.select_related('product').annotate(
|
||||
has_op=Count('orderposition'), has_v=Count('vouchers')
|
||||
).filter(subevent=subevent):
|
||||
if s.seat_guid in current_seats:
|
||||
s.delete() # Duplicates should not exist
|
||||
else:
|
||||
@@ -50,6 +64,7 @@ def generate_seats(event, subevent, plan, mapping):
|
||||
update(seat, 'row_name', ss.row),
|
||||
update(seat, 'seat_number', ss.number),
|
||||
update(seat, 'zone_name', ss.zone),
|
||||
update(seat, 'sorting_rank', ss.sorting_rank),
|
||||
])
|
||||
if updated:
|
||||
seat.save()
|
||||
@@ -62,13 +77,17 @@ def generate_seats(event, subevent, plan, mapping):
|
||||
row_name=ss.row,
|
||||
seat_number=ss.number,
|
||||
zone_name=ss.zone,
|
||||
sorting_rank=ss.sorting_rank,
|
||||
product=p,
|
||||
))
|
||||
|
||||
for s in current_seats.values():
|
||||
if s.has_op:
|
||||
raise SeatProtected(_('You can not change the plan since seat "{}" is not present in the new plan and is '
|
||||
'already sold.').format(s.name))
|
||||
raise SeatProtected(_('You can not change the plan since seat "%s" is not present in the new plan and is '
|
||||
'already sold.', s.name))
|
||||
if s.has_v:
|
||||
raise SeatProtected(_('You can not change the plan since seat "%s" is not present in the new plan and is '
|
||||
'already used in a voucher.', s.name))
|
||||
|
||||
Seat.objects.bulk_create(create_seats)
|
||||
CartPosition.objects.filter(seat__in=[s.pk for s in current_seats.values()]).delete()
|
||||
|
||||
@@ -89,8 +89,9 @@ def preview(event: int, provider: str):
|
||||
scheme = PERSON_NAME_SCHEMES[event.settings.name_scheme]
|
||||
sample = {k: str(v) for k, v in scheme['sample'].items()}
|
||||
p = order.positions.create(item=item, attendee_name_parts=sample, price=item.default_price)
|
||||
order.positions.create(item=item2, attendee_name_parts=sample, price=item.default_price, addon_to=p)
|
||||
order.positions.create(item=item2, attendee_name_parts=sample, price=item.default_price, addon_to=p)
|
||||
s = event.subevents.first()
|
||||
order.positions.create(item=item2, attendee_name_parts=sample, price=item.default_price, addon_to=p, subevent=s)
|
||||
order.positions.create(item=item2, attendee_name_parts=sample, price=item.default_price, addon_to=p, subevent=s)
|
||||
|
||||
InvoiceAddress.objects.create(order=order, name_parts=sample, company=_("Sample company"))
|
||||
|
||||
|
||||
@@ -85,6 +85,10 @@ DEFAULTS = {
|
||||
'default': 'True',
|
||||
'type': bool,
|
||||
},
|
||||
'invoice_include_expire_date': {
|
||||
'default': 'False',
|
||||
'type': bool,
|
||||
},
|
||||
'invoice_numbers_consecutive': {
|
||||
'default': 'True',
|
||||
'type': bool,
|
||||
@@ -137,6 +141,10 @@ DEFAULTS = {
|
||||
'default': 'True',
|
||||
'type': bool
|
||||
},
|
||||
'payment_resellers__restrict_to_sales_channels': {
|
||||
'default': ['resellers'],
|
||||
'type': list
|
||||
},
|
||||
'payment_term_accept_late': {
|
||||
'default': 'True',
|
||||
'type': bool
|
||||
@@ -652,6 +660,10 @@ Your {event} team"""))
|
||||
'default': None,
|
||||
'type': File
|
||||
},
|
||||
'og_image': {
|
||||
'default': None,
|
||||
'type': File
|
||||
},
|
||||
'invoice_logo_image': {
|
||||
'default': None,
|
||||
'type': File
|
||||
@@ -664,10 +676,21 @@ Your {event} team"""))
|
||||
'default': '',
|
||||
'type': LazyI18nString
|
||||
},
|
||||
'checkout_email_helptext': {
|
||||
'default': LazyI18nString.from_gettext(ugettext_noop(
|
||||
'Make sure to enter a valid email address. We will send you an order '
|
||||
'confirmation including a link that you need to access your order later.'
|
||||
)),
|
||||
'type': LazyI18nString
|
||||
},
|
||||
'organizer_info_text': {
|
||||
'default': '',
|
||||
'type': LazyI18nString
|
||||
},
|
||||
'event_team_provisioning': {
|
||||
'default': 'True',
|
||||
'type': bool
|
||||
},
|
||||
'update_check_ack': {
|
||||
'default': 'False',
|
||||
'type': bool
|
||||
@@ -723,6 +746,10 @@ Your {event} team"""))
|
||||
'name_scheme': {
|
||||
'default': 'full',
|
||||
'type': str
|
||||
},
|
||||
'giftcard_length': {
|
||||
'default': settings.ENTROPY['giftcard_secret'],
|
||||
'type': int
|
||||
}
|
||||
}
|
||||
PERSON_NAME_TITLE_GROUPS = OrderedDict([
|
||||
|
||||
@@ -622,3 +622,11 @@ order_split = EventPluginSignal(
|
||||
This signal is sent out when an order is split into two orders and allows you to copy related models
|
||||
to the new order. You will be passed the old order as ``original`` and the new order as ``split_order``.
|
||||
"""
|
||||
|
||||
invoice_line_text = EventPluginSignal(
|
||||
providing_args=["position"]
|
||||
)
|
||||
"""
|
||||
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``.
|
||||
"""
|
||||
|
||||
@@ -158,7 +158,7 @@
|
||||
<table class="layout" width="600" border="0" cellspacing="0">
|
||||
<!--[if !mso]><!-- -->
|
||||
<tr>
|
||||
<td>
|
||||
<td style="line-height: 0">
|
||||
<img class="wide" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAlgAAAA8CAAAAACf95tlAAAAAXNCSVQI5gpbmQAAAAlwSFlzAAAOxAAADsQBlSsOGwAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAG/SURBVHja7dvRboMwDIXhvf/DLiQQAwkku9+qDgq2hPyfN6j1qTlx06/uMunbLMnnhL98fuzRDtYILEeZ7GBNwAIWsIB1LdkOVgaWo4gdLAGWo6x2sFZgOUq1g1WB5SjNDlYDlqcEK1dDB5anmK3eE7C4FnIpBNbVFLo7sB7d3huwKFlULGA9pWQJsJxls4G1ActbooWr2IHlLbMFrBlY7rJbwNqBxb2QZ8nAuiUGO9ICLOo71R1YN0X9td8KLJ8ZeDEDrAd+Za3A4mLIz4TAujGqv+tUYPmN4v8LcweW3zS1t++hActzCrtRYD3pMJQOLOeJ7NyBpZFdoWaFDVjuJ6BRswpTBZbCAn5hpsDq/fbHpDMTBZbC1TAzT2ApyMIVsDROQ2GWwFJo8PR2YP3eOtywzwrsGYD1J9vlHXzcmSKw7q/wU2OEwHpdtALHILA00jJfV8DSaVofvYOPlckB658sp/8VNrBkANahqnXqfhhXJgasgymHD8REZwfWmezzga+tQdhcAet0qry1FYV3osD6dP1QJL3YbYUkhfUCsK6einWRPI0pxjROWZbK+QcsAiwCLEKARYBFgEXIu/wAYbjtwujw8KwAAAAASUVORK5CYII="
|
||||
style="max-height: 60px;">
|
||||
</td>
|
||||
@@ -188,7 +188,7 @@
|
||||
{% endblock %}
|
||||
<!--[if !mso]><!-- -->
|
||||
<tr>
|
||||
<td>
|
||||
<td style="line-height: 0">
|
||||
<br>
|
||||
<img class="wide" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAlgAAAA8CAYAAAC6nMS5AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAOxAAADsQBlSsOGwAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAPnSURBVHic7d3dbuJIEAbQsg2Ecd7/TQeDf3svVuFmdjJLxsGm+xwJKXcpqS76U3VTVCmlFAAArKbeugAAgNwIWAAAKxOwAABWJmABAKxMwAIAWNlh6wIAIiKWZYllWWKe5/vfEREppfsnIqKqqvsnIqKu66jrOpqmuf8NsDUBC3i6lFJM0xTjOMY0TbEsS6y1MaaqqqjrOg6HQxyPxzgcDvcwBvAslT1YwDN8BKpxHGOe56f+76Zp4ng83gMXwHcTsIBvsyxLDMMQfd/fr/y2Vtd1nE6nOJ1O0TTN1uUAmRKwgNV9hKppmrYu5VOHwyHO53Mcj8etSwEyI2ABqxmGIW6329OvAP9WXddxPp/j7e1t61KATAhYwF/r+z5ut9turgG/StAC1iJgAV82z3N0Xbf7q8BHNU0Tbdt6EA98mYAFPCylFNfrNfq+37qUb3U6naJtW2segIcJWMBDhmGIrutW21u1d1VVRdu2cTqdti4FeCECFvC/lDK1+h3TLOARAhbwR/M8x+VyeblvB66taZp4f3+3Pwv4IwEL+NQ4jnG5XIq5EvyTqqri/f3d7izgUwIW8FvDMMTlctm6jF1q29Y6B+C3BCzgP91ut7her1uXsWvn8zl+/PixdRnADglYwC+6riv2Mfuj3t7eom3brcsAdqbeugBgX4Srx/R9H13XbV0GsDMCFnB3u92Eqy/4+KkggA8CFhAR/z5o9+bq60reEQb8SsAC7qsY+Dtd18U4jluXAeyAgAWFW5ZFuFqRhaxAhIAFxfv586cloitKKVnMCghYULKu60xbvsE8z96zQeEELCjUOI4eZX+jvu+9x4KCCVhQoI9rLL6Xq0Iol4AFBbperw7+J0gpuSqEQglYUJh5nl0NPlHf9zFN09ZlAE8mYEFh/KzL85liQXkELCiIaco2pmmKYRi2LgN4IgELCuL38rZjigVlEbCgEMMwxLIsW5dRrGVZTLGgIAIWFML0ant6AOUQsKAA4zja2L4D8zxbPgqFELCgACYn+6EXUAYBCzK3LItvDu7INE3ewkEBBCzInIfV+6MnkD8BCzLnMN8fPYH8CViQsXmePW7fIX2B/AlYkDGTkv3SG8ibgAUZ87h9v/QG8iZgQaZSSg7xHZumKVJKW5cBfBMBCzIlXO2fHkG+BCzIlMN7//QI8iVgQaYc3vunR5AvAQsyZQ3A/tnoDvkSsCBDKSUPqF/Asiz6BJkSsCBDplevQ68gTwIWZMjV0+vQK8iTgAUZMhV5HXoFeRKwIEPe9bwOvYI8CViQIYf269AryJOABQCwMgELMmQq8jr0CvIkYEGGHNqvQ68gT/8AETAn3pyLgvsAAAAASUVORK5CYII="
|
||||
style="max-height: 60px;">
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<!--[if !mso]><!-- -->
|
||||
<tr>
|
||||
<td>
|
||||
<img class="wide" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAlgAAAAoBAMAAADQ9ZkHAAAAA3NCSVQICAjb4U/gAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAC1QTFRF7u7u7+/v8PDw8fHx8vLy9PT09fX19/f3+Pj4+fn5+vr6/Pz8/f39/v7+////BLnnfgAAAKJJREFUaN7t1cGtgUEARtFfQkREDypQihpUoIQXpahADUqRiIgINdjehcXbSTjf8s5mchYzw9P+vQEBLFiwYMGChQAWLFiwYMFCAAsWLFhfipX9pe/S1+nH9EX6OX2Wfk2fpN/TR73QMgeH9E36Nn2fvko/pc/TL+nT9Fv6OP0xvB8sWLA+g+XZ9hvCggULFiwEsGDBggULFgJYsGDBgvXzewGTOlWA3NB0eQAAAABJRU5ErkJggg=="
|
||||
style="max-height: 40px;">
|
||||
<div style="line-height: 18px;height: 18px;"> </div>
|
||||
<img class="wide" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAlgAAAAEBAMAAACgm1xKAAABhGlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw0AcxV/TSkUrDu0g4pChOlkQleKoVShChVArtOpgcukXNGlIUlwcBdeCgx+LVQcXZ10dXAVB8APExdVJ0UVK/F9aaBHjwXE/3t173L0DhEaFaVZgAtB020wnE2I2tyoGXxFCAP0IIy4zy5iTpBQ8x9c9fHy9i/Es73N/jgE1bzHAJxLPMsO0iTeI45u2wXmfOMJKskp8Tjxu0gWJH7mutPiNc9FlgWdGzEx6njhCLBa7WOliVjI14mniqKrplC9kW6xy3uKsVWqsfU/+wlBeX1nmOs0RJLGIJUgQoaCGMiqwEaNVJ8VCmvYTHv5h1y+RSyFXGYwcC6hCg+z6wf/gd7dWYWqylRRKAD0vjvMxCgR3gWbdcb6PHad5AvifgSu94682gJlP0usdLXoEDG4DF9cdTdkDLneAoSdDNmVX8tMUCgXg/Yy+KQeEb4G+tVZv7X2cPgAZ6ip1AxwcAmNFyl73eHdvd2//nmn39wNhNnKgJpT5BQAAAC1QTFRF7u7u7+/v8PDw8fHx8vLy9PT09fX19/f3+Pj4+fn5+vr6/Pz8/f39/v7+////BLnnfgAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAAd0SU1FB+MMBAsUDD3bzUUAAABhSURBVDjLY2BAgLp3CNCAJO6HJH4ASZwXSfwxkjgnkvhzJHFWJPHXSOKMSOLvFJAk1iGJJyCJ5yGJL0AS10MSf4Akzo0k/hRJnB1J/CWSOAuS+FsG7GA0sEYDazSwBiSwAPzzGpfLqBMlAAAAAElFTkSuQmCC"
|
||||
style="max-height: 4px;">
|
||||
<div style="line-height: 18px;height: 18px;"> </div>
|
||||
</td>
|
||||
</tr>
|
||||
<!--<![endif]-->
|
||||
|
||||
@@ -22,7 +22,7 @@ from pytz import common_timezones, timezone
|
||||
from pretix.base.channels import get_all_sales_channels
|
||||
from pretix.base.email import get_available_placeholders
|
||||
from pretix.base.forms import I18nModelForm, PlaceholderValidator, SettingsForm
|
||||
from pretix.base.models import Event, Organizer, TaxRule
|
||||
from pretix.base.models import Event, Organizer, TaxRule, Team
|
||||
from pretix.base.models.event import EventMetaValue, SubEvent
|
||||
from pretix.base.reldate import RelativeDateField, RelativeDateTimeField
|
||||
from pretix.base.settings import PERSON_NAME_SCHEMES, PERSON_NAME_TITLE_GROUPS
|
||||
@@ -101,6 +101,16 @@ class EventWizardBasicsForm(I18nModelForm):
|
||||
required=False
|
||||
)
|
||||
|
||||
team = forms.ModelChoiceField(
|
||||
label=_("Grant access to team"),
|
||||
help_text=_("You are allowed to create events under this organizer, however you do not have permission "
|
||||
"to edit all events under this organizer. Please select one of your existing teams that will"
|
||||
" be granted access to this event."),
|
||||
queryset=Team.objects.none(),
|
||||
required=False,
|
||||
empty_label=_('Create a new team for this event with me as the only member')
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Event
|
||||
fields = [
|
||||
@@ -133,7 +143,7 @@ class EventWizardBasicsForm(I18nModelForm):
|
||||
self.organizer = kwargs.pop('organizer')
|
||||
self.locales = kwargs.get('locales')
|
||||
self.has_subevents = kwargs.pop('has_subevents')
|
||||
kwargs.pop('user')
|
||||
self.user = kwargs.pop('user')
|
||||
kwargs.pop('session')
|
||||
super().__init__(*args, **kwargs)
|
||||
self.initial['timezone'] = get_current_timezone_name()
|
||||
@@ -147,6 +157,15 @@ class EventWizardBasicsForm(I18nModelForm):
|
||||
del self.fields['presale_start']
|
||||
del self.fields['presale_end']
|
||||
|
||||
if self.has_control_rights(self.user, self.organizer):
|
||||
del self.fields['team']
|
||||
else:
|
||||
self.fields['team'].queryset = self.user.teams.filter(organizer=self.organizer)
|
||||
if not self.organizer.settings.get("event_team_provisioning", True, as_type=bool):
|
||||
self.fields['team'].required = True
|
||||
self.fields['team'].empty_label = None
|
||||
self.fields['team'].initial = 0
|
||||
|
||||
def clean(self):
|
||||
data = super().clean()
|
||||
if data.get('locale') not in self.locales:
|
||||
@@ -179,6 +198,13 @@ class EventWizardBasicsForm(I18nModelForm):
|
||||
)
|
||||
return slug
|
||||
|
||||
@staticmethod
|
||||
def has_control_rights(user, organizer):
|
||||
return user.teams.filter(
|
||||
organizer=organizer, all_events=True, can_change_event_settings=True, can_change_items=True,
|
||||
can_change_orders=True, can_change_vouchers=True
|
||||
).exists()
|
||||
|
||||
|
||||
class EventChoiceMixin:
|
||||
def label_from_instance(self, obj):
|
||||
@@ -488,11 +514,26 @@ class EventSettingsForm(SettingsForm):
|
||||
help_text=_('If you provide a logo image, we will by default not show your events name and date '
|
||||
'in the page header. We will show your logo with a maximal height of 120 pixels.')
|
||||
)
|
||||
og_image = ExtFileField(
|
||||
label=_('Social media image'),
|
||||
ext_whitelist=(".png", ".jpg", ".gif", ".jpeg"),
|
||||
required=False,
|
||||
help_text=_('This picture will be used as a preview if you post links to your ticket shop on social media. '
|
||||
'Facebook advises to use a picture size of 1200 x 630 pixels, however some platforms like '
|
||||
'WhatsApp and Reddit only show a square 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.')
|
||||
)
|
||||
frontpage_text = I18nFormField(
|
||||
label=_("Frontpage text"),
|
||||
required=False,
|
||||
widget=I18nTextarea
|
||||
)
|
||||
checkout_email_helptext = I18nFormField(
|
||||
label=_("Help text of the email field"),
|
||||
required=False,
|
||||
widget_kwargs={'attrs': {'rows': '2'}},
|
||||
widget=I18nTextarea
|
||||
)
|
||||
presale_has_ended_text = I18nFormField(
|
||||
label=_("End of presale text"),
|
||||
required=False,
|
||||
@@ -719,7 +760,7 @@ class ProviderForm(SettingsForm):
|
||||
v.set_event(self.obj)
|
||||
|
||||
if hasattr(v, '_as_type'):
|
||||
self.initial[k] = self.obj.settings.get(k, as_type=v._as_type)
|
||||
self.initial[k] = self.obj.settings.get(k, as_type=v._as_type, default=v.initial)
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
@@ -829,6 +870,11 @@ class InvoiceSettingsForm(SettingsForm):
|
||||
label=_("Show attendee names on invoices"),
|
||||
required=False
|
||||
)
|
||||
invoice_include_expire_date = forms.BooleanField(
|
||||
label=_("Show expiration date of order"),
|
||||
help_text=_("The expiration date will not be shown if the invoice is generated after the order is paid."),
|
||||
required=False
|
||||
)
|
||||
invoice_email_attachment = forms.BooleanField(
|
||||
label=_("Attach invoices to emails"),
|
||||
help_text=_("If invoices are automatically generated for all orders, they will be attached to the order "
|
||||
|
||||
@@ -18,6 +18,7 @@ from pretix.base.models import (
|
||||
)
|
||||
from pretix.base.signals import register_payment_providers
|
||||
from pretix.control.forms.widgets import Select2
|
||||
from pretix.control.signals import order_search_filter_q
|
||||
from pretix.helpers.database import FixedOrderBy, rolledback_transaction
|
||||
from pretix.helpers.i18n import i18ncomp
|
||||
|
||||
@@ -139,7 +140,7 @@ class OrderFilterForm(FilterForm):
|
||||
)
|
||||
).values('id')
|
||||
|
||||
qs = qs.annotate(has_pos=Exists(matching_positions)).filter(
|
||||
mainq = (
|
||||
code
|
||||
| Q(email__icontains=u)
|
||||
| Q(invoice_address__name_cached__icontains=u)
|
||||
@@ -148,6 +149,11 @@ class OrderFilterForm(FilterForm):
|
||||
| Q(comment__icontains=u)
|
||||
| Q(has_pos=True)
|
||||
)
|
||||
for recv, q in order_search_filter_q.send(sender=getattr(self, 'event', None), query=u):
|
||||
mainq = mainq | q
|
||||
qs = qs.annotate(has_pos=Exists(matching_positions)).filter(
|
||||
mainq
|
||||
)
|
||||
|
||||
if fdata.get('status'):
|
||||
s = fdata.get('status')
|
||||
@@ -289,7 +295,7 @@ class EventOrderFilterForm(OrderFilterForm):
|
||||
answers = QuestionAnswer.objects.filter(
|
||||
question_id=q.pk,
|
||||
orderposition__order_id=OuterRef('pk'),
|
||||
answer__iexact=fdata.get('answer')
|
||||
answer__exact=fdata.get('answer')
|
||||
)
|
||||
qs = qs.annotate(has_answer=Exists(answers)).filter(has_answer=True)
|
||||
|
||||
@@ -705,6 +711,8 @@ class CheckInFilterForm(FilterForm):
|
||||
'-timestamp': (FixedOrderBy(F('last_checked_in'), nulls_last=True, descending=True), '-order__code'),
|
||||
'item': ('item__name', 'variation__value', 'order__code'),
|
||||
'-item': ('-item__name', '-variation__value', '-order__code'),
|
||||
'seat': ('seat__sorting_rank', 'seat__guid'),
|
||||
'-seat': ('-seat__sorting_rank', '-seat__guid'),
|
||||
'name': {'_order': F('display_name').asc(nulls_first=True),
|
||||
'display_name': Coalesce('attendee_name_cached', 'addon_to__attendee_name_cached')},
|
||||
'-name': {'_order': F('display_name').desc(nulls_last=True),
|
||||
@@ -843,6 +851,32 @@ class UserFilterForm(FilterForm):
|
||||
|
||||
class VoucherFilterForm(FilterForm):
|
||||
orders = {
|
||||
'code': 'code',
|
||||
'-code': '-code',
|
||||
'redeemed': 'redeemed',
|
||||
'-redeemed': '-redeemed',
|
||||
'valid_until': 'valid_until',
|
||||
'-valid_until': '-valid_until',
|
||||
'tag': 'tag',
|
||||
'-tag': '-tag',
|
||||
'item': (
|
||||
'seat__sorting_rank',
|
||||
'item__category__position',
|
||||
'item__category',
|
||||
'item__position',
|
||||
'item__variation__position',
|
||||
'quota__name',
|
||||
),
|
||||
'subevent': 'subevent__date_from',
|
||||
'-subevent': '-subevent__date_from',
|
||||
'-item': (
|
||||
'-seat__sorting_rank',
|
||||
'-item__category__position',
|
||||
'-item__category',
|
||||
'-item__position',
|
||||
'-item__variation__position',
|
||||
'-quota__name',
|
||||
)
|
||||
}
|
||||
status = forms.ChoiceField(
|
||||
label=_('Status'),
|
||||
@@ -979,7 +1013,15 @@ class VoucherFilterForm(FilterForm):
|
||||
qs = qs.filter(subevent_id=fdata.get('subevent').pk)
|
||||
|
||||
if fdata.get('ordering'):
|
||||
qs = qs.order_by(self.get_order_by())
|
||||
ob = self.orders[fdata.get('ordering')]
|
||||
if isinstance(ob, dict):
|
||||
ob = dict(ob)
|
||||
o = ob.pop('_order')
|
||||
qs = qs.annotate(**ob).order_by(o)
|
||||
elif isinstance(ob, (list, tuple)):
|
||||
qs = qs.order_by(*ob)
|
||||
else:
|
||||
qs = qs.order_by(ob)
|
||||
|
||||
return qs
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from decimal import Decimal
|
||||
|
||||
from django import forms
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db.models import Max
|
||||
@@ -77,6 +79,14 @@ class QuestionForm(I18nModelForm):
|
||||
dep = dep.dependency_question
|
||||
return val
|
||||
|
||||
def clean_ask_during_checkin(self):
|
||||
val = self.cleaned_data.get('ask_during_checkin')
|
||||
|
||||
if val and self.cleaned_data.get('type') in Question.ASK_DURING_CHECKIN_UNSUPPORTED:
|
||||
raise ValidationError(_('This type of question cannot be asked during check-in.'))
|
||||
|
||||
return val
|
||||
|
||||
def clean(self):
|
||||
d = super().clean()
|
||||
if d.get('dependency_question') and not d.get('dependency_values'):
|
||||
@@ -415,7 +425,7 @@ class ItemUpdateForm(I18nModelForm):
|
||||
'event': self.event.slug,
|
||||
'organizer': self.event.organizer.slug,
|
||||
}),
|
||||
'data-placeholder': _('Quota')
|
||||
'data-placeholder': _('Shown independently of other products')
|
||||
}
|
||||
)
|
||||
self.fields['hidden_if_available'].widget.choices = self.fields['hidden_if_available'].choices
|
||||
@@ -679,6 +689,10 @@ class ItemBundleForm(I18nModelForm):
|
||||
|
||||
def clean(self):
|
||||
d = super().clean()
|
||||
if not self.cleaned_data['designated_price']:
|
||||
d['designated_price'] = Decimal('0.00')
|
||||
self.instance.designated_price = Decimal('0.00')
|
||||
|
||||
if 'itemvar' in self.cleaned_data:
|
||||
if '-' in self.cleaned_data['itemvar']:
|
||||
itemid, varid = self.cleaned_data['itemvar'].split('-')
|
||||
|
||||
@@ -211,6 +211,15 @@ class OrganizerSettingsForm(SettingsForm):
|
||||
widget=I18nTextarea,
|
||||
help_text=_('Not displayed anywhere by default, but if you want to, you can use this e.g. in ticket templates.')
|
||||
)
|
||||
|
||||
event_team_provisioning = forms.BooleanField(
|
||||
label=_('Allow creating a new team during event creation'),
|
||||
help_text=_('Users that do not have access to all events under this organizer, must select one of their teams '
|
||||
'to have access to the created event. This setting allows users to create an event-specified team'
|
||||
' on-the-fly, even when they do not have \"Can change teams and permissions\" permission.'),
|
||||
required=False,
|
||||
)
|
||||
|
||||
primary_color = forms.CharField(
|
||||
label=_("Primary color"),
|
||||
required=False,
|
||||
@@ -293,6 +302,12 @@ class OrganizerSettingsForm(SettingsForm):
|
||||
help_text=_('If you provide a favicon, we will show it instead of the default pretix icon. '
|
||||
'We recommend a size of at least 200x200px to accomodate most devices.')
|
||||
)
|
||||
giftcard_length = forms.IntegerField(
|
||||
label=_('Length of gift card codes'),
|
||||
help_text=_('The system generates by default {}-character long gift card codes. However, if a different length '
|
||||
'is required, it can be set here.'.format(settings.ENTROPY['giftcard_secret'])),
|
||||
required=False
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
@@ -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'
|
||||
'comment', 'max_usages', 'price_mode', 'subevent', 'show_hidden_items',
|
||||
]
|
||||
field_classes = {
|
||||
'valid_until': SplitDateTimeField,
|
||||
@@ -109,16 +109,26 @@ class VoucherForm(I18nModelForm):
|
||||
'event': instance.event.slug,
|
||||
'organizer': instance.event.organizer.slug,
|
||||
}),
|
||||
'data-placeholder': ''
|
||||
'data-placeholder': _('All products')
|
||||
}
|
||||
)
|
||||
self.fields['itemvar'].required = False
|
||||
self.fields['itemvar'].widget.choices = self.fields['itemvar'].choices
|
||||
self.fields['itemvar'].required = True
|
||||
|
||||
if self.instance.event.seating_plan or self.instance.event.subevents.filter(seating_plan__isnull=False).exists():
|
||||
self.fields['seat'] = forms.CharField(
|
||||
label=_("Specific seat ID"),
|
||||
max_length=255,
|
||||
required=False,
|
||||
widget=forms.TextInput(attrs={'data-seat-guid-field': '1'}),
|
||||
initial=self.instance.seat.seat_guid if self.instance.seat else '',
|
||||
help_text=str(self.instance.seat) if self.instance.seat else '',
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
data = super().clean()
|
||||
|
||||
if not self._errors:
|
||||
if not self._errors and self.data.get('itemvar'):
|
||||
try:
|
||||
itemid = quotaid = None
|
||||
iv = self.data.get('itemvar', '')
|
||||
@@ -152,7 +162,9 @@ class VoucherForm(I18nModelForm):
|
||||
|
||||
Voucher.clean_item_properties(
|
||||
data, self.instance.event,
|
||||
self.instance.quota, self.instance.item, self.instance.variation
|
||||
self.instance.quota, self.instance.item, self.instance.variation,
|
||||
seats_given=data.get('seat') or data.get('seats'),
|
||||
block_quota=data.get('block_quota')
|
||||
)
|
||||
if not self.instance.show_hidden_items and (
|
||||
(self.instance.quota and all(i.hide_without_voucher for i in self.instance.quota.items.all()))
|
||||
@@ -179,6 +191,11 @@ class VoucherForm(I18nModelForm):
|
||||
self.instance.quota, self.instance.item, self.instance.variation
|
||||
)
|
||||
Voucher.clean_voucher_code(data, self.instance.event, self.instance.pk)
|
||||
if 'seat' in self.fields and data.get('seat'):
|
||||
self.instance.seat = Voucher.clean_seat_id(
|
||||
data, self.instance.item, self.instance.quota, self.instance.event, self.instance.pk
|
||||
)
|
||||
self.instance.item = self.instance.seat.product
|
||||
|
||||
voucher_form_validation.send(sender=self.instance.event, form=self, data=data)
|
||||
|
||||
@@ -271,6 +288,13 @@ class VoucherBulkForm(VoucherForm):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._set_field_placeholders('send_subject', ['event', 'name'])
|
||||
self._set_field_placeholders('send_message', ['event', 'voucher_list', 'name'])
|
||||
if 'seat' in self.fields:
|
||||
self.fields['seats'] = forms.CharField(
|
||||
label=_("Specific seat IDs"),
|
||||
required=False,
|
||||
widget=forms.Textarea(attrs={'data-seat-guid-field': '1'}),
|
||||
initial=self.instance.seat.seat_guid if self.instance.seat else '',
|
||||
)
|
||||
|
||||
def clean_send_recipients(self):
|
||||
raw = self.cleaned_data['send_recipients']
|
||||
@@ -296,7 +320,7 @@ class VoucherBulkForm(VoucherForm):
|
||||
try:
|
||||
res.append(self.Recipient(
|
||||
name=row.get('name', ''),
|
||||
email=row['email'],
|
||||
email=row['email'].strip(),
|
||||
number=int(row.get('number', 1)),
|
||||
tag=row.get('tag', None)
|
||||
))
|
||||
@@ -309,7 +333,7 @@ class VoucherBulkForm(VoucherForm):
|
||||
except ValidationError as err:
|
||||
raise ValidationError(_('{value} is not a valid email address.').format(value=e.strip())) from err
|
||||
else:
|
||||
res.append(self.Recipient(email=e, number=1, tag=None, name=''))
|
||||
res.append(self.Recipient(email=e.strip(), number=1, tag=None, name=''))
|
||||
return res
|
||||
|
||||
def clean(self):
|
||||
@@ -331,6 +355,20 @@ class VoucherBulkForm(VoucherForm):
|
||||
if code_len != recp_len:
|
||||
raise ValidationError(_('You generated {codes} vouchers, but entered recipients for {recp} vouchers.').format(codes=code_len, recp=recp_len))
|
||||
|
||||
if data.get('seats'):
|
||||
seatids = [s.strip() for s in data.get('seats').strip().split("\n") if s]
|
||||
if len(seatids) != len(data.get('codes')):
|
||||
raise ValidationError(_('You need to specify as many seats as voucher codes.'))
|
||||
data['seats'] = []
|
||||
for s in seatids:
|
||||
data['seat'] = s
|
||||
data['seats'].append(Voucher.clean_seat_id(
|
||||
data, self.instance.item, self.instance.quota, self.instance.event, None
|
||||
))
|
||||
self.instance.seat = data['seats'][0] # Trick model-level validation
|
||||
else:
|
||||
data['seats'] = []
|
||||
|
||||
return data
|
||||
|
||||
def save(self, event, *args, **kwargs):
|
||||
@@ -339,6 +377,11 @@ class VoucherBulkForm(VoucherForm):
|
||||
obj = modelcopy(self.instance)
|
||||
obj.event = event
|
||||
obj.code = code
|
||||
try:
|
||||
obj.seat = self.cleaned_data['seats'].pop()
|
||||
obj.item = obj.seat.product
|
||||
except IndexError:
|
||||
pass
|
||||
data = dict(self.cleaned_data)
|
||||
data['code'] = code
|
||||
data['bulk'] = True
|
||||
|
||||
@@ -225,6 +225,7 @@ def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
|
||||
'pretix.event.order.email.resend': _('An email with a link to the order detail page has been resent to the user.'),
|
||||
'pretix.event.order.payment.confirmed': _('Payment {local_id} has been confirmed.'),
|
||||
'pretix.event.order.payment.canceled': _('Payment {local_id} has been canceled.'),
|
||||
'pretix.event.order.payment.canceled.failed': _('Cancelling payment {local_id} has failed.'),
|
||||
'pretix.event.order.payment.started': _('Payment {local_id} has been started.'),
|
||||
'pretix.event.order.payment.failed': _('Payment {local_id} has failed.'),
|
||||
'pretix.event.order.quotaexceeded': _('The order could not be marked as paid: {message}'),
|
||||
|
||||
@@ -301,3 +301,14 @@ oauth_application_registered = Signal(
|
||||
"""
|
||||
This signal will be called whenever a user registers a new OAuth application.
|
||||
"""
|
||||
|
||||
order_search_filter_q = Signal(
|
||||
providing_args=["query"]
|
||||
)
|
||||
"""
|
||||
This signal will be called whenever a free-text order search is performed. You are expected to return one
|
||||
Q object that will be OR-ed with existing search queries. As order search exists on a global level as well,
|
||||
this is not an Event signal and will be called even if your plugin is not active. ``sender`` will contain the
|
||||
event if the search is performed within an event, and ``None`` otherwise. The search query will be passed as
|
||||
``query``.
|
||||
"""
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
<script type="text/javascript" src="{% static "fileupload/jquery.ui.widget.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "fileupload/jquery.fileupload.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "lightbox/js/lightbox.min.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "are-you-sure/jquery.are-you-sure.js" %}"></script>
|
||||
{% endcompress %}
|
||||
{{ html_head|safe }}
|
||||
|
||||
|
||||
@@ -66,6 +66,10 @@
|
||||
<a href="?{% url_replace request 'ordering' 'code'%}"><i class="fa fa-caret-up"></i></a></th>
|
||||
<th>{% trans "Item" %} <a href="?{% url_replace request 'ordering' '-item'%}"><i class="fa fa-caret-down"></i></a>
|
||||
<a href="?{% url_replace request 'ordering' 'item'%}"><i class="fa fa-caret-up"></i></a></th>
|
||||
{% if seats %}
|
||||
<th>{% trans "Seat" %} <a href="?{% url_replace request 'ordering' '-seat'%}"><i class="fa fa-caret-down"></i></a>
|
||||
<a href="?{% url_replace request 'ordering' 'seat'%}"><i class="fa fa-caret-up"></i></a></th>
|
||||
{% endif %}
|
||||
<th>{% trans "Email" %} <a href="?{% url_replace request 'ordering' '-email'%}"><i class="fa fa-caret-down"></i></a>
|
||||
<a href="?{% url_replace request 'ordering' 'email'%}"><i class="fa fa-caret-up"></i></a></th>
|
||||
<th>{% trans "Name" %} <a href="?{% url_replace request 'ordering' '-name'%}"><i class="fa fa-caret-down"></i></a>
|
||||
@@ -95,6 +99,9 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ e.item }}{% if e.variation %} – {{ e.variation }}{% endif %}</td>
|
||||
{% if seats %}
|
||||
<td>{{ e.seat }}</td>
|
||||
{% endif %}
|
||||
<td>
|
||||
{% if e.addon_to and e.addon_to.attendee_email %}
|
||||
{{ e.addon_to.attendee_email }}
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
<legend>{% trans "Invoice customization" %}</legend>
|
||||
{% bootstrap_field form.invoice_renderer layout="control" %}
|
||||
{% bootstrap_field form.invoice_attendee_name layout="control" %}
|
||||
{% bootstrap_field form.invoice_include_expire_date layout="control" %}
|
||||
{% bootstrap_field form.invoice_introductory_text layout="control" %}
|
||||
{% bootstrap_field form.invoice_additional_text layout="control" %}
|
||||
{% bootstrap_field form.invoice_footer_text layout="control" %}
|
||||
|
||||
@@ -28,6 +28,12 @@
|
||||
</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="iconcol">
|
||||
{% for channel in provider.sales_channels %}
|
||||
<span class="fa fa-{{ channel.icon }} text-muted"
|
||||
data-toggle="tooltip" title="{% trans channel.verbose_name %}"></span>
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td class="text-right flip">
|
||||
<a href="{% url 'control:event.settings.payment.provider' event=request.event.slug organizer=request.organizer.slug provider=provider.identifier %}"
|
||||
class="btn btn-default">
|
||||
|
||||
@@ -92,10 +92,12 @@
|
||||
{% bootstrap_field sform.presale_has_ended_text layout="control" %}
|
||||
{% bootstrap_field sform.voucher_explanation_text layout="control" %}
|
||||
{% bootstrap_field sform.confirm_text layout="control" %}
|
||||
{% bootstrap_field sform.checkout_email_helptext layout="control" %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Shop design" %}</legend>
|
||||
{% bootstrap_field sform.logo_image layout="control" %}
|
||||
{% bootstrap_field sform.og_image layout="control" %}
|
||||
{% url "control:organizer.edit" organizer=request.organizer.slug as org_url %}
|
||||
{% propagated request.event org_url "primary_color" "primary_font" "theme_color_success" "theme_color_danger" %}
|
||||
{% bootstrap_field sform.primary_color layout="control" %}
|
||||
|
||||
@@ -76,4 +76,11 @@
|
||||
{% bootstrap_field form.presale_end layout="control" %}
|
||||
</fieldset>
|
||||
{% endif %}
|
||||
|
||||
{% if form.team %}
|
||||
<fieldset>
|
||||
<legend>{% trans "Team" %}</legend>
|
||||
{% bootstrap_field form.team layout="control" %}
|
||||
</fieldset>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -24,6 +24,15 @@
|
||||
{% endblocktrans %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if object.hidden_if_available and object.hidden_if_available.availability.0 == 100 %}
|
||||
<div class="alert alert-warning">
|
||||
{% blocktrans trimmed %}
|
||||
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.
|
||||
{% endblocktrans %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% block inside %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -35,12 +35,12 @@
|
||||
{% bootstrap_field form.available_until layout="control" %}
|
||||
{% bootstrap_field form.max_per_order layout="control" %}
|
||||
{% bootstrap_field form.min_per_order layout="control" %}
|
||||
{% bootstrap_field form.hidden_if_available layout="control" %}
|
||||
{% bootstrap_field form.require_voucher layout="control" %}
|
||||
{% bootstrap_field form.hide_without_voucher layout="control" %}
|
||||
{% bootstrap_field form.require_bundling layout="control" %}
|
||||
{% bootstrap_field form.allow_cancel layout="control" %}
|
||||
{% bootstrap_field form.allow_waitinglist layout="control" %}
|
||||
{% bootstrap_field form.hidden_if_available layout="control" %}
|
||||
</fieldset>
|
||||
{% for v in formsets.values %}
|
||||
<fieldset>
|
||||
|
||||
@@ -266,7 +266,6 @@
|
||||
<br />
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="14" viewBox="0 0 4.7624999 3.7041668" class="svg-icon">
|
||||
<path
|
||||
style="fill:black"
|
||||
d="m 1.9592032,1.8522629e-4 c -0.21468,0 -0.38861,0.17394000371 -0.38861,0.38861000371 0,0.21466 0.17393,0.38861 0.38861,0.38861 0.21468,0 0.3886001,-0.17395 0.3886001,-0.38861 0,-0.21467 -0.1739201,-0.38861000371 -0.3886001,-0.38861000371 z m 0.1049,0.84543000371 c -0.20823,-0.0326 -0.44367,0.12499 -0.39998,0.40462997 l 0.20361,1.01854 c 0.0306,0.15316 0.15301,0.28732 0.3483,0.28732 h 0.8376701 v 0.92708 c 0,0.29313 0.41187,0.29447 0.41187,0.005 v -1.19115 c 0,-0.14168 -0.0995,-0.29507 -0.29094,-0.29507 l -0.65578,-10e-4 -0.1757,-0.87644 C 2.3042533,0.95300523 2.1890432,0.86500523 2.0641032,0.84547523 Z m -0.58549,0.44906997 c -0.0946,-0.0134 -0.20202,0.0625 -0.17829,0.19172 l 0.18759,0.91054 c 0.0763,0.33956 0.36802,0.55914 0.66042,0.55914 h 0.6015201 c 0.21356,0 0.21448,-0.32143 -0.003,-0.32143 H 2.1954632 c -0.19911,0 -0.36364,-0.11898 -0.41341,-0.34107 l -0.17777,-0.87126 c -0.0165,-0.0794 -0.0688,-0.11963 -0.12557,-0.12764 z"/>
|
||||
</svg>
|
||||
{{ line.seat }}
|
||||
@@ -646,12 +645,28 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% if staff_session %}
|
||||
{% if r.html_info %}
|
||||
<tr>
|
||||
<td colspan="1"></td>
|
||||
<td colspan="7">
|
||||
{{ r.html_info|safe }}
|
||||
{% if staff_session %}
|
||||
<p>
|
||||
<a href="" class="btn btn-default btn-xs" data-expandrefund
|
||||
data-id="{{ r.pk }}">
|
||||
<span class="fa-eye fa fa-fw"></span>
|
||||
{% trans "Inspect" %}
|
||||
</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% elif staff_session %}
|
||||
<tr>
|
||||
<td colspan="1"></td>
|
||||
<td colspan="7">
|
||||
<a href="" class="btn btn-default btn-xs" data-expandrefund
|
||||
data-id="{{ r.pk }}">
|
||||
data-id="{{ r.pk }}">
|
||||
<span class="fa-eye fa fa-fw"></span>
|
||||
{% trans "Inspect" %}
|
||||
</a>
|
||||
|
||||
@@ -83,6 +83,27 @@
|
||||
value="" title="" class="form-control">
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td>
|
||||
<strong>{% trans "Create a new gift card" %}</strong>
|
||||
</td>
|
||||
<td></td>
|
||||
<td>
|
||||
<div class="input-group">
|
||||
<input type="text" name="refund-new-giftcard"
|
||||
title="" class="form-control" value="{{ 0|floatformat:2 }}">
|
||||
<span class="input-group-addon">
|
||||
{{ request.event.currency }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-muted">
|
||||
{% trans "The gift card can be used to buy tickets for all events of this organizer." %}
|
||||
</div>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% load formset_tags %}
|
||||
{% block custom_header %}
|
||||
{{ block.super }}
|
||||
<link type="text/css" rel="stylesheet" href="{% url "control:pdf.css" %}">
|
||||
{% endblock %}
|
||||
{% block title %}{% trans "Organizer" %}{% endblock %}
|
||||
{% block content %}
|
||||
<h1>
|
||||
@@ -27,6 +31,8 @@
|
||||
{% bootstrap_field form.domain layout="control" %}
|
||||
{% endif %}
|
||||
{% bootstrap_field sform.organizer_info_text layout="control" %}
|
||||
{% bootstrap_field sform.event_team_provisioning layout="control" %}
|
||||
{% bootstrap_field sform.giftcard_length layout="control" %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Organizer page" %}</legend>
|
||||
|
||||
@@ -65,6 +65,9 @@
|
||||
{% if form.subevent %}
|
||||
{% bootstrap_field form.subevent layout="control" %}
|
||||
{% endif %}
|
||||
{% if "seats" in form.fields %}
|
||||
{% bootstrap_field form.seats layout="control" %}
|
||||
{% endif %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Advanced settings" %}</legend>
|
||||
|
||||
@@ -67,6 +67,9 @@
|
||||
{% if form.subevent %}
|
||||
{% bootstrap_field form.subevent layout="control" %}
|
||||
{% endif %}
|
||||
{% if "seat" in form.fields %}
|
||||
{% bootstrap_field form.seat layout="control" %}
|
||||
{% endif %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Advanced settings" %}</legend>
|
||||
|
||||
@@ -95,13 +95,37 @@
|
||||
<input type="checkbox" data-toggle-table />
|
||||
{% endif %}
|
||||
</th>
|
||||
<th>{% trans "Voucher code" %}</th>
|
||||
<th>{% trans "Redemptions" %}</th>
|
||||
<th>{% trans "Expiry" %}</th>
|
||||
<th>{% trans "Tag" %}</th>
|
||||
<th>{% trans "Product" %}</th>
|
||||
<th>
|
||||
{% trans "Voucher code" %}
|
||||
<a href="?{% url_replace request 'ordering' '-code' %}"><i class="fa fa-caret-down"></i></a>
|
||||
<a href="?{% url_replace request 'ordering' 'code' %}"><i class="fa fa-caret-up"></i></a>
|
||||
</th>
|
||||
<th>
|
||||
{% trans "Redemptions" %}
|
||||
<a href="?{% url_replace request 'ordering' '-redeemed' %}"><i class="fa fa-caret-down"></i></a>
|
||||
<a href="?{% url_replace request 'ordering' 'redeemed' %}"><i class="fa fa-caret-up"></i></a>
|
||||
</th>
|
||||
<th>
|
||||
{% trans "Expiry" %}
|
||||
<a href="?{% url_replace request 'ordering' '-valid_until' %}"><i class="fa fa-caret-down"></i></a>
|
||||
<a href="?{% url_replace request 'ordering' 'valid_until' %}"><i class="fa fa-caret-up"></i></a>
|
||||
</th>
|
||||
<th>
|
||||
{% trans "Tag" %}
|
||||
<a href="?{% url_replace request 'ordering' '-tag' %}"><i class="fa fa-caret-down"></i></a>
|
||||
<a href="?{% url_replace request 'ordering' 'tag' %}"><i class="fa fa-caret-up"></i></a>
|
||||
</th>
|
||||
<th>
|
||||
{% trans "Product" %}
|
||||
<a href="?{% url_replace request 'ordering' '-item' %}"><i class="fa fa-caret-down"></i></a>
|
||||
<a href="?{% url_replace request 'ordering' 'item' %}"><i class="fa fa-caret-up"></i></a>
|
||||
</th>
|
||||
{% if request.event.has_subevents %}
|
||||
<th>{% trans "Date" context "subevent" %}</th>
|
||||
<th>
|
||||
{% trans "Date" context "subevent" %}
|
||||
<a href="?{% url_replace request 'ordering' '-subevent' %}"><i class="fa fa-caret-down"></i></a>
|
||||
<a href="?{% url_replace request 'ordering' 'subevent' %}"><i class="fa fa-caret-up"></i></a>
|
||||
</th>
|
||||
{% endif %}
|
||||
<th></th>
|
||||
</tr>
|
||||
@@ -115,7 +139,9 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if not v.is_active %}<del>{% endif %}
|
||||
<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 }}</td>
|
||||
<td>{{ v.valid_until|date }}</td>
|
||||
@@ -133,6 +159,7 @@
|
||||
Any product in quota "{{ quota }}"
|
||||
{% endblocktrans %}
|
||||
{% endif %}
|
||||
{% if v.seat %}<br><small class="text-muted">{{ v.seat }}</small>{% endif %}
|
||||
</td>
|
||||
{% if request.event.has_subevents %}
|
||||
<td>{{ v.subevent.name }} – {{ v.subevent.get_date_range_display }}</td>
|
||||
|
||||
@@ -68,6 +68,7 @@ class CheckInListShow(EventPermissionRequiredMixin, PaginationMixin, ListView):
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
ctx['checkinlist'] = self.list
|
||||
ctx['seats'] = self.list.subevent.seating_plan if self.list.subevent else self.request.event.seating_plan
|
||||
ctx['filter_form'] = self.filter_form
|
||||
for e in ctx['entries']:
|
||||
if e.last_checked_in:
|
||||
|
||||
@@ -392,11 +392,15 @@ class PaymentSettings(EventSettingsViewMixin, EventSettingsFormView):
|
||||
context = super().get_context_data(*args, **kwargs)
|
||||
context['providers'] = sorted(
|
||||
[p for p in self.request.event.get_payment_providers().values()
|
||||
if not p.is_implicit and (p.settings_form_fields or p.settings_content_render(self.request))],
|
||||
if not (p.is_implicit(self.request) if callable(p.is_implicit) else p.is_implicit) and
|
||||
(p.settings_form_fields or p.settings_content_render(self.request))],
|
||||
key=lambda s: s.verbose_name
|
||||
)
|
||||
|
||||
sales_channels = get_all_sales_channels()
|
||||
for p in context['providers']:
|
||||
p.show_enabled = p.is_enabled
|
||||
p.sales_channels = [sales_channels[channel] for channel in p.settings.get('_restrict_to_sales_channels', as_type=list, default=['web'])]
|
||||
if p.is_meta:
|
||||
p.show_enabled = p.settings._enabled in (True, 'True')
|
||||
return context
|
||||
|
||||
@@ -477,8 +477,8 @@ class QuestionView(EventPermissionRequiredMixin, QuestionMixin, ChartContainingV
|
||||
question=self.object, orderposition__isnull=False,
|
||||
orderposition__order__event=self.request.event
|
||||
)
|
||||
if self.request.GET.get("status", "np") != "":
|
||||
s = self.request.GET.get("status", "np")
|
||||
s = self.request.GET.get("status", "np")
|
||||
if s != "":
|
||||
if s == 'o':
|
||||
qs = qs.filter(orderposition__order__status=Order.STATUS_PENDING,
|
||||
orderposition__order__expires__lt=now().replace(hour=0, minute=0, second=0))
|
||||
@@ -488,6 +488,9 @@ class QuestionView(EventPermissionRequiredMixin, QuestionMixin, ChartContainingV
|
||||
qs = qs.filter(orderposition__order__status__in=[Order.STATUS_PENDING, Order.STATUS_EXPIRED])
|
||||
else:
|
||||
qs = qs.filter(orderposition__order__status=s)
|
||||
|
||||
if s not in (Order.STATUS_CANCELED, ""):
|
||||
qs = qs.filter(orderposition__canceled=False)
|
||||
if self.request.GET.get("item", "") != "":
|
||||
i = self.request.GET.get("item", "")
|
||||
qs = qs.filter(orderposition__item_id__in=(i,))
|
||||
|
||||
@@ -228,19 +228,19 @@ class EventWizard(SafeSessionWizardView):
|
||||
event.testmode = True
|
||||
form_dict['basics'].save()
|
||||
|
||||
has_control_rights = self.request.user.teams.filter(
|
||||
organizer=event.organizer, all_events=True, can_change_event_settings=True, can_change_items=True,
|
||||
can_change_orders=True, can_change_vouchers=True
|
||||
).exists()
|
||||
if not has_control_rights:
|
||||
t = Team.objects.create(
|
||||
organizer=event.organizer, name=_('Team {event}').format(event=event.name),
|
||||
can_change_event_settings=True, can_change_items=True,
|
||||
can_view_orders=True, can_change_orders=True, can_view_vouchers=True,
|
||||
can_change_vouchers=True
|
||||
)
|
||||
t.members.add(self.request.user)
|
||||
t.limit_events.add(event)
|
||||
if not EventWizardBasicsForm.has_control_rights(self.request.user, event.organizer):
|
||||
if basics_data["team"] is not None:
|
||||
t = basics_data["team"]
|
||||
t.limit_events.add(event)
|
||||
elif event.organizer.settings.event_team_provisioning:
|
||||
t = Team.objects.create(
|
||||
organizer=event.organizer, name=_('Team {event}').format(event=event.name),
|
||||
can_change_event_settings=True, can_change_items=True,
|
||||
can_view_orders=True, can_change_orders=True, can_view_vouchers=True,
|
||||
can_change_vouchers=True
|
||||
)
|
||||
t.members.add(self.request.user)
|
||||
t.limit_events.add(event)
|
||||
|
||||
if event.has_subevents:
|
||||
se = event.subevents.create(
|
||||
@@ -267,17 +267,19 @@ class EventWizard(SafeSessionWizardView):
|
||||
event.copy_data_from(from_event)
|
||||
elif self.clone_from:
|
||||
event.copy_data_from(self.clone_from)
|
||||
elif event.has_subevents:
|
||||
event.checkin_lists.create(
|
||||
name=str(se),
|
||||
all_products=True,
|
||||
subevent=se
|
||||
)
|
||||
else:
|
||||
event.checkin_lists.create(
|
||||
name=_('Default'),
|
||||
all_products=True
|
||||
)
|
||||
if event.has_subevents:
|
||||
event.checkin_lists.create(
|
||||
name=str(se),
|
||||
all_products=True,
|
||||
subevent=se
|
||||
)
|
||||
else:
|
||||
event.checkin_lists.create(
|
||||
name=_('Default'),
|
||||
all_products=True
|
||||
)
|
||||
event.set_defaults()
|
||||
|
||||
if basics_data['tax_rate']:
|
||||
if not event.settings.tax_rate_default or event.settings.tax_rate_default.rate != basics_data['tax_rate']:
|
||||
|
||||
@@ -5,6 +5,7 @@ import os
|
||||
import re
|
||||
from datetime import datetime, time, timedelta
|
||||
from decimal import Decimal, DecimalException
|
||||
from urllib.parse import urlencode
|
||||
|
||||
import vat_moss.id
|
||||
from django.conf import settings
|
||||
@@ -203,6 +204,9 @@ class OrderDetail(OrderView):
|
||||
for p in ctx['payments']:
|
||||
if p.payment_provider:
|
||||
p.html_info = (p.payment_provider.payment_control_render(self.request, p) or "").strip()
|
||||
for r in ctx['refunds']:
|
||||
if r.payment_provider:
|
||||
r.html_info = (r.payment_provider.refund_control_render(self.request, r) or "").strip()
|
||||
ctx['invoices'] = list(self.order.invoices.all().select_related('event'))
|
||||
ctx['comment_form'] = CommentForm(initial={
|
||||
'comment': self.order.comment,
|
||||
@@ -480,14 +484,26 @@ class OrderPaymentCancel(OrderView):
|
||||
|
||||
def post(self, *args, **kwargs):
|
||||
if self.payment.state in (OrderPayment.PAYMENT_STATE_CREATED, OrderPayment.PAYMENT_STATE_PENDING):
|
||||
with transaction.atomic():
|
||||
self.payment.state = OrderPayment.PAYMENT_STATE_CANCELED
|
||||
self.payment.save()
|
||||
self.order.log_action('pretix.event.order.payment.canceled', {
|
||||
'local_id': self.payment.local_id,
|
||||
'provider': self.payment.provider,
|
||||
}, user=self.request.user)
|
||||
messages.success(self.request, _('This payment has been canceled.'))
|
||||
try:
|
||||
with transaction.atomic():
|
||||
self.payment.payment_provider.cancel_payment(self.payment)
|
||||
self.order.log_action('pretix.event.order.payment.canceled', {
|
||||
'local_id': self.payment.local_id,
|
||||
'provider': self.payment.provider,
|
||||
}, user=self.request.user if self.request.user.is_authenticated else None)
|
||||
except PaymentException as e:
|
||||
self.order.log_action(
|
||||
'pretix.event.order.payment.canceled.failed',
|
||||
{
|
||||
'local_id': self.payment.local_id,
|
||||
'provider': self.payment.provider,
|
||||
'error': str(e)
|
||||
},
|
||||
user=self.request.user if self.request.user.is_authenticated else None,
|
||||
)
|
||||
messages.error(self.request, str(e))
|
||||
else:
|
||||
messages.success(self.request, _('This payment has been canceled.'))
|
||||
else:
|
||||
messages.error(self.request, _('This payment can not be canceled at the moment.'))
|
||||
return redirect(self.get_order_url())
|
||||
@@ -539,7 +555,7 @@ class OrderRefundProcess(OrderView):
|
||||
if self.refund.state == OrderRefund.REFUND_STATE_EXTERNAL:
|
||||
self.refund.done(user=self.request.user)
|
||||
|
||||
if self.request.POST.get("action") == "r":
|
||||
if self.request.POST.get("action") == "r" and self.order.status != Order.STATUS_CANCELED:
|
||||
mark_order_refunded(self.order, user=self.request.user)
|
||||
elif not (self.order.status == Order.STATUS_PAID and self.order.pending_sum <= 0):
|
||||
self.order.status = Order.STATUS_PENDING
|
||||
@@ -694,6 +710,33 @@ class OrderRefundView(OrderView):
|
||||
provider='manual'
|
||||
))
|
||||
|
||||
giftcard_value = self.request.POST.get('refund-new-giftcard', '0') or '0'
|
||||
giftcard_value = formats.sanitize_separators(giftcard_value)
|
||||
try:
|
||||
giftcard_value = Decimal(giftcard_value)
|
||||
except (DecimalException, TypeError):
|
||||
messages.error(self.request, _('You entered an invalid number.'))
|
||||
is_valid = False
|
||||
else:
|
||||
if giftcard_value:
|
||||
refund_selected += giftcard_value
|
||||
giftcard = self.request.organizer.issued_gift_cards.create(
|
||||
currency=self.request.event.currency,
|
||||
testmode=self.order.testmode
|
||||
)
|
||||
refunds.append(OrderRefund(
|
||||
order=self.order,
|
||||
payment=None,
|
||||
source=OrderRefund.REFUND_SOURCE_ADMIN,
|
||||
state=OrderRefund.REFUND_STATE_CREATED,
|
||||
execution_date=now(),
|
||||
amount=giftcard_value,
|
||||
provider='giftcard',
|
||||
info=json.dumps({
|
||||
'gift_card': giftcard.pk
|
||||
})
|
||||
))
|
||||
|
||||
offsetting_value = self.request.POST.get('refund-offsetting', '0') or '0'
|
||||
offsetting_value = formats.sanitize_separators(offsetting_value)
|
||||
try:
|
||||
@@ -764,10 +807,10 @@ class OrderRefundView(OrderView):
|
||||
'local_id': r.local_id,
|
||||
'provider': r.provider,
|
||||
}, user=self.request.user)
|
||||
if r.payment or r.provider == "offsetting":
|
||||
if r.payment or r.provider == "offsetting" or r.provider == "giftcard":
|
||||
try:
|
||||
r.payment_provider.execute_refund(r)
|
||||
except (PaymentException, Quota.QuotaExceededException) as e:
|
||||
except PaymentException as e:
|
||||
r.state = OrderRefund.REFUND_STATE_FAILED
|
||||
r.save()
|
||||
messages.error(self.request, _('One of the refunds failed to be processed. You should '
|
||||
@@ -801,6 +844,23 @@ class OrderRefundView(OrderView):
|
||||
)
|
||||
self.order.save(update_fields=['status', 'expires'])
|
||||
|
||||
if giftcard_value and self.order.email:
|
||||
messages.success(self.request, _('A new gift card was created. You can now send the user their '
|
||||
'gift card code.'))
|
||||
return redirect(reverse('control:event.order.sendmail', kwargs={
|
||||
'event': self.request.event.slug,
|
||||
'organizer': self.request.event.organizer.slug,
|
||||
'code': self.order.code
|
||||
}) + '?' + urlencode({
|
||||
'subject': _('Your gift card code'),
|
||||
'message': _('Hello,\n\nwe have refunded you {amount} for your order.\n\nYou can use the gift '
|
||||
'card code {giftcard} to pay for future ticket purchases in our shop.\n\n'
|
||||
'Your {event} team').format(
|
||||
event="{event}",
|
||||
amount=money_filter(giftcard_value, self.request.event.currency),
|
||||
giftcard=giftcard.secret,
|
||||
)
|
||||
}))
|
||||
return redirect(self.get_order_url())
|
||||
else:
|
||||
messages.error(self.request, _('The refunds you selected do not match the selected total refund '
|
||||
@@ -859,9 +919,25 @@ class OrderTransition(OrderView):
|
||||
amount=ps
|
||||
)
|
||||
except OrderPayment.DoesNotExist:
|
||||
self.order.payments.filter(state__in=(OrderPayment.PAYMENT_STATE_PENDING,
|
||||
OrderPayment.PAYMENT_STATE_CREATED)) \
|
||||
.update(state=OrderPayment.PAYMENT_STATE_CANCELED)
|
||||
for p in self.order.payments.filter(state__in=(OrderPayment.PAYMENT_STATE_PENDING,
|
||||
OrderPayment.PAYMENT_STATE_CREATED)):
|
||||
try:
|
||||
with transaction.atomic():
|
||||
p.payment_provider.cancel_payment(p)
|
||||
self.order.log_action('pretix.event.order.payment.canceled', {
|
||||
'local_id': p.local_id,
|
||||
'provider': p.provider,
|
||||
}, user=self.request.user if self.request.user.is_authenticated else None)
|
||||
except PaymentException as e:
|
||||
self.order.log_action(
|
||||
'pretix.event.order.payment.canceled.failed',
|
||||
{
|
||||
'local_id': p.local_id,
|
||||
'provider': p.provider,
|
||||
'error': str(e)
|
||||
},
|
||||
user=self.request.user if self.request.user.is_authenticated else None,
|
||||
)
|
||||
p = self.order.payments.create(
|
||||
state=OrderPayment.PAYMENT_STATE_CREATED,
|
||||
provider='manual',
|
||||
@@ -1532,6 +1608,11 @@ class OrderSendMail(EventPermissionRequiredMixin, OrderViewMixin, FormView):
|
||||
event=self.request.event,
|
||||
code=self.kwargs['code'].upper()
|
||||
)
|
||||
kwargs['initial'] = {}
|
||||
if self.request.GET.get('subject'):
|
||||
kwargs['initial']['subject'] = self.request.GET.get('subject')
|
||||
if self.request.GET.get('message'):
|
||||
kwargs['initial']['message'] = self.request.GET.get('message')
|
||||
return kwargs
|
||||
|
||||
def form_invalid(self, form):
|
||||
|
||||
@@ -932,7 +932,7 @@ class GiftCardListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixi
|
||||
|
||||
def get_queryset(self):
|
||||
qs = self.request.organizer.issued_gift_cards.annotate(
|
||||
cached_value=Sum('transactions__value')
|
||||
cached_value=Coalesce(Sum('transactions__value'), Decimal('0.00'))
|
||||
)
|
||||
if self.filter_form.is_valid():
|
||||
qs = self.filter_form.filter_qs(qs)
|
||||
|
||||
@@ -235,11 +235,11 @@ def seat_select2(request, **kwargs):
|
||||
|
||||
if request.event.has_subevents:
|
||||
try:
|
||||
qs = request.event.subevents.get(active=True, pk=request.GET.get('subevent', 0)).free_seats
|
||||
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 = request.event.free_seats()
|
||||
qs = qs.filter(
|
||||
Q(name__icontains=query) | Q(seat_guid__icontains=query)
|
||||
).order_by('name').select_related('product', 'subevent')
|
||||
|
||||
@@ -37,7 +37,9 @@ class VoucherList(PaginationMixin, EventPermissionRequiredMixin, ListView):
|
||||
permission = 'can_view_vouchers'
|
||||
|
||||
def get_queryset(self):
|
||||
qs = self.request.event.vouchers.filter(waitinglistentries__isnull=True).select_related('item', 'variation')
|
||||
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)
|
||||
|
||||
@@ -75,6 +77,8 @@ class VoucherList(PaginationMixin, EventPermissionRequiredMixin, ListView):
|
||||
prod = '%s' % str(v.item)
|
||||
elif v.quota:
|
||||
prod = _('Any product in quota "{quota}"').format(quota=str(v.quota.name))
|
||||
else:
|
||||
prod = _('Any product')
|
||||
row = [
|
||||
v.code,
|
||||
v.valid_until.isoformat() if v.valid_until else "",
|
||||
|
||||
105
src/pretix/helpers/cookies.py
Normal file
105
src/pretix/helpers/cookies.py
Normal file
@@ -0,0 +1,105 @@
|
||||
import re
|
||||
|
||||
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)
|
||||
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
|
||||
# This however breaks all pretix-in-an-iframe things, such as the pretix Widget.
|
||||
# Sadly, this means we need to forcefully set SameSite=None and rely on our other
|
||||
# CSRF protections to be working.
|
||||
response.cookies[key]['samesite'] = 'None'
|
||||
# This will only work on secure cookies as well
|
||||
# https://www.chromestatus.com/feature/5633521622188032
|
||||
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
|
||||
# Copyright 2019 Google LLC.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
|
||||
def should_send_same_site_none(useragent):
|
||||
# Don’t send `SameSite=None` to known incompatible clients.
|
||||
return not has_web_kit_same_site_bug(useragent) and not drops_unrecognized_same_site_cookies(useragent)
|
||||
|
||||
|
||||
def has_web_kit_same_site_bug(useragent):
|
||||
return is_ios_version(12, useragent) or (
|
||||
is_macosx_version(10, 14, useragent) and (is_safari(useragent) or is_mac_embedded_browser(useragent))
|
||||
)
|
||||
|
||||
|
||||
def drops_unrecognized_same_site_cookies(useragent):
|
||||
if is_uc_browser(useragent):
|
||||
return not is_uc_browser_version_at_least(12, 13, 2, useragent)
|
||||
return (
|
||||
is_chromium_based(useragent) and is_chromium_version_at_least(51, useragent) and
|
||||
not is_chromium_version_at_least(67, 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_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/")
|
||||
RE_SAFARI = re.compile(r"Version/.* Safari/")
|
||||
RE_MAC_EMBEDDED = re.compile(r"^Mozilla/[.0-9]+ \(Macintosh;.*Mac OS X [_0-9]+\) AppleWebKit/[.0-9]+ \(KHTML, "
|
||||
r"like Gecko\)$")
|
||||
|
||||
|
||||
def is_ios_version(major, useragent):
|
||||
m = RE_IOS_VERSION.search(useragent)
|
||||
if not m:
|
||||
return False
|
||||
return m.group(1) == str(major)
|
||||
|
||||
|
||||
def is_macosx_version(major, minor, useragent):
|
||||
m = RE_MAC_VERSION.search(useragent)
|
||||
if not m:
|
||||
return False
|
||||
|
||||
return m.group(1) == str(major) and m.group(2) == str(minor)
|
||||
|
||||
|
||||
def is_safari(useragent):
|
||||
return RE_SAFARI.search(useragent) and not is_chromium_based(useragent)
|
||||
|
||||
|
||||
def is_mac_embedded_browser(useragent):
|
||||
return RE_MAC_EMBEDDED.search(useragent)
|
||||
|
||||
|
||||
def is_chromium_based(useragent):
|
||||
return RE_CHROMIUM.search(useragent)
|
||||
|
||||
|
||||
def is_chromium_version_at_least(major, useragent):
|
||||
# Extract digits from first capturing group.
|
||||
version = int(RE_CHROMIUM_VERSION.search(useragent).group(1))
|
||||
return version >= major
|
||||
|
||||
|
||||
def is_uc_browser(useragent):
|
||||
return 'UCBrowser/' in useragent
|
||||
|
||||
|
||||
def is_uc_browser_version_at_least(major, minor, build, useragent):
|
||||
major_version = int(RE_UC_VERSION.search(useragent).group(1))
|
||||
minor_version = int(RE_UC_VERSION.search(useragent).group(2))
|
||||
build_version = int(RE_UC_VERSION.search(useragent).group(3))
|
||||
if major_version != major:
|
||||
return major_version > major
|
||||
if minor_version != minor:
|
||||
return minor_version > minor
|
||||
return build_version >= build
|
||||
@@ -27,6 +27,8 @@ def daterange(df, dt):
|
||||
elif df.year == dt.year:
|
||||
return "{} de {} - {} de {} de {}".format(_date(df, "j"), _date(df, "F"), _date(dt, "j"), _date(dt, "F"), _date(dt, "Y"))
|
||||
|
||||
if df.year == dt.year and df.month == dt.month and df.day == dt.day:
|
||||
return _date(df, "DATE_FORMAT")
|
||||
return _("{date_from} – {date_to}").format(
|
||||
date_from=_date(df, "DATE_FORMAT"), date_to=_date(dt, "DATE_FORMAT")
|
||||
)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from django.core.files import File
|
||||
from i18nfield.utils import I18nJSONEncoder
|
||||
from phonenumber_field.phonenumber import PhoneNumber
|
||||
|
||||
from pretix.base.reldate import RelativeDateWrapper
|
||||
|
||||
@@ -10,6 +11,8 @@ class CustomJSONEncoder(I18nJSONEncoder):
|
||||
return obj.to_string()
|
||||
elif isinstance(obj, File):
|
||||
return obj.name
|
||||
if isinstance(obj, PhoneNumber):
|
||||
return str(obj)
|
||||
else:
|
||||
return super().default(obj)
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="14" viewBox="0 0 4.7624999 3.7041668">
|
||||
<path
|
||||
style="fill:black"
|
||||
d="m 1.9592032,1.8522629e-4 c -0.21468,0 -0.38861,0.17394000371 -0.38861,0.38861000371 0,0.21466 0.17393,0.38861 0.38861,0.38861 0.21468,0 0.3886001,-0.17395 0.3886001,-0.38861 0,-0.21467 -0.1739201,-0.38861000371 -0.3886001,-0.38861000371 z m 0.1049,0.84543000371 c -0.20823,-0.0326 -0.44367,0.12499 -0.39998,0.40462997 l 0.20361,1.01854 c 0.0306,0.15316 0.15301,0.28732 0.3483,0.28732 h 0.8376701 v 0.92708 c 0,0.29313 0.41187,0.29447 0.41187,0.005 v -1.19115 c 0,-0.14168 -0.0995,-0.29507 -0.29094,-0.29507 l -0.65578,-10e-4 -0.1757,-0.87644 C 2.3042533,0.95300523 2.1890432,0.86500523 2.0641032,0.84547523 Z m -0.58549,0.44906997 c -0.0946,-0.0134 -0.20202,0.0625 -0.17829,0.19172 l 0.18759,0.91054 c 0.0763,0.33956 0.36802,0.55914 0.66042,0.55914 h 0.6015201 c 0.21356,0 0.21448,-0.32143 -0.003,-0.32143 H 2.1954632 c -0.19911,0 -0.36364,-0.11898 -0.41341,-0.34107 l -0.17777,-0.87126 c -0.0165,-0.0794 -0.0688,-0.11963 -0.12557,-0.12764 z"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
File diff suppressed because it is too large
Load Diff
@@ -7,57 +7,59 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-10-30 16:23+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: Automatically generated\n"
|
||||
"Language-Team: none\n"
|
||||
"POT-Creation-Date: 2019-12-05 13:34+0000\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"
|
||||
"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"
|
||||
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:68
|
||||
msgid "Marked as paid"
|
||||
msgstr ""
|
||||
msgstr "وضع علامة على دفع"
|
||||
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:76
|
||||
msgid "Comment:"
|
||||
msgstr ""
|
||||
msgstr "تعليق:"
|
||||
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:15
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:39
|
||||
msgid "Placed orders"
|
||||
msgstr ""
|
||||
msgstr "الطلبات"
|
||||
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:15
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:39
|
||||
msgid "Paid orders"
|
||||
msgstr ""
|
||||
msgstr "أوامر دفع"
|
||||
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:27
|
||||
msgid "Total revenue"
|
||||
msgstr ""
|
||||
msgstr "إجمالي الإيرادات"
|
||||
|
||||
#: pretix/plugins/stripe/static/pretixplugins/stripe/pretix-stripe.js:12
|
||||
msgid "Contacting Stripe …"
|
||||
msgstr ""
|
||||
msgstr "الاتصال الشريط ..."
|
||||
|
||||
#: pretix/plugins/stripe/static/pretixplugins/stripe/pretix-stripe.js:57
|
||||
msgid "Total"
|
||||
msgstr ""
|
||||
msgstr "مجموع"
|
||||
|
||||
#: pretix/plugins/stripe/static/pretixplugins/stripe/pretix-stripe.js:146
|
||||
#: pretix/plugins/stripe/static/pretixplugins/stripe/pretix-stripe.js:177
|
||||
msgid "Confirming your payment …"
|
||||
msgstr ""
|
||||
msgstr "تأكيد الدفع الخاص بك ..."
|
||||
|
||||
#: pretix/plugins/stripe/static/pretixplugins/stripe/pretix-stripe.js:153
|
||||
msgid "Contacting your bank …"
|
||||
msgstr ""
|
||||
msgstr "الاتصال البنك الذي تتعامل معه ..."
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:39
|
||||
#: pretix/static/pretixbase/js/asynctask.js:105
|
||||
@@ -65,6 +67,8 @@ msgid ""
|
||||
"Your request has been queued on the server and will now be processed. "
|
||||
"Depending on the size of your event, this might take up to a few minutes."
|
||||
msgstr ""
|
||||
"وقد اصطف طلبك على الخادم وسيتم الآن معالجتها. اعتمادا على حجم الحدث، وهذا قد "
|
||||
"يستغرق ما يصل الى بضع دقائق."
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:45
|
||||
#: pretix/static/pretixbase/js/asynctask.js:111
|
||||
@@ -73,34 +77,43 @@ msgid ""
|
||||
"If this takes longer than two minutes, please contact us or go back in your "
|
||||
"browser and try again."
|
||||
msgstr ""
|
||||
"وصل طلبك على الخادم ولكن ما زلنا ننتظر أن تتم معالجتها. إذا كان هذا يستغرق "
|
||||
"وقتا أطول من دقيقتين، يرجى الاتصال بنا أو العودة في المتصفح الخاص بك وحاول "
|
||||
"مرة أخرى."
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:76
|
||||
#: pretix/static/pretixbase/js/asynctask.js:142
|
||||
#: pretix/static/pretixbase/js/asynctask.js:147
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:24
|
||||
msgid "An error of type {code} occurred."
|
||||
msgstr ""
|
||||
msgstr "حدث خطأ من نوع {كود}."
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:79
|
||||
msgid ""
|
||||
"We currently cannot reach the server, but we keep trying. Last error code: "
|
||||
"{code}"
|
||||
msgstr ""
|
||||
"نحن في الوقت الراهن لا يمكن أن تصل إلى الخادم، ولكننا نواصل المحاولة. رمز "
|
||||
"الخطأ نشاط: {كود}"
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:125
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:21
|
||||
msgid "The request took to long. Please try again."
|
||||
msgstr ""
|
||||
#, fuzzy
|
||||
#| msgid "The request took to long. Please try again."
|
||||
msgid "The request took too long. Please try again."
|
||||
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 ""
|
||||
"نحن في الوقت الراهن لا يمكن أن تصل إلى الخادم. حاول مرة اخرى. رمز الخطأ: "
|
||||
"{كود}"
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:171
|
||||
msgid "We are processing your request …"
|
||||
msgstr ""
|
||||
msgstr "نحن معالجة طلبك ..."
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:179
|
||||
msgid ""
|
||||
@@ -108,130 +121,136 @@ 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
|
||||
msgid "Close message"
|
||||
msgstr ""
|
||||
msgstr "رسالة ثيقة"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/clipboard.js:23
|
||||
msgid "Copied!"
|
||||
msgstr ""
|
||||
msgstr "نسخ!"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/clipboard.js:29
|
||||
msgid "Press Ctrl-C to copy!"
|
||||
msgstr ""
|
||||
msgstr "اضغط Ctrl + C لنسخ!"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:43
|
||||
msgid "Lead Scan QR"
|
||||
msgstr ""
|
||||
msgstr "يؤدي مسح QR"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:45
|
||||
msgid "Check-in QR"
|
||||
msgstr ""
|
||||
msgstr "تحقق في QR"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:249
|
||||
msgid "The PDF background file could not be loaded for the following reason:"
|
||||
msgstr ""
|
||||
msgstr "لا يمكن تحميل ملف PDF الخلفية للأسباب التالية:"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:418
|
||||
msgid "Group of objects"
|
||||
msgstr ""
|
||||
msgstr "مجموعة من الكائنات"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:424
|
||||
msgid "Text object"
|
||||
msgstr ""
|
||||
msgstr "كائن النص"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:426
|
||||
msgid "Barcode area"
|
||||
msgstr ""
|
||||
msgstr "منطقة الباركود"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:428
|
||||
msgid "Powered by pretix"
|
||||
msgstr ""
|
||||
msgstr "مدعوم من pretix"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:430
|
||||
msgid "Object"
|
||||
msgstr ""
|
||||
msgstr "موضوع"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:434
|
||||
msgid "Ticket design"
|
||||
msgstr ""
|
||||
msgstr "تصميم تذكرة"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:687
|
||||
msgid "Saving failed."
|
||||
msgstr ""
|
||||
msgstr "فشل الادخار."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:736
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:774
|
||||
msgid "Error while uploading your PDF file, please try again."
|
||||
msgstr ""
|
||||
msgstr "خطأ أثناء تحميل ملف PDF الخاصة بك، يرجى المحاولة مرة أخرى."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:759
|
||||
msgid "Do you really want to leave the editor without saving your changes?"
|
||||
msgstr ""
|
||||
msgstr "هل تريد حقا أن تترك المحرر دون حفظ التغييرات؟"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:19
|
||||
msgid "An error has occurred."
|
||||
msgstr ""
|
||||
msgstr "حدث خطأ."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:54
|
||||
msgid "Generating messages …"
|
||||
msgstr ""
|
||||
msgstr "توليد رسائل ..."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:69
|
||||
msgid "Unknown error."
|
||||
msgstr ""
|
||||
msgstr "خطأ غير معروف."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:231
|
||||
msgid "Your color has great contrast and is very easy to read!"
|
||||
msgstr ""
|
||||
msgstr "اللون لديه التباين الكبير وهو من السهل جدا أن تقرأ!"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:235
|
||||
msgid "Your color has decent contrast and is probably good-enough to read!"
|
||||
msgstr ""
|
||||
msgstr "اللون لديه النقيض لائق وهو على الارجح جيدة بما فيه الكفاية لقراءة!"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:239
|
||||
msgid ""
|
||||
"Your color has bad contrast for text on white background, please choose a "
|
||||
"darker shade."
|
||||
msgstr ""
|
||||
msgstr "اللون لديه النقيض سيئة للنص على خلفية بيضاء، يرجى اختيار الظل أغمق."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:355
|
||||
msgid "All"
|
||||
msgstr ""
|
||||
msgstr "الكل"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:356
|
||||
msgid "None"
|
||||
msgstr ""
|
||||
msgstr "لا شيء"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:677
|
||||
msgid "Use a different name internally"
|
||||
msgstr ""
|
||||
msgstr "استخدام اسم مختلف داخليا"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:734
|
||||
msgid "Click to close"
|
||||
msgstr "انقر لقريب"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:749
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/orderchange.js:24
|
||||
msgid "Calculating default price…"
|
||||
msgstr ""
|
||||
msgstr "حساب السعر الافتراضي ..."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:42
|
||||
msgid "Others"
|
||||
msgstr ""
|
||||
msgstr "الآخرين"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:71
|
||||
msgid "Count"
|
||||
msgstr ""
|
||||
msgstr "عد"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:120
|
||||
msgid "Yes"
|
||||
msgstr ""
|
||||
msgstr "نعم"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:121
|
||||
msgid "No"
|
||||
msgstr ""
|
||||
msgstr "لا"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/subevent.js:108
|
||||
msgid "(one more date)"
|
||||
@@ -245,11 +264,11 @@ msgstr[5] ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:39
|
||||
msgid "The items in your cart are no longer reserved for you."
|
||||
msgstr ""
|
||||
msgstr "العناصر الموجودة في سلة التسوق الخاصة بك لم تعد حكرا على لك."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:41
|
||||
msgid "Cart expired"
|
||||
msgstr ""
|
||||
msgstr "انتهت العربة"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:46
|
||||
msgid "The items in your cart are reserved for you for one minute."
|
||||
@@ -261,96 +280,96 @@ msgstr[3] ""
|
||||
msgstr[4] ""
|
||||
msgstr[5] ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:213
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:210
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr ""
|
||||
msgstr "الرجاء إدخال كمية لأحد أنواع التذاكر."
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:15
|
||||
msgctxt "widget"
|
||||
msgid "Sold out"
|
||||
msgstr ""
|
||||
msgstr "بيعت كلها"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:16
|
||||
msgctxt "widget"
|
||||
msgid "Buy"
|
||||
msgstr ""
|
||||
msgstr "يشترى"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:17
|
||||
msgctxt "widget"
|
||||
msgid "Register"
|
||||
msgstr ""
|
||||
msgstr "تسجيل"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:18
|
||||
msgctxt "widget"
|
||||
msgid "Reserved"
|
||||
msgstr ""
|
||||
msgstr "محجوز"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:19
|
||||
msgctxt "widget"
|
||||
msgid "FREE"
|
||||
msgstr ""
|
||||
msgstr "مجانا"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:20
|
||||
msgctxt "widget"
|
||||
msgid "from %(currency)s %(price)s"
|
||||
msgstr ""
|
||||
msgstr "من٪ (العملة) ق٪ (سعر) ق"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:21
|
||||
msgctxt "widget"
|
||||
msgid "incl. %(rate)s% %(taxname)s"
|
||||
msgstr ""
|
||||
msgstr "بما في ذلك ٪ (معدل) ق٪٪ (taxname) ق"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:22
|
||||
msgctxt "widget"
|
||||
msgid "plus %(rate)s% %(taxname)s"
|
||||
msgstr ""
|
||||
msgstr "بالإضافة٪ (معدل) ق٪٪ (taxname) ق"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:23
|
||||
msgctxt "widget"
|
||||
msgid "incl. taxes"
|
||||
msgstr ""
|
||||
msgstr "بما في ذلك الضرائب"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:24
|
||||
msgctxt "widget"
|
||||
msgid "plus taxes"
|
||||
msgstr ""
|
||||
msgstr "بالإضافة إلى الضرائب"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:25
|
||||
#, javascript-format
|
||||
msgctxt "widget"
|
||||
msgid "currently available: %s"
|
||||
msgstr ""
|
||||
msgstr "المتاحة حاليا:%s ق"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:26
|
||||
msgctxt "widget"
|
||||
msgid "Only available with a voucher"
|
||||
msgstr ""
|
||||
msgstr "متوفرة فقط مع قسيمة"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:27
|
||||
#, javascript-format
|
||||
msgctxt "widget"
|
||||
msgid "minimum amount to order: %s"
|
||||
msgstr ""
|
||||
msgstr "الحد الأدنى للنظام:%s ق"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:28
|
||||
msgctxt "widget"
|
||||
msgid "Close ticket shop"
|
||||
msgstr ""
|
||||
msgstr "انهيار متجر تذكرة"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:29
|
||||
msgctxt "widget"
|
||||
msgid "The ticket shop could not be loaded."
|
||||
msgstr ""
|
||||
msgstr "لا يمكن تحميل المحل التذاكر."
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:30
|
||||
msgctxt "widget"
|
||||
msgid "The cart could not be created. Please try again later"
|
||||
msgstr ""
|
||||
msgstr "لا يمكن إنشاء العربة. الرجاء معاودة المحاولة في وقت لاحق"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:31
|
||||
msgctxt "widget"
|
||||
msgid "Waiting list"
|
||||
msgstr ""
|
||||
msgstr "قائمة الانتظار"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:32
|
||||
msgctxt "widget"
|
||||
@@ -358,151 +377,154 @@ msgid ""
|
||||
"You currently have an active cart for this event. If you select more "
|
||||
"products, they will be added to your existing cart."
|
||||
msgstr ""
|
||||
"لديك حاليا عربة فعالة لهذا الحدث. إذا قمت بتحديد المزيد من المنتجات، وسوف "
|
||||
"تتم إضافته إلى عربة الموجودة لديك."
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:34
|
||||
msgctxt "widget"
|
||||
msgid "Resume checkout"
|
||||
msgstr ""
|
||||
msgstr "استئناف الخروج"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:35
|
||||
msgctxt "widget"
|
||||
msgid ""
|
||||
"<a href=\"https://pretix.eu\" target=\"_blank\" rel=\"noopener\">event "
|
||||
"ticketing powered by pretix</a>"
|
||||
msgstr ""
|
||||
msgid "Redeem a voucher"
|
||||
msgstr "استبدال قسيمة"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:36
|
||||
msgctxt "widget"
|
||||
msgid "Redeem"
|
||||
msgstr "خلص"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:37
|
||||
msgctxt "widget"
|
||||
msgid "Redeem a voucher"
|
||||
msgstr ""
|
||||
msgid "Voucher code"
|
||||
msgstr "كود قسيمة"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:38
|
||||
msgctxt "widget"
|
||||
msgid "Redeem"
|
||||
msgstr ""
|
||||
msgid "Close"
|
||||
msgstr "قريب"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:39
|
||||
msgctxt "widget"
|
||||
msgid "Voucher code"
|
||||
msgstr ""
|
||||
msgid "Continue"
|
||||
msgstr "استمر"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:40
|
||||
msgctxt "widget"
|
||||
msgid "Close"
|
||||
msgstr ""
|
||||
msgid "See variations"
|
||||
msgstr "نرى الاختلافات"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:41
|
||||
msgctxt "widget"
|
||||
msgid "Continue"
|
||||
msgstr ""
|
||||
msgid "Choose a different event"
|
||||
msgstr "اختيار الحدث مختلفة"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:42
|
||||
msgctxt "widget"
|
||||
msgid "See variations"
|
||||
msgstr ""
|
||||
msgid "Choose a different date"
|
||||
msgstr "اختر تاريخا مختلفا"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:43
|
||||
msgctxt "widget"
|
||||
msgid "Choose a different event"
|
||||
msgstr ""
|
||||
msgid "Back"
|
||||
msgstr "عودة"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:44
|
||||
msgctxt "widget"
|
||||
msgid "Choose a different date"
|
||||
msgstr ""
|
||||
msgid "Next month"
|
||||
msgstr "الشهر القادم"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:45
|
||||
msgctxt "widget"
|
||||
msgid "Back"
|
||||
msgstr ""
|
||||
msgid "Previous month"
|
||||
msgstr "الشهر الماضى"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:46
|
||||
msgctxt "widget"
|
||||
msgid "Next month"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:47
|
||||
msgctxt "widget"
|
||||
msgid "Previous month"
|
||||
msgstr ""
|
||||
msgid "Open seat selection"
|
||||
msgstr "مفتوحة اختيار المقعد"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:48
|
||||
msgctxt "widget"
|
||||
msgid "Open seat selection"
|
||||
msgstr ""
|
||||
msgid "Mo"
|
||||
msgstr "مو"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:49
|
||||
msgid "Tu"
|
||||
msgstr "تو"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:50
|
||||
msgid "Mo"
|
||||
msgstr ""
|
||||
msgid "We"
|
||||
msgstr "نحن"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:51
|
||||
msgid "Tu"
|
||||
msgstr ""
|
||||
msgid "Th"
|
||||
msgstr "العاشر"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:52
|
||||
msgid "We"
|
||||
msgstr ""
|
||||
msgid "Fr"
|
||||
msgstr "الاب"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:53
|
||||
msgid "Th"
|
||||
msgstr ""
|
||||
msgid "Sa"
|
||||
msgstr "سا"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:54
|
||||
msgid "Fr"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:55
|
||||
msgid "Sa"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:56
|
||||
msgid "Su"
|
||||
msgstr ""
|
||||
msgstr "سو"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:57
|
||||
msgid "January"
|
||||
msgstr "كانون الثاني"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:58
|
||||
msgid "February"
|
||||
msgstr "شهر فبراير"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:59
|
||||
msgid "January"
|
||||
msgstr ""
|
||||
msgid "March"
|
||||
msgstr "مارس"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:60
|
||||
msgid "February"
|
||||
msgstr ""
|
||||
msgid "April"
|
||||
msgstr "أبريل"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:61
|
||||
msgid "March"
|
||||
msgstr ""
|
||||
msgid "May"
|
||||
msgstr "مايو"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:62
|
||||
msgid "April"
|
||||
msgstr ""
|
||||
msgid "June"
|
||||
msgstr "يونيو"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:63
|
||||
msgid "May"
|
||||
msgstr ""
|
||||
msgid "July"
|
||||
msgstr "يوليو"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:64
|
||||
msgid "June"
|
||||
msgstr ""
|
||||
msgid "August"
|
||||
msgstr "أغسطس"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:65
|
||||
msgid "July"
|
||||
msgstr ""
|
||||
msgid "September"
|
||||
msgstr "سبتمبر"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:66
|
||||
msgid "August"
|
||||
msgstr ""
|
||||
msgid "October"
|
||||
msgstr "شهر اكتوبر"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:67
|
||||
msgid "September"
|
||||
msgstr ""
|
||||
msgid "November"
|
||||
msgstr "شهر نوفمبر"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:68
|
||||
msgid "October"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:69
|
||||
msgid "November"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:70
|
||||
msgid "December"
|
||||
msgstr ""
|
||||
msgstr "ديسمبر"
|
||||
|
||||
#~ msgctxt "widget"
|
||||
#~ msgid ""
|
||||
#~ "<a href=\"https://pretix.eu\" target=\"_blank\" rel=\"noopener\">event "
|
||||
#~ "ticketing powered by pretix</a>"
|
||||
#~ msgstr ""
|
||||
#~ "الحدث التذاكر مدعوم من <a href=\"https://pretix.eu\" target=\"_blank\" "
|
||||
#~ "rel=\"noopener\">pretix</a>"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-10-30 16:23+0000\n"
|
||||
"POT-Creation-Date: 2019-12-05 13:34+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: Automatically generated\n"
|
||||
"Language-Team: none\n"
|
||||
@@ -88,7 +88,7 @@ msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:125
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:21
|
||||
msgid "The request took to long. Please try again."
|
||||
msgid "The request took too long. Please try again."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:150
|
||||
@@ -212,6 +212,10 @@ msgstr ""
|
||||
msgid "Click to close"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:749
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/orderchange.js:24
|
||||
msgid "Calculating default price…"
|
||||
msgstr ""
|
||||
@@ -252,7 +256,7 @@ msgid_plural "The items in your cart are reserved for you for {num} minutes."
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:213
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:210
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr ""
|
||||
|
||||
@@ -357,143 +361,136 @@ msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:35
|
||||
msgctxt "widget"
|
||||
msgid ""
|
||||
"<a href=\"https://pretix.eu\" target=\"_blank\" rel=\"noopener\">event "
|
||||
"ticketing powered by pretix</a>"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:37
|
||||
msgctxt "widget"
|
||||
msgid "Redeem a voucher"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:38
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:36
|
||||
msgctxt "widget"
|
||||
msgid "Redeem"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:39
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:37
|
||||
msgctxt "widget"
|
||||
msgid "Voucher code"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:40
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:38
|
||||
msgctxt "widget"
|
||||
msgid "Close"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:41
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:39
|
||||
msgctxt "widget"
|
||||
msgid "Continue"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:42
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:40
|
||||
msgctxt "widget"
|
||||
msgid "See variations"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:43
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:41
|
||||
msgctxt "widget"
|
||||
msgid "Choose a different event"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:44
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:42
|
||||
msgctxt "widget"
|
||||
msgid "Choose a different date"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:45
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:43
|
||||
msgctxt "widget"
|
||||
msgid "Back"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:46
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:44
|
||||
msgctxt "widget"
|
||||
msgid "Next month"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:47
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:45
|
||||
msgctxt "widget"
|
||||
msgid "Previous month"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:48
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:46
|
||||
msgctxt "widget"
|
||||
msgid "Open seat selection"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:50
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:48
|
||||
msgid "Mo"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:51
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:49
|
||||
msgid "Tu"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:52
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:50
|
||||
msgid "We"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:53
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:51
|
||||
msgid "Th"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:54
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:52
|
||||
msgid "Fr"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:55
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:53
|
||||
msgid "Sa"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:56
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:54
|
||||
msgid "Su"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:59
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:57
|
||||
msgid "January"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:60
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:58
|
||||
msgid "February"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:61
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:59
|
||||
msgid "March"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:62
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:60
|
||||
msgid "April"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:63
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:61
|
||||
msgid "May"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:64
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:62
|
||||
msgid "June"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:65
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:63
|
||||
msgid "July"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:66
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:64
|
||||
msgid "August"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:67
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:65
|
||||
msgid "September"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:68
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:66
|
||||
msgid "October"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:69
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:67
|
||||
msgid "November"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:70
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:68
|
||||
msgid "December"
|
||||
msgstr ""
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-10-30 16:23+0000\n"
|
||||
"POT-Creation-Date: 2019-12-05 13:34+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: Automatically generated\n"
|
||||
"Language-Team: none\n"
|
||||
@@ -88,7 +88,7 @@ msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:125
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:21
|
||||
msgid "The request took to long. Please try again."
|
||||
msgid "The request took too long. Please try again."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:150
|
||||
@@ -212,6 +212,10 @@ msgstr ""
|
||||
msgid "Click to close"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:749
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/orderchange.js:24
|
||||
msgid "Calculating default price…"
|
||||
msgstr ""
|
||||
@@ -254,7 +258,7 @@ msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
msgstr[2] ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:213
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:210
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr ""
|
||||
|
||||
@@ -359,143 +363,136 @@ msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:35
|
||||
msgctxt "widget"
|
||||
msgid ""
|
||||
"<a href=\"https://pretix.eu\" target=\"_blank\" rel=\"noopener\">event "
|
||||
"ticketing powered by pretix</a>"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:37
|
||||
msgctxt "widget"
|
||||
msgid "Redeem a voucher"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:38
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:36
|
||||
msgctxt "widget"
|
||||
msgid "Redeem"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:39
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:37
|
||||
msgctxt "widget"
|
||||
msgid "Voucher code"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:40
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:38
|
||||
msgctxt "widget"
|
||||
msgid "Close"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:41
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:39
|
||||
msgctxt "widget"
|
||||
msgid "Continue"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:42
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:40
|
||||
msgctxt "widget"
|
||||
msgid "See variations"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:43
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:41
|
||||
msgctxt "widget"
|
||||
msgid "Choose a different event"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:44
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:42
|
||||
msgctxt "widget"
|
||||
msgid "Choose a different date"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:45
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:43
|
||||
msgctxt "widget"
|
||||
msgid "Back"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:46
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:44
|
||||
msgctxt "widget"
|
||||
msgid "Next month"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:47
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:45
|
||||
msgctxt "widget"
|
||||
msgid "Previous month"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:48
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:46
|
||||
msgctxt "widget"
|
||||
msgid "Open seat selection"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:50
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:48
|
||||
msgid "Mo"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:51
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:49
|
||||
msgid "Tu"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:52
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:50
|
||||
msgid "We"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:53
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:51
|
||||
msgid "Th"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:54
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:52
|
||||
msgid "Fr"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:55
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:53
|
||||
msgid "Sa"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:56
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:54
|
||||
msgid "Su"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:59
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:57
|
||||
msgid "January"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:60
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:58
|
||||
msgid "February"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:61
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:59
|
||||
msgid "March"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:62
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:60
|
||||
msgid "April"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:63
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:61
|
||||
msgid "May"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:64
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:62
|
||||
msgid "June"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:65
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:63
|
||||
msgid "July"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:66
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:64
|
||||
msgid "August"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:67
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:65
|
||||
msgid "September"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:68
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:66
|
||||
msgid "October"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:69
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:67
|
||||
msgid "November"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:70
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:68
|
||||
msgid "December"
|
||||
msgstr ""
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,9 +6,9 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-10-30 16:23+0000\n"
|
||||
"PO-Revision-Date: 2019-09-05 18:00+0000\n"
|
||||
"Last-Translator: Ture Gjørup <ture@ignatz.dk>\n"
|
||||
"POT-Creation-Date: 2019-12-05 13:34+0000\n"
|
||||
"PO-Revision-Date: 2019-11-20 14:41+0000\n"
|
||||
"Last-Translator: Mie Frydensbjerg <mif@aarhus.dk>\n"
|
||||
"Language-Team: Danish <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
"da/>\n"
|
||||
"Language: da\n"
|
||||
@@ -95,7 +95,9 @@ msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:125
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:21
|
||||
msgid "The request took to long. Please try again."
|
||||
#, fuzzy
|
||||
#| msgid "The request took to long. Please try again."
|
||||
msgid "The request took too long. Please try again."
|
||||
msgstr "Forespørgselen tog for lang tid. Prøv venligst igen."
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:150
|
||||
@@ -224,6 +226,10 @@ msgstr ""
|
||||
msgid "Click to close"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:749
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/orderchange.js:24
|
||||
#, fuzzy
|
||||
#| msgid "Contacting Stripe …"
|
||||
@@ -268,7 +274,7 @@ msgid_plural "The items in your cart are reserved for you for {num} minutes."
|
||||
msgstr[0] "Varerne i din kurv er reserveret for dig i et minut."
|
||||
msgstr[1] "Varerne i din kurv er reserveret for dig i {num} minutter."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:213
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:210
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr ""
|
||||
|
||||
@@ -285,7 +291,7 @@ msgstr "Læg i kurv"
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:17
|
||||
msgctxt "widget"
|
||||
msgid "Register"
|
||||
msgstr ""
|
||||
msgstr "Book nu"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:18
|
||||
msgctxt "widget"
|
||||
@@ -315,12 +321,12 @@ msgstr "plus %(rate)s% %(taxname)s"
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:23
|
||||
msgctxt "widget"
|
||||
msgid "incl. taxes"
|
||||
msgstr ""
|
||||
msgstr "inkl. moms"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:24
|
||||
msgctxt "widget"
|
||||
msgid "plus taxes"
|
||||
msgstr ""
|
||||
msgstr "plus moms"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:25
|
||||
#, javascript-format
|
||||
@@ -360,171 +366,163 @@ msgid "Waiting list"
|
||||
msgstr "Venteliste"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:32
|
||||
#, fuzzy
|
||||
#| msgctxt "widget"
|
||||
#| msgid ""
|
||||
#| "You currently have an active cart for this event. If you select more "
|
||||
#| "products, they will be added to your existing cart. Click on this message "
|
||||
#| "to continue checkout with your cart."
|
||||
msgctxt "widget"
|
||||
msgid ""
|
||||
"You currently have an active cart for this event. If you select more "
|
||||
"products, they will be added to your existing cart."
|
||||
msgstr ""
|
||||
"Du har allerede en aktiv kurv for dette arrangement. Hvis du vælge flere "
|
||||
"varer vil de blive tilføjet din eksisterende kurv. Klik på denne besked for "
|
||||
"at gå mod kassen med din kurv."
|
||||
"Du har allerede en aktiv booking i gang for dette arrangement. Hvis du "
|
||||
"vælger flere produkter, så vil de blive tilføjet din eksisterende booking."
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:34
|
||||
msgctxt "widget"
|
||||
msgid "Resume checkout"
|
||||
msgstr ""
|
||||
msgstr "Fortsæt booking"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:35
|
||||
msgctxt "widget"
|
||||
msgid ""
|
||||
"<a href=\"https://pretix.eu\" target=\"_blank\" rel=\"noopener\">event "
|
||||
"ticketing powered by pretix</a>"
|
||||
msgstr ""
|
||||
"<a href=\"https://pretix.eu\" target=\"_blank\" rel=\"noopener"
|
||||
"\">billetsystem drevet af pretix</a>"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:37
|
||||
msgctxt "widget"
|
||||
msgid "Redeem a voucher"
|
||||
msgstr "Indløs voucher"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:38
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:36
|
||||
msgctxt "widget"
|
||||
msgid "Redeem"
|
||||
msgstr "Indløs"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:39
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:37
|
||||
msgctxt "widget"
|
||||
msgid "Voucher code"
|
||||
msgstr "Voucherkode"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:40
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:38
|
||||
msgctxt "widget"
|
||||
msgid "Close"
|
||||
msgstr "Luk"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:41
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:39
|
||||
msgctxt "widget"
|
||||
msgid "Continue"
|
||||
msgstr "Fortsæt"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:42
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:40
|
||||
msgctxt "widget"
|
||||
msgid "See variations"
|
||||
msgstr "Vis varianter"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:43
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:41
|
||||
msgctxt "widget"
|
||||
msgid "Choose a different event"
|
||||
msgstr ""
|
||||
msgstr "Vælg et andet arrangement"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:42
|
||||
msgctxt "widget"
|
||||
msgid "Choose a different date"
|
||||
msgstr "Vælg en anden dato"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:43
|
||||
msgctxt "widget"
|
||||
msgid "Back"
|
||||
msgstr "Tilbage"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:44
|
||||
msgctxt "widget"
|
||||
msgid "Choose a different date"
|
||||
msgstr ""
|
||||
msgid "Next month"
|
||||
msgstr "Næste måned"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:45
|
||||
msgctxt "widget"
|
||||
msgid "Back"
|
||||
msgstr ""
|
||||
msgid "Previous month"
|
||||
msgstr "Forrige måned"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:46
|
||||
msgctxt "widget"
|
||||
msgid "Next month"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:47
|
||||
msgctxt "widget"
|
||||
msgid "Previous month"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:48
|
||||
msgctxt "widget"
|
||||
msgid "Open seat selection"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:50
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:48
|
||||
msgid "Mo"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:51
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:49
|
||||
msgid "Tu"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:52
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:50
|
||||
msgid "We"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:53
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:51
|
||||
msgid "Th"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:54
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:52
|
||||
msgid "Fr"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:55
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:53
|
||||
msgid "Sa"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:56
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:54
|
||||
msgid "Su"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:59
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:57
|
||||
msgid "January"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:60
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:58
|
||||
msgid "February"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:61
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:59
|
||||
msgid "March"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:62
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:60
|
||||
msgid "April"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:63
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:61
|
||||
msgid "May"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:64
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:62
|
||||
msgid "June"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:65
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:63
|
||||
msgid "July"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:66
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:64
|
||||
msgid "August"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:67
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:65
|
||||
msgid "September"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:68
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:66
|
||||
msgid "October"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:69
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:67
|
||||
msgid "November"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:70
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:68
|
||||
msgid "December"
|
||||
msgstr ""
|
||||
|
||||
#~ msgctxt "widget"
|
||||
#~ msgid ""
|
||||
#~ "<a href=\"https://pretix.eu\" target=\"_blank\" rel=\"noopener\">event "
|
||||
#~ "ticketing powered by pretix</a>"
|
||||
#~ msgstr ""
|
||||
#~ "<a href=\"https://pretix.eu\" target=\"_blank\" rel=\"noopener"
|
||||
#~ "\">billetsystem drevet af pretix</a>"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "Your request has been queued on the server and will now be processed. If "
|
||||
#~ "this takes longer than two minutes, please contact us or go back in your "
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,8 +7,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-10-30 16:23+0000\n"
|
||||
"PO-Revision-Date: 2019-07-29 08:36+0000\n"
|
||||
"POT-Creation-Date: 2019-12-05 13:34+0000\n"
|
||||
"PO-Revision-Date: 2019-12-06 14:06+0000\n"
|
||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||
"Language-Team: German <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
"de/>\n"
|
||||
@@ -98,7 +98,7 @@ msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:125
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:21
|
||||
msgid "The request took to long. Please try again."
|
||||
msgid "The request took too long. Please try again."
|
||||
msgstr "Diese Anfrage hat zu lange gedauert. Bitte erneut versuchen."
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:150
|
||||
@@ -232,6 +232,10 @@ msgstr "Intern einen anderen Namen verwenden"
|
||||
msgid "Click to close"
|
||||
msgstr "Klicken zum Schließen"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:749
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr "Sie haben ungespeicherte Änderungen!"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/orderchange.js:24
|
||||
msgid "Calculating default price…"
|
||||
msgstr "Berechne Standardpreis…"
|
||||
@@ -274,7 +278,7 @@ msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
"Die Produkte in Ihrem Warenkorb sind noch {num} Minuten für Sie reserviert."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:213
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:210
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr "Bitte tragen Sie eine Menge für eines der Produkte ein."
|
||||
|
||||
@@ -381,149 +385,148 @@ msgstr "Kauf fortsetzen"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:35
|
||||
msgctxt "widget"
|
||||
msgid ""
|
||||
"<a href=\"https://pretix.eu\" target=\"_blank\" rel=\"noopener\">event "
|
||||
"ticketing powered by pretix</a>"
|
||||
msgstr ""
|
||||
"<a href=\"https://pretix.eu\" target=\"_blank\" rel=\"noopener\">Event-"
|
||||
"Ticketshop von pretix</a>"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:37
|
||||
msgctxt "widget"
|
||||
msgid "Redeem a voucher"
|
||||
msgstr "Gutschein einlösen"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:38
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:36
|
||||
msgctxt "widget"
|
||||
msgid "Redeem"
|
||||
msgstr "Einlösen"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:39
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:37
|
||||
msgctxt "widget"
|
||||
msgid "Voucher code"
|
||||
msgstr "Gutscheincode"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:40
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:38
|
||||
msgctxt "widget"
|
||||
msgid "Close"
|
||||
msgstr "Schließen"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:41
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:39
|
||||
msgctxt "widget"
|
||||
msgid "Continue"
|
||||
msgstr "Weiter"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:42
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:40
|
||||
msgctxt "widget"
|
||||
msgid "See variations"
|
||||
msgstr "Varianten zeigen"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:43
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:41
|
||||
msgctxt "widget"
|
||||
msgid "Choose a different event"
|
||||
msgstr "Andere Veranstaltung auswählen"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:44
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:42
|
||||
msgctxt "widget"
|
||||
msgid "Choose a different date"
|
||||
msgstr "Anderen Termin auswählen"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:45
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:43
|
||||
msgctxt "widget"
|
||||
msgid "Back"
|
||||
msgstr "Zurück"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:46
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:44
|
||||
msgctxt "widget"
|
||||
msgid "Next month"
|
||||
msgstr "Nächster Monat"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:47
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:45
|
||||
msgctxt "widget"
|
||||
msgid "Previous month"
|
||||
msgstr "Vorheriger Monat"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:48
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:46
|
||||
msgctxt "widget"
|
||||
msgid "Open seat selection"
|
||||
msgstr "Saalplan öffnen"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:50
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:48
|
||||
msgid "Mo"
|
||||
msgstr "Mo"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:51
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:49
|
||||
msgid "Tu"
|
||||
msgstr "Di"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:52
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:50
|
||||
msgid "We"
|
||||
msgstr "Mi"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:53
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:51
|
||||
msgid "Th"
|
||||
msgstr "Do"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:54
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:52
|
||||
msgid "Fr"
|
||||
msgstr "Fr"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:55
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:53
|
||||
msgid "Sa"
|
||||
msgstr "Sa"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:56
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:54
|
||||
msgid "Su"
|
||||
msgstr "So"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:59
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:57
|
||||
msgid "January"
|
||||
msgstr "Januar"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:60
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:58
|
||||
msgid "February"
|
||||
msgstr "Februar"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:61
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:59
|
||||
msgid "March"
|
||||
msgstr "März"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:62
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:60
|
||||
msgid "April"
|
||||
msgstr "April"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:63
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:61
|
||||
msgid "May"
|
||||
msgstr "Mai"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:64
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:62
|
||||
msgid "June"
|
||||
msgstr "Juni"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:65
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:63
|
||||
msgid "July"
|
||||
msgstr "Juli"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:66
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:64
|
||||
msgid "August"
|
||||
msgstr "August"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:67
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:65
|
||||
msgid "September"
|
||||
msgstr "September"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:68
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:66
|
||||
msgid "October"
|
||||
msgstr "Oktober"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:69
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:67
|
||||
msgid "November"
|
||||
msgstr "November"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:70
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:68
|
||||
msgid "December"
|
||||
msgstr "Dezember"
|
||||
|
||||
#~ msgctxt "widget"
|
||||
#~ msgid ""
|
||||
#~ "<a href=\"https://pretix.eu\" target=\"_blank\" rel=\"noopener\">event "
|
||||
#~ "ticketing powered by pretix</a>"
|
||||
#~ msgstr ""
|
||||
#~ "<a href=\"https://pretix.eu\" target=\"_blank\" rel=\"noopener\">Event-"
|
||||
#~ "Ticketshop von pretix</a>"
|
||||
|
||||
#~ msgid "Ja"
|
||||
#~ msgstr "Ja"
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,8 +7,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-10-30 16:23+0000\n"
|
||||
"PO-Revision-Date: 2019-07-29 08:36+0000\n"
|
||||
"POT-Creation-Date: 2019-12-05 13:34+0000\n"
|
||||
"PO-Revision-Date: 2019-12-06 14:06+0000\n"
|
||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||
"Language-Team: German (informal) <https://translate.pretix.eu/projects/"
|
||||
"pretix/pretix-js/de_Informal/>\n"
|
||||
@@ -97,7 +97,7 @@ msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:125
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:21
|
||||
msgid "The request took to long. Please try again."
|
||||
msgid "The request took too long. Please try again."
|
||||
msgstr "Diese Anfrage hat zu lange gedauert. Bitte erneut versuchen."
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:150
|
||||
@@ -231,6 +231,10 @@ msgstr "Intern einen anderen Namen verwenden"
|
||||
msgid "Click to close"
|
||||
msgstr "Klicken zum Schließen"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:749
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr "Du hast ungespeicherte Änderungen!"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/orderchange.js:24
|
||||
msgid "Calculating default price…"
|
||||
msgstr "Berechne Standardpreis…"
|
||||
@@ -273,7 +277,7 @@ msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
"Die Produkte in deinem Warenkorb sind noch {num} Minuten für dich reserviert."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:213
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:210
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr "Bitte trage eine Menge für eines der Produkte ein."
|
||||
|
||||
@@ -380,149 +384,148 @@ msgstr "Kauf fortsetzen"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:35
|
||||
msgctxt "widget"
|
||||
msgid ""
|
||||
"<a href=\"https://pretix.eu\" target=\"_blank\" rel=\"noopener\">event "
|
||||
"ticketing powered by pretix</a>"
|
||||
msgstr ""
|
||||
"<a href=\"https://pretix.eu\" target=\"_blank\" rel=\"noopener\">Event-"
|
||||
"Ticketshop von pretix</a>"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:37
|
||||
msgctxt "widget"
|
||||
msgid "Redeem a voucher"
|
||||
msgstr "Gutschein einlösen"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:38
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:36
|
||||
msgctxt "widget"
|
||||
msgid "Redeem"
|
||||
msgstr "Einlösen"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:39
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:37
|
||||
msgctxt "widget"
|
||||
msgid "Voucher code"
|
||||
msgstr "Gutscheincode"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:40
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:38
|
||||
msgctxt "widget"
|
||||
msgid "Close"
|
||||
msgstr "Schließen"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:41
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:39
|
||||
msgctxt "widget"
|
||||
msgid "Continue"
|
||||
msgstr "Fortfahren"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:42
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:40
|
||||
msgctxt "widget"
|
||||
msgid "See variations"
|
||||
msgstr "Varianten zeigen"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:43
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:41
|
||||
msgctxt "widget"
|
||||
msgid "Choose a different event"
|
||||
msgstr "Andere Veranstaltung auswählen"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:44
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:42
|
||||
msgctxt "widget"
|
||||
msgid "Choose a different date"
|
||||
msgstr "Anderen Termin auswählen"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:45
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:43
|
||||
msgctxt "widget"
|
||||
msgid "Back"
|
||||
msgstr "Zurück"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:46
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:44
|
||||
msgctxt "widget"
|
||||
msgid "Next month"
|
||||
msgstr "Nächster Monat"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:47
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:45
|
||||
msgctxt "widget"
|
||||
msgid "Previous month"
|
||||
msgstr "Vorheriger Monat"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:48
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:46
|
||||
msgctxt "widget"
|
||||
msgid "Open seat selection"
|
||||
msgstr "Saalplan öffnen"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:50
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:48
|
||||
msgid "Mo"
|
||||
msgstr "Mo"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:51
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:49
|
||||
msgid "Tu"
|
||||
msgstr "Di"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:52
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:50
|
||||
msgid "We"
|
||||
msgstr "Mi"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:53
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:51
|
||||
msgid "Th"
|
||||
msgstr "Do"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:54
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:52
|
||||
msgid "Fr"
|
||||
msgstr "Fr"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:55
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:53
|
||||
msgid "Sa"
|
||||
msgstr "Sa"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:56
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:54
|
||||
msgid "Su"
|
||||
msgstr "So"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:59
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:57
|
||||
msgid "January"
|
||||
msgstr "Januar"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:60
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:58
|
||||
msgid "February"
|
||||
msgstr "Februar"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:61
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:59
|
||||
msgid "March"
|
||||
msgstr "März"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:62
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:60
|
||||
msgid "April"
|
||||
msgstr "April"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:63
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:61
|
||||
msgid "May"
|
||||
msgstr "Mai"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:64
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:62
|
||||
msgid "June"
|
||||
msgstr "Juni"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:65
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:63
|
||||
msgid "July"
|
||||
msgstr "Juli"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:66
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:64
|
||||
msgid "August"
|
||||
msgstr "August"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:67
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:65
|
||||
msgid "September"
|
||||
msgstr "September"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:68
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:66
|
||||
msgid "October"
|
||||
msgstr "Oktober"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:69
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:67
|
||||
msgid "November"
|
||||
msgstr "November"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:70
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:68
|
||||
msgid "December"
|
||||
msgstr "Dezember"
|
||||
|
||||
#~ msgctxt "widget"
|
||||
#~ msgid ""
|
||||
#~ "<a href=\"https://pretix.eu\" target=\"_blank\" rel=\"noopener\">event "
|
||||
#~ "ticketing powered by pretix</a>"
|
||||
#~ msgstr ""
|
||||
#~ "<a href=\"https://pretix.eu\" target=\"_blank\" rel=\"noopener\">Event-"
|
||||
#~ "Ticketshop von pretix</a>"
|
||||
|
||||
#~ msgid "Ja"
|
||||
#~ msgstr "Ja"
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-10-30 16:23+0000\n"
|
||||
"POT-Creation-Date: 2019-12-05 13:34+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -89,7 +89,7 @@ msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:125
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:21
|
||||
msgid "The request took to long. Please try again."
|
||||
msgid "The request took too long. Please try again."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:150
|
||||
@@ -213,6 +213,10 @@ msgstr ""
|
||||
msgid "Click to close"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:749
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/orderchange.js:24
|
||||
msgid "Calculating default price…"
|
||||
msgstr ""
|
||||
@@ -253,7 +257,7 @@ msgid_plural "The items in your cart are reserved for you for {num} minutes."
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:213
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:210
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr ""
|
||||
|
||||
@@ -358,143 +362,136 @@ msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:35
|
||||
msgctxt "widget"
|
||||
msgid ""
|
||||
"<a href=\"https://pretix.eu\" target=\"_blank\" rel=\"noopener\">event "
|
||||
"ticketing powered by pretix</a>"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:37
|
||||
msgctxt "widget"
|
||||
msgid "Redeem a voucher"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:38
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:36
|
||||
msgctxt "widget"
|
||||
msgid "Redeem"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:39
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:37
|
||||
msgctxt "widget"
|
||||
msgid "Voucher code"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:40
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:38
|
||||
msgctxt "widget"
|
||||
msgid "Close"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:41
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:39
|
||||
msgctxt "widget"
|
||||
msgid "Continue"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:42
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:40
|
||||
msgctxt "widget"
|
||||
msgid "See variations"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:43
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:41
|
||||
msgctxt "widget"
|
||||
msgid "Choose a different event"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:44
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:42
|
||||
msgctxt "widget"
|
||||
msgid "Choose a different date"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:45
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:43
|
||||
msgctxt "widget"
|
||||
msgid "Back"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:46
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:44
|
||||
msgctxt "widget"
|
||||
msgid "Next month"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:47
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:45
|
||||
msgctxt "widget"
|
||||
msgid "Previous month"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:48
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:46
|
||||
msgctxt "widget"
|
||||
msgid "Open seat selection"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:50
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:48
|
||||
msgid "Mo"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:51
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:49
|
||||
msgid "Tu"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:52
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:50
|
||||
msgid "We"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:53
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:51
|
||||
msgid "Th"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:54
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:52
|
||||
msgid "Fr"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:55
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:53
|
||||
msgid "Sa"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:56
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:54
|
||||
msgid "Su"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:59
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:57
|
||||
msgid "January"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:60
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:58
|
||||
msgid "February"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:61
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:59
|
||||
msgid "March"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:62
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:60
|
||||
msgid "April"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:63
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:61
|
||||
msgid "May"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:64
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:62
|
||||
msgid "June"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:65
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:63
|
||||
msgid "July"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:66
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:64
|
||||
msgid "August"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:67
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:65
|
||||
msgid "September"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:68
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:66
|
||||
msgid "October"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:69
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:67
|
||||
msgid "November"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:70
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:68
|
||||
msgid "December"
|
||||
msgstr ""
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-10-30 16:23+0000\n"
|
||||
"POT-Creation-Date: 2019-12-05 13:34+0000\n"
|
||||
"PO-Revision-Date: 2019-10-03 19:00+0000\n"
|
||||
"Last-Translator: Chris Spy <chrispiropoulou@hotmail.com>\n"
|
||||
"Language-Team: Greek <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
@@ -100,7 +100,9 @@ msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:125
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:21
|
||||
msgid "The request took to long. Please try again."
|
||||
#, fuzzy
|
||||
#| msgid "The request took to long. Please try again."
|
||||
msgid "The request took too long. Please try again."
|
||||
msgstr "Το αίτημα διήρκησε πολύ. Παρακαλώ προσπαθήστε ξανά."
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:150
|
||||
@@ -237,6 +239,10 @@ msgstr "Χρησιμοποιήστε διαφορετικό όνομα εσωτ
|
||||
msgid "Click to close"
|
||||
msgstr "Κάντε κλικ για να κλείσετε"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:749
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/orderchange.js:24
|
||||
msgid "Calculating default price…"
|
||||
msgstr "Επικοινωνία με το Stripe …"
|
||||
@@ -277,7 +283,7 @@ msgid_plural "The items in your cart are reserved for you for {num} minutes."
|
||||
msgstr[0] "Τα είδη στο καλάθι θα παραμείνουν δεσμευμένα για ένα λεπτό."
|
||||
msgstr[1] "Τα είδη στο καλάθι θα παραμείνουν δεσμευμένα για {num} λεπτά."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:213
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:210
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr "Εισαγάγετε μια ποσότητα για έναν από τους τύπους εισιτηρίων."
|
||||
|
||||
@@ -385,145 +391,144 @@ msgstr "Συνεχίστε την ολοκλήρωση της αγοράς"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:35
|
||||
msgctxt "widget"
|
||||
msgid ""
|
||||
"<a href=\"https://pretix.eu\" target=\"_blank\" rel=\"noopener\">event "
|
||||
"ticketing powered by pretix</a>"
|
||||
msgstr ""
|
||||
"<a href=\"https://pretix.eu\" target=\"_blank\" rel=\"noopener\">εισιτήρια "
|
||||
"εκδηλώσεων powered by pretix</a>"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:37
|
||||
msgctxt "widget"
|
||||
msgid "Redeem a voucher"
|
||||
msgstr "Εξαργυρώστε ένα κουπόνι"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:38
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:36
|
||||
msgctxt "widget"
|
||||
msgid "Redeem"
|
||||
msgstr "Εξαργυρώστε"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:39
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:37
|
||||
msgctxt "widget"
|
||||
msgid "Voucher code"
|
||||
msgstr "Κωδικός κουπονιού"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:40
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:38
|
||||
msgctxt "widget"
|
||||
msgid "Close"
|
||||
msgstr "Κλείσιμο"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:41
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:39
|
||||
msgctxt "widget"
|
||||
msgid "Continue"
|
||||
msgstr "Συνέχεια"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:42
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:40
|
||||
msgctxt "widget"
|
||||
msgid "See variations"
|
||||
msgstr "Δείτε παραλλαγές"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:43
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:41
|
||||
msgctxt "widget"
|
||||
msgid "Choose a different event"
|
||||
msgstr "Επιλέξτε διαφορετική εκδήλωση"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:44
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:42
|
||||
msgctxt "widget"
|
||||
msgid "Choose a different date"
|
||||
msgstr "Επιλέξτε διαφορετική ημερομηνία"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:45
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:43
|
||||
msgctxt "widget"
|
||||
msgid "Back"
|
||||
msgstr "Πίσω"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:46
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:44
|
||||
msgctxt "widget"
|
||||
msgid "Next month"
|
||||
msgstr "Επόμενος μήνας"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:47
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:45
|
||||
msgctxt "widget"
|
||||
msgid "Previous month"
|
||||
msgstr "Προηγούμενος μήνας"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:48
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:46
|
||||
msgctxt "widget"
|
||||
msgid "Open seat selection"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:50
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:48
|
||||
msgid "Mo"
|
||||
msgstr "Δευ"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:51
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:49
|
||||
msgid "Tu"
|
||||
msgstr "Τρι"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:52
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:50
|
||||
msgid "We"
|
||||
msgstr "Τετ"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:53
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:51
|
||||
msgid "Th"
|
||||
msgstr "Πεμ"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:54
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:52
|
||||
msgid "Fr"
|
||||
msgstr "Παρ"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:55
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:53
|
||||
msgid "Sa"
|
||||
msgstr "Σαβ"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:56
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:54
|
||||
msgid "Su"
|
||||
msgstr "Κυρ"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:59
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:57
|
||||
msgid "January"
|
||||
msgstr "Ιανουάριος"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:60
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:58
|
||||
msgid "February"
|
||||
msgstr "Φεβρουάριος"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:61
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:59
|
||||
msgid "March"
|
||||
msgstr "Μάρτιος"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:62
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:60
|
||||
msgid "April"
|
||||
msgstr "Απρίλιος"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:63
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:61
|
||||
msgid "May"
|
||||
msgstr "Μάιος"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:64
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:62
|
||||
msgid "June"
|
||||
msgstr "Ιούνιος"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:65
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:63
|
||||
msgid "July"
|
||||
msgstr "Ιούλιος"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:66
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:64
|
||||
msgid "August"
|
||||
msgstr "Αύγουστος"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:67
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:65
|
||||
msgid "September"
|
||||
msgstr "Σεπτέμβριος"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:68
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:66
|
||||
msgid "October"
|
||||
msgstr "Οκτώβριος"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:69
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:67
|
||||
msgid "November"
|
||||
msgstr "Νοέμβριος"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:70
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:68
|
||||
msgid "December"
|
||||
msgstr "Δεκέμβριος"
|
||||
|
||||
#~ msgctxt "widget"
|
||||
#~ msgid ""
|
||||
#~ "<a href=\"https://pretix.eu\" target=\"_blank\" rel=\"noopener\">event "
|
||||
#~ "ticketing powered by pretix</a>"
|
||||
#~ msgstr ""
|
||||
#~ "<a href=\"https://pretix.eu\" target=\"_blank\" rel=\"noopener"
|
||||
#~ "\">εισιτήρια εκδηλώσεων powered by pretix</a>"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,9 +7,9 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-10-30 16:23+0000\n"
|
||||
"PO-Revision-Date: 2019-03-31 08:00+0000\n"
|
||||
"Last-Translator: oocf <oswaldocerna@gmail.com>\n"
|
||||
"POT-Creation-Date: 2019-12-05 13:34+0000\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-"
|
||||
"js/es/>\n"
|
||||
"Language: es\n"
|
||||
@@ -54,13 +54,11 @@ msgstr "Total"
|
||||
#: pretix/plugins/stripe/static/pretixplugins/stripe/pretix-stripe.js:146
|
||||
#: pretix/plugins/stripe/static/pretixplugins/stripe/pretix-stripe.js:177
|
||||
msgid "Confirming your payment …"
|
||||
msgstr ""
|
||||
msgstr "Confirmando el pago…"
|
||||
|
||||
#: pretix/plugins/stripe/static/pretixplugins/stripe/pretix-stripe.js:153
|
||||
#, fuzzy
|
||||
#| msgid "Contacting Stripe …"
|
||||
msgid "Contacting your bank …"
|
||||
msgstr "Contactando con Stripe…"
|
||||
msgstr "Contactando con el banco…"
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:39
|
||||
#: pretix/static/pretixbase/js/asynctask.js:105
|
||||
@@ -99,7 +97,9 @@ msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:125
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:21
|
||||
msgid "The request took to long. Please try again."
|
||||
#, fuzzy
|
||||
#| msgid "The request took to long. Please try again."
|
||||
msgid "The request took too long. Please try again."
|
||||
msgstr "La solicitud ha tomado demasiado tiempo. Por favor, pruebe de nuevo."
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:150
|
||||
@@ -235,11 +235,13 @@ msgstr "Usar un nombre diferente internamente"
|
||||
msgid "Click to close"
|
||||
msgstr "Click para cerrar"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:749
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/orderchange.js:24
|
||||
#, fuzzy
|
||||
#| msgid "Contacting Stripe …"
|
||||
msgid "Calculating default price…"
|
||||
msgstr "Contactando con Stripe…"
|
||||
msgstr "Calculando el precio por defecto…"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:42
|
||||
msgid "Others"
|
||||
@@ -279,9 +281,9 @@ msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
"Los elementos en su carrito de compras se han reservado por {num} minutos."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:213
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:210
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr ""
|
||||
msgstr "Por favor, introduce un valor para cada tipo de entrada."
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:15
|
||||
msgctxt "widget"
|
||||
@@ -296,7 +298,7 @@ msgstr "Comprar"
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:17
|
||||
msgctxt "widget"
|
||||
msgid "Register"
|
||||
msgstr ""
|
||||
msgstr "Registrarse"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:18
|
||||
msgctxt "widget"
|
||||
@@ -388,152 +390,148 @@ msgstr "Reanudar pago"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:35
|
||||
msgctxt "widget"
|
||||
msgid ""
|
||||
"<a href=\"https://pretix.eu\" target=\"_blank\" rel=\"noopener\">event "
|
||||
"ticketing powered by pretix</a>"
|
||||
msgstr ""
|
||||
"<a href=\"https://pretix.eu\" target=\"_blank\" rel=\"noopener\">tickets "
|
||||
"para eventos cortesía de pretix</a>"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:37
|
||||
msgctxt "widget"
|
||||
msgid "Redeem a voucher"
|
||||
msgstr "Utilizar un cupón"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:38
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:36
|
||||
msgctxt "widget"
|
||||
msgid "Redeem"
|
||||
msgstr "Utilizar cupón"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:39
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:37
|
||||
msgctxt "widget"
|
||||
msgid "Voucher code"
|
||||
msgstr "Código del cupón"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:40
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:38
|
||||
msgctxt "widget"
|
||||
msgid "Close"
|
||||
msgstr "Cerrar"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:41
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:39
|
||||
msgctxt "widget"
|
||||
msgid "Continue"
|
||||
msgstr "Continuar"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:42
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:40
|
||||
msgctxt "widget"
|
||||
msgid "See variations"
|
||||
msgstr "Ver variaciones"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:43
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:41
|
||||
msgctxt "widget"
|
||||
msgid "Choose a different event"
|
||||
msgstr "Elige un evento diferente"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:44
|
||||
#, fuzzy
|
||||
#| msgctxt "widget"
|
||||
#| msgid "Choose a different event"
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:42
|
||||
msgctxt "widget"
|
||||
msgid "Choose a different date"
|
||||
msgstr "Elige un evento diferente"
|
||||
msgstr "Elegir una fecha diferente"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:45
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:43
|
||||
msgctxt "widget"
|
||||
msgid "Back"
|
||||
msgstr "Atrás"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:46
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:44
|
||||
msgctxt "widget"
|
||||
msgid "Next month"
|
||||
msgstr "Siguiente mes"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:47
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:45
|
||||
msgctxt "widget"
|
||||
msgid "Previous month"
|
||||
msgstr "Mes anterior"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:48
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:46
|
||||
msgctxt "widget"
|
||||
msgid "Open seat selection"
|
||||
msgstr ""
|
||||
msgstr "Abrir selección de asientos"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:50
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:48
|
||||
msgid "Mo"
|
||||
msgstr "Me"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:51
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:49
|
||||
msgid "Tu"
|
||||
msgstr "Ma"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:52
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:50
|
||||
msgid "We"
|
||||
msgstr "Mie"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:53
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:51
|
||||
msgid "Th"
|
||||
msgstr "Ju"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:54
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:52
|
||||
msgid "Fr"
|
||||
msgstr "Vi"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:55
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:53
|
||||
msgid "Sa"
|
||||
msgstr "Sá"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:56
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:54
|
||||
msgid "Su"
|
||||
msgstr "Do"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:59
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:57
|
||||
msgid "January"
|
||||
msgstr "Enero"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:60
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:58
|
||||
msgid "February"
|
||||
msgstr "Febrero"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:61
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:59
|
||||
msgid "March"
|
||||
msgstr "Marzo"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:62
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:60
|
||||
msgid "April"
|
||||
msgstr "Abril"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:63
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:61
|
||||
msgid "May"
|
||||
msgstr "Mayo"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:64
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:62
|
||||
msgid "June"
|
||||
msgstr "Junio"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:65
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:63
|
||||
msgid "July"
|
||||
msgstr "Julio"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:66
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:64
|
||||
msgid "August"
|
||||
msgstr "Agosto"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:67
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:65
|
||||
msgid "September"
|
||||
msgstr "Septiembre"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:68
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:66
|
||||
msgid "October"
|
||||
msgstr "Octubre"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:69
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:67
|
||||
msgid "November"
|
||||
msgstr "Noviembre"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:70
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:68
|
||||
msgid "December"
|
||||
msgstr "Diciembre"
|
||||
|
||||
#~ msgctxt "widget"
|
||||
#~ msgid ""
|
||||
#~ "<a href=\"https://pretix.eu\" target=\"_blank\" rel=\"noopener\">event "
|
||||
#~ "ticketing powered by pretix</a>"
|
||||
#~ msgstr ""
|
||||
#~ "<a href=\"https://pretix.eu\" target=\"_blank\" rel=\"noopener\">tickets "
|
||||
#~ "para eventos cortesía de pretix</a>"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "Your request has been queued on the server and will now be processed. If "
|
||||
#~ "this takes longer than two minutes, please contact us or go back in your "
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: French\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-10-30 16:23+0000\n"
|
||||
"POT-Creation-Date: 2019-12-05 13:34+0000\n"
|
||||
"PO-Revision-Date: 2019-10-01 01:00+0000\n"
|
||||
"Last-Translator: Fabian Rodriguez <magicfab@legoutdulibre.com>\n"
|
||||
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
@@ -96,7 +96,9 @@ msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:125
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:21
|
||||
msgid "The request took to long. Please try again."
|
||||
#, fuzzy
|
||||
#| msgid "The request took to long. Please try again."
|
||||
msgid "The request took too long. Please try again."
|
||||
msgstr "La requête a prit trop de temps. Veuillez réessayer."
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:150
|
||||
@@ -233,6 +235,10 @@ msgstr "Utiliser un nom différent en interne"
|
||||
msgid "Click to close"
|
||||
msgstr "Cliquez pour fermer"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:749
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/orderchange.js:24
|
||||
msgid "Calculating default price…"
|
||||
msgstr "Calcul du prix par défaut …"
|
||||
@@ -273,7 +279,7 @@ msgid_plural "The items in your cart are reserved for you for {num} minutes."
|
||||
msgstr[0] "Les articles de votre panier vous sont réservés pour une minute."
|
||||
msgstr[1] "Les articles de votre panier vous sont réservés pour {num} minutes."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:213
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:210
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr "SVP entrez une quantité pour un de vos types de billets."
|
||||
|
||||
@@ -380,149 +386,148 @@ msgstr "Finaliser ma commande"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:35
|
||||
msgctxt "widget"
|
||||
msgid ""
|
||||
"<a href=\"https://pretix.eu\" target=\"_blank\" rel=\"noopener\">event "
|
||||
"ticketing powered by pretix</a>"
|
||||
msgstr ""
|
||||
"<a href=\"https://pretix.eu\" target=\"_blank\" rel=\"noopener\">Billetterie "
|
||||
"en ligne propulsée par Pretix</a>"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:37
|
||||
msgctxt "widget"
|
||||
msgid "Redeem a voucher"
|
||||
msgstr "Utiliser un bon d'achat"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:38
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:36
|
||||
msgctxt "widget"
|
||||
msgid "Redeem"
|
||||
msgstr "Echanger"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:39
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:37
|
||||
msgctxt "widget"
|
||||
msgid "Voucher code"
|
||||
msgstr "Code de réduction"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:40
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:38
|
||||
msgctxt "widget"
|
||||
msgid "Close"
|
||||
msgstr "Fermer"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:41
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:39
|
||||
msgctxt "widget"
|
||||
msgid "Continue"
|
||||
msgstr "Continuer"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:42
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:40
|
||||
msgctxt "widget"
|
||||
msgid "See variations"
|
||||
msgstr "Voir les variations"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:43
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:41
|
||||
msgctxt "widget"
|
||||
msgid "Choose a different event"
|
||||
msgstr "Choisissez un autre événement"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:44
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:42
|
||||
msgctxt "widget"
|
||||
msgid "Choose a different date"
|
||||
msgstr "Choisir une autre date"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:45
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:43
|
||||
msgctxt "widget"
|
||||
msgid "Back"
|
||||
msgstr "Retour"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:46
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:44
|
||||
msgctxt "widget"
|
||||
msgid "Next month"
|
||||
msgstr "Mois suivant"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:47
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:45
|
||||
msgctxt "widget"
|
||||
msgid "Previous month"
|
||||
msgstr "Moins précédent"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:48
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:46
|
||||
msgctxt "widget"
|
||||
msgid "Open seat selection"
|
||||
msgstr "Ouvrir la sélection de sièges"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:50
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:48
|
||||
msgid "Mo"
|
||||
msgstr "Lu"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:51
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:49
|
||||
msgid "Tu"
|
||||
msgstr "Ma"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:52
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:50
|
||||
msgid "We"
|
||||
msgstr "Me"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:53
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:51
|
||||
msgid "Th"
|
||||
msgstr "Je"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:54
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:52
|
||||
msgid "Fr"
|
||||
msgstr "Ve"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:55
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:53
|
||||
msgid "Sa"
|
||||
msgstr "Sa"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:56
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:54
|
||||
msgid "Su"
|
||||
msgstr "Di"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:59
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:57
|
||||
msgid "January"
|
||||
msgstr "Janvier"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:60
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:58
|
||||
msgid "February"
|
||||
msgstr "Février"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:61
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:59
|
||||
msgid "March"
|
||||
msgstr "Mars"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:62
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:60
|
||||
msgid "April"
|
||||
msgstr "Avril"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:63
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:61
|
||||
msgid "May"
|
||||
msgstr "Mai"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:64
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:62
|
||||
msgid "June"
|
||||
msgstr "Juin"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:65
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:63
|
||||
msgid "July"
|
||||
msgstr "Juillet"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:66
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:64
|
||||
msgid "August"
|
||||
msgstr "Août"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:67
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:65
|
||||
msgid "September"
|
||||
msgstr "Septembre"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:68
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:66
|
||||
msgid "October"
|
||||
msgstr "Octobre"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:69
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:67
|
||||
msgid "November"
|
||||
msgstr "Novembre"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:70
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:68
|
||||
msgid "December"
|
||||
msgstr "Décembre"
|
||||
|
||||
#~ msgctxt "widget"
|
||||
#~ msgid ""
|
||||
#~ "<a href=\"https://pretix.eu\" target=\"_blank\" rel=\"noopener\">event "
|
||||
#~ "ticketing powered by pretix</a>"
|
||||
#~ msgstr ""
|
||||
#~ "<a href=\"https://pretix.eu\" target=\"_blank\" rel=\"noopener"
|
||||
#~ "\">Billetterie en ligne propulsée par Pretix</a>"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "Your request has been queued on the server and will now be processed. If "
|
||||
#~ "this takes longer than two minutes, please contact us or go back in your "
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-10-30 16:23+0000\n"
|
||||
"POT-Creation-Date: 2019-12-05 13:34+0000\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-"
|
||||
@@ -98,7 +98,9 @@ msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:125
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:21
|
||||
msgid "The request took to long. Please try again."
|
||||
#, fuzzy
|
||||
#| msgid "The request took to long. Please try again."
|
||||
msgid "The request took too long. Please try again."
|
||||
msgstr "La richiesta ha impiegato troppo tempo. Si prega di riprovare."
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:150
|
||||
@@ -229,6 +231,10 @@ msgstr "Utilizza un nome diverso internamente"
|
||||
msgid "Click to close"
|
||||
msgstr "Clicca per chiudere"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:749
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/orderchange.js:24
|
||||
msgid "Calculating default price…"
|
||||
msgstr "Calcolando il prezzo di default…"
|
||||
@@ -269,7 +275,7 @@ msgid_plural "The items in your cart are reserved for you for {num} minutes."
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:213
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:210
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr ""
|
||||
|
||||
@@ -374,143 +380,136 @@ msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:35
|
||||
msgctxt "widget"
|
||||
msgid ""
|
||||
"<a href=\"https://pretix.eu\" target=\"_blank\" rel=\"noopener\">event "
|
||||
"ticketing powered by pretix</a>"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:37
|
||||
msgctxt "widget"
|
||||
msgid "Redeem a voucher"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:38
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:36
|
||||
msgctxt "widget"
|
||||
msgid "Redeem"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:39
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:37
|
||||
msgctxt "widget"
|
||||
msgid "Voucher code"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:40
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:38
|
||||
msgctxt "widget"
|
||||
msgid "Close"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:41
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:39
|
||||
msgctxt "widget"
|
||||
msgid "Continue"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:42
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:40
|
||||
msgctxt "widget"
|
||||
msgid "See variations"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:43
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:41
|
||||
msgctxt "widget"
|
||||
msgid "Choose a different event"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:44
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:42
|
||||
msgctxt "widget"
|
||||
msgid "Choose a different date"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:45
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:43
|
||||
msgctxt "widget"
|
||||
msgid "Back"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:46
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:44
|
||||
msgctxt "widget"
|
||||
msgid "Next month"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:47
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:45
|
||||
msgctxt "widget"
|
||||
msgid "Previous month"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:48
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:46
|
||||
msgctxt "widget"
|
||||
msgid "Open seat selection"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:50
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:48
|
||||
msgid "Mo"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:51
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:49
|
||||
msgid "Tu"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:52
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:50
|
||||
msgid "We"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:53
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:51
|
||||
msgid "Th"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:54
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:52
|
||||
msgid "Fr"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:55
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:53
|
||||
msgid "Sa"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:56
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:54
|
||||
msgid "Su"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:59
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:57
|
||||
msgid "January"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:60
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:58
|
||||
msgid "February"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:61
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:59
|
||||
msgid "March"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:62
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:60
|
||||
msgid "April"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:63
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:61
|
||||
msgid "May"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:64
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:62
|
||||
msgid "June"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:65
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:63
|
||||
msgid "July"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:66
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:64
|
||||
msgid "August"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:67
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:65
|
||||
msgid "September"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:68
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:66
|
||||
msgid "October"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:69
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:67
|
||||
msgid "November"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:70
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:68
|
||||
msgid "December"
|
||||
msgstr ""
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user