forked from CGM_Public/pretix_original
Compare commits
69
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d220cce0f | ||
|
|
17574e8a23 | ||
|
|
de7314edcc | ||
|
|
df25a1cebf | ||
|
|
0578955273 | ||
|
|
0b4daa9b16 | ||
|
|
8dfc77a927 | ||
|
|
d0f603283b | ||
|
|
3df61b8fb5 | ||
|
|
fdead71884 | ||
|
|
369251b0b0 | ||
|
|
e83798a9b7 | ||
|
|
4c9640561c | ||
|
|
e9ab0d8654 | ||
|
|
c9e5cce7d0 | ||
|
|
859004ec59 | ||
|
|
136511f394 | ||
|
|
ee4081d9c3 | ||
|
|
28b4982161 | ||
|
|
5c8a3f18f3 | ||
|
|
440d1b5766 | ||
|
|
1ff8c6f78b | ||
|
|
71e6a85c38 | ||
|
|
497e6f5c8f | ||
|
|
1d60827fa1 | ||
|
|
3bbed98844 | ||
|
|
8b8ad34d30 | ||
|
|
e64034ed90 | ||
|
|
b185dce17c | ||
|
|
2ffa2315ca | ||
|
|
b0616ed00d | ||
|
|
c36b9bcfcd | ||
|
|
2533ae2b3a | ||
|
|
61ae434ab1 | ||
|
|
2ebbe82baf | ||
|
|
153eb67300 | ||
|
|
a2ed32be8b | ||
|
|
b5c94fd002 | ||
|
|
1b02a898a1 | ||
|
|
f29aa73f8d | ||
|
|
62cbed4891 | ||
|
|
68e31b92fe | ||
|
|
9a90444cca | ||
|
|
18f57ea012 | ||
|
|
08a85b3dab | ||
|
|
81a5e263cb | ||
|
|
926d334b10 | ||
|
|
9bed40fa09 | ||
|
|
ed1dae5fde | ||
|
|
c7060d188f | ||
|
|
9a53dc9c5e | ||
|
|
d1e0a7293b | ||
|
|
7d5b1eebcb | ||
|
|
e4a02c494e | ||
|
|
6ecac70727 | ||
|
|
8a1554323e | ||
|
|
cfc22c806a | ||
|
|
f70d6877dc | ||
|
|
4dfad2ef42 | ||
|
|
dc1d35cb1f | ||
|
|
6e657db882 | ||
|
|
91d3aaf20b | ||
|
|
2f1318e2b9 | ||
|
|
844a74d33f | ||
|
|
76788a874a | ||
|
|
e29d5c37cd | ||
|
|
258e66587e | ||
|
|
d13af2eeab | ||
|
|
14b9afcc40 |
@@ -137,6 +137,7 @@ Endpoints
|
|||||||
:query page: The page number in case of a multi-page result set, default is 1
|
:query page: The page number in case of a multi-page result set, default is 1
|
||||||
:query is_public: If set to ``true``/``false``, only events with a matching value of ``is_public`` are returned.
|
:query is_public: If set to ``true``/``false``, only events with a matching value of ``is_public`` are returned.
|
||||||
:query live: If set to ``true``/``false``, only events with a matching value of ``live`` are returned.
|
:query live: If set to ``true``/``false``, only events with a matching value of ``live`` are returned.
|
||||||
|
:query testmode: If set to ``true``/``false``, only events with a matching value of ``testmode`` are returned.
|
||||||
:query has_subevents: If set to ``true``/``false``, only events with a matching value of ``has_subevents`` are returned.
|
:query has_subevents: If set to ``true``/``false``, only events with a matching value of ``has_subevents`` are returned.
|
||||||
:query is_future: If set to ``true`` (``false``), only events that happen currently or in the future are (not) returned. Event series are never (always) returned.
|
:query is_future: If set to ``true`` (``false``), only events that happen currently or in the future are (not) returned. Event series are never (always) returned.
|
||||||
:query is_past: If set to ``true`` (``false``), only events that are over are (not) returned. Event series are never (always) returned.
|
:query is_past: If set to ``true`` (``false``), only events that are over are (not) returned. Event series are never (always) returned.
|
||||||
|
|||||||
@@ -19,4 +19,4 @@
|
|||||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||||
# <https://www.gnu.org/licenses/>.
|
# <https://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
__version__ = "4.17.0"
|
__version__ = "4.18.0.dev0"
|
||||||
|
|||||||
@@ -693,6 +693,7 @@ class EventSettingsSerializer(SettingsSerializer):
|
|||||||
'frontpage_subevent_ordering',
|
'frontpage_subevent_ordering',
|
||||||
'event_list_type',
|
'event_list_type',
|
||||||
'event_list_available_only',
|
'event_list_available_only',
|
||||||
|
'event_calendar_future_only',
|
||||||
'frontpage_text',
|
'frontpage_text',
|
||||||
'event_info_text',
|
'event_info_text',
|
||||||
'attendee_names_asked',
|
'attendee_names_asked',
|
||||||
@@ -784,6 +785,7 @@ class EventSettingsSerializer(SettingsSerializer):
|
|||||||
'change_allow_user_addons',
|
'change_allow_user_addons',
|
||||||
'change_allow_user_until',
|
'change_allow_user_until',
|
||||||
'change_allow_user_price',
|
'change_allow_user_price',
|
||||||
|
'change_allow_attendee',
|
||||||
'primary_color',
|
'primary_color',
|
||||||
'theme_color_success',
|
'theme_color_success',
|
||||||
'theme_color_danger',
|
'theme_color_danger',
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
# <https://www.gnu.org/licenses/>.
|
# <https://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.core.validators import URLValidator
|
||||||
from i18nfield.fields import I18nCharField, I18nTextField
|
from i18nfield.fields import I18nCharField, I18nTextField
|
||||||
from i18nfield.strings import LazyI18nString
|
from i18nfield.strings import LazyI18nString
|
||||||
from rest_framework.exceptions import ValidationError
|
from rest_framework.exceptions import ValidationError
|
||||||
@@ -69,3 +70,17 @@ class I18nAwareModelSerializer(ModelSerializer):
|
|||||||
|
|
||||||
I18nAwareModelSerializer.serializer_field_mapping[I18nCharField] = I18nField
|
I18nAwareModelSerializer.serializer_field_mapping[I18nCharField] = I18nField
|
||||||
I18nAwareModelSerializer.serializer_field_mapping[I18nTextField] = I18nField
|
I18nAwareModelSerializer.serializer_field_mapping[I18nTextField] = I18nField
|
||||||
|
|
||||||
|
|
||||||
|
class I18nURLField(I18nField):
|
||||||
|
def to_internal_value(self, value):
|
||||||
|
value = super().to_internal_value(value)
|
||||||
|
if not value:
|
||||||
|
return value
|
||||||
|
if isinstance(value.data, dict):
|
||||||
|
for v in value.data.values():
|
||||||
|
if v:
|
||||||
|
URLValidator()(v)
|
||||||
|
else:
|
||||||
|
URLValidator()(value.data)
|
||||||
|
return value
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ from pretix.base.models import (
|
|||||||
|
|
||||||
|
|
||||||
class InlineItemVariationSerializer(I18nAwareModelSerializer):
|
class InlineItemVariationSerializer(I18nAwareModelSerializer):
|
||||||
price = serializers.DecimalField(read_only=True, decimal_places=2, max_digits=10,
|
price = serializers.DecimalField(read_only=True, decimal_places=2, max_digits=13,
|
||||||
coerce_to_string=True)
|
coerce_to_string=True)
|
||||||
meta_data = MetaDataField(required=False, source='*')
|
meta_data = MetaDataField(required=False, source='*')
|
||||||
|
|
||||||
@@ -76,7 +76,7 @@ class InlineItemVariationSerializer(I18nAwareModelSerializer):
|
|||||||
|
|
||||||
|
|
||||||
class ItemVariationSerializer(I18nAwareModelSerializer):
|
class ItemVariationSerializer(I18nAwareModelSerializer):
|
||||||
price = serializers.DecimalField(read_only=True, decimal_places=2, max_digits=10,
|
price = serializers.DecimalField(read_only=True, decimal_places=2, max_digits=13,
|
||||||
coerce_to_string=True)
|
coerce_to_string=True)
|
||||||
meta_data = MetaDataField(required=False, source='*')
|
meta_data = MetaDataField(required=False, source='*')
|
||||||
|
|
||||||
@@ -304,9 +304,9 @@ class ItemSerializer(I18nAwareModelSerializer):
|
|||||||
if not self.instance:
|
if not self.instance:
|
||||||
for addon_data in value:
|
for addon_data in value:
|
||||||
ItemAddOn.clean_categories(self.context['event'], None, self.instance, addon_data['addon_category'])
|
ItemAddOn.clean_categories(self.context['event'], None, self.instance, addon_data['addon_category'])
|
||||||
ItemAddOn.clean_min_count(addon_data['min_count'])
|
ItemAddOn.clean_min_count(addon_data.get('min_count', 0))
|
||||||
ItemAddOn.clean_max_count(addon_data['max_count'])
|
ItemAddOn.clean_max_count(addon_data.get('max_count', 0))
|
||||||
ItemAddOn.clean_max_min_count(addon_data['max_count'], addon_data['min_count'])
|
ItemAddOn.clean_max_min_count(addon_data.get('max_count', 0), addon_data.get('min_count', 0))
|
||||||
return value
|
return value
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
|
|||||||
@@ -381,11 +381,9 @@ class PdfDataSerializer(serializers.Field):
|
|||||||
res['meta:' + k] = v
|
res['meta:' + k] = v
|
||||||
|
|
||||||
if instance.variation_id:
|
if instance.variation_id:
|
||||||
print(instance, instance.variation, instance.variation_id, instance.item)
|
|
||||||
if not hasattr(instance.variation, '_cached_meta_data'):
|
if not hasattr(instance.variation, '_cached_meta_data'):
|
||||||
instance.variation.item = instance.item # saves some database lookups
|
instance.variation.item = instance.item # saves some database lookups
|
||||||
instance.variation._cached_meta_data = instance.variation.meta_data
|
instance.variation._cached_meta_data = instance.variation.meta_data
|
||||||
print(instance.variation._cached_meta_data.items())
|
|
||||||
for k, v in instance.variation._cached_meta_data.items():
|
for k, v in instance.variation._cached_meta_data.items():
|
||||||
res['itemmeta:' + k] = v
|
res['itemmeta:' + k] = v
|
||||||
else:
|
else:
|
||||||
@@ -781,7 +779,7 @@ class OrderPositionCreateSerializer(I18nAwareModelSerializer):
|
|||||||
attendee_name = serializers.CharField(required=False, allow_null=True)
|
attendee_name = serializers.CharField(required=False, allow_null=True)
|
||||||
seat = serializers.CharField(required=False, allow_null=True)
|
seat = serializers.CharField(required=False, allow_null=True)
|
||||||
price = serializers.DecimalField(required=False, allow_null=True, decimal_places=2,
|
price = serializers.DecimalField(required=False, allow_null=True, decimal_places=2,
|
||||||
max_digits=10)
|
max_digits=13)
|
||||||
voucher = serializers.SlugRelatedField(slug_field='code', queryset=Voucher.objects.none(),
|
voucher = serializers.SlugRelatedField(slug_field='code', queryset=Voucher.objects.none(),
|
||||||
required=False, allow_null=True)
|
required=False, allow_null=True)
|
||||||
country = CompatibleCountryField(source='*')
|
country = CompatibleCountryField(source='*')
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ class OrderPositionCreateForExistingOrderSerializer(OrderPositionCreateSerialize
|
|||||||
attendee_name = serializers.CharField(required=False, allow_null=True)
|
attendee_name = serializers.CharField(required=False, allow_null=True)
|
||||||
seat = serializers.CharField(required=False, allow_null=True)
|
seat = serializers.CharField(required=False, allow_null=True)
|
||||||
price = serializers.DecimalField(required=False, allow_null=True, decimal_places=2,
|
price = serializers.DecimalField(required=False, allow_null=True, decimal_places=2,
|
||||||
max_digits=10)
|
max_digits=13)
|
||||||
country = CompatibleCountryField(source='*')
|
country = CompatibleCountryField(source='*')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ class MembershipSerializer(I18nAwareModelSerializer):
|
|||||||
|
|
||||||
|
|
||||||
class GiftCardSerializer(I18nAwareModelSerializer):
|
class GiftCardSerializer(I18nAwareModelSerializer):
|
||||||
value = serializers.DecimalField(max_digits=10, decimal_places=2, min_value=Decimal('0.00'))
|
value = serializers.DecimalField(max_digits=13, decimal_places=2, min_value=Decimal('0.00'))
|
||||||
|
|
||||||
def validate(self, data):
|
def validate(self, data):
|
||||||
data = super().validate(data)
|
data = super().validate(data)
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ with scopes_disabled():
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Event
|
model = Event
|
||||||
fields = ['is_public', 'live', 'has_subevents']
|
fields = ['is_public', 'live', 'has_subevents', 'testmode']
|
||||||
|
|
||||||
def ends_after_qs(self, queryset, name, value):
|
def ends_after_qs(self, queryset, name, value):
|
||||||
expr = (
|
expr = (
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ from oauth2_provider.views import (
|
|||||||
|
|
||||||
from pretix.api.models import OAuthApplication
|
from pretix.api.models import OAuthApplication
|
||||||
from pretix.base.models import Organizer
|
from pretix.base.models import Organizer
|
||||||
|
from pretix.control.views.user import RecentAuthenticationRequiredMixin
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -54,7 +55,7 @@ class OAuthAllowForm(AllowForm):
|
|||||||
del self.fields['organizers']
|
del self.fields['organizers']
|
||||||
|
|
||||||
|
|
||||||
class AuthorizationView(BaseAuthorizationView):
|
class AuthorizationView(RecentAuthenticationRequiredMixin, BaseAuthorizationView):
|
||||||
template_name = "pretixcontrol/auth/oauth_authorization.html"
|
template_name = "pretixcontrol/auth/oauth_authorization.html"
|
||||||
form_class = OAuthAllowForm
|
form_class = OAuthAllowForm
|
||||||
|
|
||||||
@@ -111,6 +112,7 @@ class AuthorizationView(BaseAuthorizationView):
|
|||||||
self.request.user.log_action('pretix.user.oauth.authorized', user=self.request.user, data={
|
self.request.user.log_action('pretix.user.oauth.authorized', user=self.request.user, data={
|
||||||
'application_id': application.pk,
|
'application_id': application.pk,
|
||||||
'application_name': application.name,
|
'application_name': application.name,
|
||||||
|
'organizers': [o.pk for o in form.cleaned_data.get("organizers")] if form.cleaned_data.get("organizers") else []
|
||||||
})
|
})
|
||||||
|
|
||||||
return self.redirect(self.success_url, application)
|
return self.redirect(self.success_url, application)
|
||||||
|
|||||||
@@ -105,9 +105,9 @@ with scopes_disabled():
|
|||||||
subevent_after = django_filters.IsoDateTimeFilter(method='subevent_after_qs')
|
subevent_after = django_filters.IsoDateTimeFilter(method='subevent_after_qs')
|
||||||
subevent_before = django_filters.IsoDateTimeFilter(method='subevent_before_qs')
|
subevent_before = django_filters.IsoDateTimeFilter(method='subevent_before_qs')
|
||||||
search = django_filters.CharFilter(method='search_qs')
|
search = django_filters.CharFilter(method='search_qs')
|
||||||
item = django_filters.CharFilter(field_name='all_positions', lookup_expr='item_id')
|
item = django_filters.CharFilter(field_name='all_positions', lookup_expr='item_id', distinct=True)
|
||||||
variation = django_filters.CharFilter(field_name='all_positions', lookup_expr='variation_id')
|
variation = django_filters.CharFilter(field_name='all_positions', lookup_expr='variation_id', distinct=True)
|
||||||
subevent = django_filters.CharFilter(field_name='all_positions', lookup_expr='subevent_id')
|
subevent = django_filters.CharFilter(field_name='all_positions', lookup_expr='subevent_id', distinct=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Order
|
model = Order
|
||||||
@@ -1507,7 +1507,7 @@ class PaymentViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
|
|||||||
@action(detail=True, methods=['POST'])
|
@action(detail=True, methods=['POST'])
|
||||||
def refund(self, request, **kwargs):
|
def refund(self, request, **kwargs):
|
||||||
payment = self.get_object()
|
payment = self.get_object()
|
||||||
amount = serializers.DecimalField(max_digits=10, decimal_places=2).to_internal_value(
|
amount = serializers.DecimalField(max_digits=13, decimal_places=2).to_internal_value(
|
||||||
request.data.get('amount', str(payment.amount))
|
request.data.get('amount', str(payment.amount))
|
||||||
)
|
)
|
||||||
if 'mark_refunded' in request.data:
|
if 'mark_refunded' in request.data:
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ class GiftCardViewSet(viewsets.ModelViewSet):
|
|||||||
@transaction.atomic()
|
@transaction.atomic()
|
||||||
def transact(self, request, **kwargs):
|
def transact(self, request, **kwargs):
|
||||||
gc = GiftCard.objects.select_for_update(of=OF_SELF).get(pk=self.get_object().pk)
|
gc = GiftCard.objects.select_for_update(of=OF_SELF).get(pk=self.get_object().pk)
|
||||||
value = serializers.DecimalField(max_digits=10, decimal_places=2).to_internal_value(
|
value = serializers.DecimalField(max_digits=13, decimal_places=2).to_internal_value(
|
||||||
request.data.get('value')
|
request.data.get('value')
|
||||||
)
|
)
|
||||||
text = serializers.CharField(allow_blank=True, allow_null=True).to_internal_value(
|
text = serializers.CharField(allow_blank=True, allow_null=True).to_internal_value(
|
||||||
|
|||||||
@@ -646,6 +646,10 @@ def base_placeholders(sender, **kwargs):
|
|||||||
'attendee_name', ['position'], lambda position: position.attendee_name,
|
'attendee_name', ['position'], lambda position: position.attendee_name,
|
||||||
_('John Doe'),
|
_('John Doe'),
|
||||||
),
|
),
|
||||||
|
SimpleFunctionalMailTextPlaceholder(
|
||||||
|
'positionid', ['position'], lambda position: str(position.positionid),
|
||||||
|
'1'
|
||||||
|
),
|
||||||
SimpleFunctionalMailTextPlaceholder(
|
SimpleFunctionalMailTextPlaceholder(
|
||||||
'name', ['position_or_address'],
|
'name', ['position_or_address'],
|
||||||
get_best_name,
|
get_best_name,
|
||||||
|
|||||||
@@ -218,7 +218,9 @@ class ItemDataExporter(ListExporter):
|
|||||||
yield row
|
yield row
|
||||||
|
|
||||||
def get_filename(self):
|
def get_filename(self):
|
||||||
return '{}_products'.format(self.events.first().organizer.slug)
|
if self.is_multievent:
|
||||||
|
return '{}_products'.format(self.events.first().organizer.slug)
|
||||||
|
return '{}_products'.format(self.event.slug)
|
||||||
|
|
||||||
def prepare_xlsx_sheet(self, ws):
|
def prepare_xlsx_sheet(self, ws):
|
||||||
self.__ws = ws
|
self.__ws = ws
|
||||||
|
|||||||
@@ -36,11 +36,13 @@ import logging
|
|||||||
|
|
||||||
import i18nfield.forms
|
import i18nfield.forms
|
||||||
from django import forms
|
from django import forms
|
||||||
|
from django.core.validators import URLValidator
|
||||||
from django.forms.models import ModelFormMetaclass
|
from django.forms.models import ModelFormMetaclass
|
||||||
from django.utils.crypto import get_random_string
|
from django.utils.crypto import get_random_string
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from formtools.wizard.views import SessionWizardView
|
from formtools.wizard.views import SessionWizardView
|
||||||
from hierarkey.forms import HierarkeyForm
|
from hierarkey.forms import HierarkeyForm
|
||||||
|
from i18nfield.strings import LazyI18nString
|
||||||
|
|
||||||
from pretix.base.reldate import RelativeDateField, RelativeDateTimeField
|
from pretix.base.reldate import RelativeDateField, RelativeDateTimeField
|
||||||
|
|
||||||
@@ -222,3 +224,17 @@ class SecretKeySettingsField(forms.CharField):
|
|||||||
if value == SECRET_REDACTED:
|
if value == SECRET_REDACTED:
|
||||||
return
|
return
|
||||||
return super().run_validators(value)
|
return super().run_validators(value)
|
||||||
|
|
||||||
|
|
||||||
|
class I18nURLFormField(i18nfield.forms.I18nFormField):
|
||||||
|
def clean(self, value) -> LazyI18nString:
|
||||||
|
value = super().clean(value)
|
||||||
|
if not value:
|
||||||
|
return value
|
||||||
|
if isinstance(value.data, dict):
|
||||||
|
for v in value.data.values():
|
||||||
|
if v:
|
||||||
|
URLValidator()(v)
|
||||||
|
else:
|
||||||
|
URLValidator()(value.data)
|
||||||
|
return value
|
||||||
|
|||||||
@@ -719,7 +719,7 @@ class BaseQuestionsForm(forms.Form):
|
|||||||
label = escape(q.question) # django-bootstrap3 calls mark_safe
|
label = escape(q.question) # django-bootstrap3 calls mark_safe
|
||||||
required = q.required and not self.all_optional
|
required = q.required and not self.all_optional
|
||||||
if q.type == Question.TYPE_BOOLEAN:
|
if q.type == Question.TYPE_BOOLEAN:
|
||||||
if q.required:
|
if required:
|
||||||
# For some reason, django-bootstrap3 does not set the required attribute
|
# For some reason, django-bootstrap3 does not set the required attribute
|
||||||
# itself.
|
# itself.
|
||||||
widget = forms.CheckboxInput(attrs={'required': 'required'})
|
widget = forms.CheckboxInput(attrs={'required': 'required'})
|
||||||
|
|||||||
@@ -148,6 +148,7 @@ class BaseReportlabInvoiceRenderer(BaseInvoiceRenderer):
|
|||||||
"""
|
"""
|
||||||
stylesheet = StyleSheet1()
|
stylesheet = StyleSheet1()
|
||||||
stylesheet.add(ParagraphStyle(name='Normal', fontName=self.font_regular, fontSize=10, leading=12))
|
stylesheet.add(ParagraphStyle(name='Normal', fontName=self.font_regular, fontSize=10, leading=12))
|
||||||
|
stylesheet.add(ParagraphStyle(name='NormalRight', fontName=self.font_regular, fontSize=10, leading=12, alignment=TA_RIGHT))
|
||||||
stylesheet.add(ParagraphStyle(name='BoldInverseCenter', fontName=self.font_bold, fontSize=10, leading=12,
|
stylesheet.add(ParagraphStyle(name='BoldInverseCenter', fontName=self.font_bold, fontSize=10, leading=12,
|
||||||
textColor=colors.white, alignment=TA_CENTER))
|
textColor=colors.white, alignment=TA_CENTER))
|
||||||
stylesheet.add(ParagraphStyle(name='InvoiceFrom', parent=stylesheet['Normal']))
|
stylesheet.add(ParagraphStyle(name='InvoiceFrom', parent=stylesheet['Normal']))
|
||||||
@@ -610,8 +611,8 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
|||||||
),
|
),
|
||||||
str(len(lines)),
|
str(len(lines)),
|
||||||
localize(tax_rate) + " %",
|
localize(tax_rate) + " %",
|
||||||
money_filter(net_value * len(lines), self.invoice.event.currency),
|
Paragraph(money_filter(net_value * len(lines), self.invoice.event.currency).replace('\xa0', ' '), self.stylesheet['NormalRight']),
|
||||||
money_filter(gross_value * len(lines), self.invoice.event.currency),
|
Paragraph(money_filter(gross_value * len(lines), self.invoice.event.currency).replace('\xa0', ' '), self.stylesheet['NormalRight']),
|
||||||
))
|
))
|
||||||
else:
|
else:
|
||||||
if len(lines) > 1:
|
if len(lines) > 1:
|
||||||
@@ -625,7 +626,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
|||||||
self.stylesheet['Normal']
|
self.stylesheet['Normal']
|
||||||
),
|
),
|
||||||
str(len(lines)),
|
str(len(lines)),
|
||||||
money_filter(gross_value * len(lines), self.invoice.event.currency),
|
Paragraph(money_filter(gross_value * len(lines), self.invoice.event.currency).replace('\xa0', ' '), self.stylesheet['NormalRight']),
|
||||||
))
|
))
|
||||||
taxvalue_map[tax_rate, tax_name] += (gross_value - net_value) * len(lines)
|
taxvalue_map[tax_rate, tax_name] += (gross_value - net_value) * len(lines)
|
||||||
grossvalue_map[tax_rate, tax_name] += gross_value * len(lines)
|
grossvalue_map[tax_rate, tax_name] += gross_value * len(lines)
|
||||||
@@ -633,12 +634,14 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
|||||||
|
|
||||||
if has_taxes:
|
if has_taxes:
|
||||||
tdata.append([
|
tdata.append([
|
||||||
pgettext('invoice', 'Invoice total'), '', '', '', money_filter(total, self.invoice.event.currency)
|
pgettext('invoice', 'Invoice total'), '', '', '',
|
||||||
|
money_filter(total, self.invoice.event.currency)
|
||||||
])
|
])
|
||||||
colwidths = [a * doc.width for a in (.50, .05, .15, .15, .15)]
|
colwidths = [a * doc.width for a in (.50, .05, .15, .15, .15)]
|
||||||
else:
|
else:
|
||||||
tdata.append([
|
tdata.append([
|
||||||
pgettext('invoice', 'Invoice total'), '', money_filter(total, self.invoice.event.currency)
|
pgettext('invoice', 'Invoice total'), '',
|
||||||
|
money_filter(total, self.invoice.event.currency)
|
||||||
])
|
])
|
||||||
colwidths = [a * doc.width for a in (.65, .20, .15)]
|
colwidths = [a * doc.width for a in (.65, .20, .15)]
|
||||||
|
|
||||||
|
|||||||
@@ -44,8 +44,8 @@ class Command(BaseCommand):
|
|||||||
OrderPosition.objects.filter(
|
OrderPosition.objects.filter(
|
||||||
order=OuterRef('pk')
|
order=OuterRef('pk')
|
||||||
).order_by().values('order').annotate(p=Sum('price')).values('p'),
|
).order_by().values('order').annotate(p=Sum('price')).values('p'),
|
||||||
output_field=models.DecimalField(decimal_places=2, max_digits=10)
|
output_field=models.DecimalField(decimal_places=2, max_digits=13)
|
||||||
), Value(0), output_field=models.DecimalField(decimal_places=2, max_digits=10)
|
), Value(0), output_field=models.DecimalField(decimal_places=2, max_digits=13)
|
||||||
),
|
),
|
||||||
position_cnt=Case(
|
position_cnt=Case(
|
||||||
When(Q(status__in=('e', 'c')) | Q(require_approval=True), then=Value(0)),
|
When(Q(status__in=('e', 'c')) | Q(require_approval=True), then=Value(0)),
|
||||||
@@ -64,16 +64,16 @@ class Command(BaseCommand):
|
|||||||
OrderFee.objects.filter(
|
OrderFee.objects.filter(
|
||||||
order=OuterRef('pk')
|
order=OuterRef('pk')
|
||||||
).order_by().values('order').annotate(p=Sum('value')).values('p'),
|
).order_by().values('order').annotate(p=Sum('value')).values('p'),
|
||||||
output_field=models.DecimalField(decimal_places=2, max_digits=10)
|
output_field=models.DecimalField(decimal_places=2, max_digits=13)
|
||||||
), Value(0), output_field=models.DecimalField(decimal_places=2, max_digits=10)
|
), Value(0), output_field=models.DecimalField(decimal_places=2, max_digits=13)
|
||||||
),
|
),
|
||||||
tx_total=Coalesce(
|
tx_total=Coalesce(
|
||||||
Subquery(
|
Subquery(
|
||||||
Transaction.objects.filter(
|
Transaction.objects.filter(
|
||||||
order=OuterRef('pk')
|
order=OuterRef('pk')
|
||||||
).order_by().values('order').annotate(p=Sum(F('price') * F('count'))).values('p'),
|
).order_by().values('order').annotate(p=Sum(F('price') * F('count'))).values('p'),
|
||||||
output_field=models.DecimalField(decimal_places=2, max_digits=10)
|
output_field=models.DecimalField(decimal_places=2, max_digits=13)
|
||||||
), Value(0), output_field=models.DecimalField(decimal_places=2, max_digits=10)
|
), Value(0), output_field=models.DecimalField(decimal_places=2, max_digits=13)
|
||||||
),
|
),
|
||||||
tx_cnt=Coalesce(
|
tx_cnt=Coalesce(
|
||||||
Subquery(
|
Subquery(
|
||||||
@@ -81,15 +81,15 @@ class Command(BaseCommand):
|
|||||||
order=OuterRef('pk'),
|
order=OuterRef('pk'),
|
||||||
item__isnull=False,
|
item__isnull=False,
|
||||||
).order_by().values('order').annotate(p=Sum(F('count'))).values('p'),
|
).order_by().values('order').annotate(p=Sum(F('count'))).values('p'),
|
||||||
output_field=models.DecimalField(decimal_places=2, max_digits=10)
|
output_field=models.DecimalField(decimal_places=2, max_digits=13)
|
||||||
), Value(0), output_field=models.DecimalField(decimal_places=2, max_digits=10)
|
), Value(0), output_field=models.DecimalField(decimal_places=2, max_digits=13)
|
||||||
),
|
),
|
||||||
).annotate(
|
).annotate(
|
||||||
correct_total=Case(
|
correct_total=Case(
|
||||||
When(Q(status=Order.STATUS_CANCELED) | Q(status=Order.STATUS_EXPIRED) | Q(require_approval=True),
|
When(Q(status=Order.STATUS_CANCELED) | Q(status=Order.STATUS_EXPIRED) | Q(require_approval=True),
|
||||||
then=Value(0)),
|
then=Value(0)),
|
||||||
default=F('position_total') + F('fee_total'),
|
default=F('position_total') + F('fee_total'),
|
||||||
output_field=models.DecimalField(decimal_places=2, max_digits=10)
|
output_field=models.DecimalField(decimal_places=2, max_digits=13)
|
||||||
),
|
),
|
||||||
).exclude(
|
).exclude(
|
||||||
total=F('position_total') + F('fee_total'),
|
total=F('position_total') + F('fee_total'),
|
||||||
|
|||||||
@@ -0,0 +1,165 @@
|
|||||||
|
# Generated by Django 3.2.18 on 2023-03-16 20:23
|
||||||
|
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('pretixbase', '0234_total_ordering'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='cancellationrequest',
|
||||||
|
name='cancellation_fee',
|
||||||
|
field=models.DecimalField(decimal_places=2, max_digits=13),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='cartposition',
|
||||||
|
name='custom_price_input',
|
||||||
|
field=models.DecimalField(decimal_places=2, max_digits=13, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='cartposition',
|
||||||
|
name='line_price_gross',
|
||||||
|
field=models.DecimalField(decimal_places=2, max_digits=13, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='cartposition',
|
||||||
|
name='listed_price',
|
||||||
|
field=models.DecimalField(decimal_places=2, max_digits=13, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='cartposition',
|
||||||
|
name='price',
|
||||||
|
field=models.DecimalField(decimal_places=2, max_digits=13),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='cartposition',
|
||||||
|
name='price_after_voucher',
|
||||||
|
field=models.DecimalField(decimal_places=2, max_digits=13, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='discount',
|
||||||
|
name='condition_min_value',
|
||||||
|
field=models.DecimalField(decimal_places=2, default=Decimal('0.00'), max_digits=13),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='giftcardtransaction',
|
||||||
|
name='value',
|
||||||
|
field=models.DecimalField(decimal_places=2, max_digits=13),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='invoice',
|
||||||
|
name='foreign_currency_rate',
|
||||||
|
field=models.DecimalField(decimal_places=4, max_digits=13, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='invoiceline',
|
||||||
|
name='gross_value',
|
||||||
|
field=models.DecimalField(decimal_places=2, max_digits=13),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='invoiceline',
|
||||||
|
name='tax_value',
|
||||||
|
field=models.DecimalField(decimal_places=2, default=Decimal('0.00'), max_digits=13),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='item',
|
||||||
|
name='default_price',
|
||||||
|
field=models.DecimalField(decimal_places=2, max_digits=13, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='item',
|
||||||
|
name='original_price',
|
||||||
|
field=models.DecimalField(decimal_places=2, max_digits=13, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='itembundle',
|
||||||
|
name='designated_price',
|
||||||
|
field=models.DecimalField(decimal_places=2, default=Decimal('0.00'), max_digits=13),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='itemvariation',
|
||||||
|
name='default_price',
|
||||||
|
field=models.DecimalField(decimal_places=2, max_digits=13, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='itemvariation',
|
||||||
|
name='original_price',
|
||||||
|
field=models.DecimalField(decimal_places=2, max_digits=13, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='order',
|
||||||
|
name='total',
|
||||||
|
field=models.DecimalField(decimal_places=2, max_digits=13),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='orderfee',
|
||||||
|
name='tax_value',
|
||||||
|
field=models.DecimalField(decimal_places=2, max_digits=13),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='orderfee',
|
||||||
|
name='value',
|
||||||
|
field=models.DecimalField(decimal_places=2, max_digits=13),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='orderpayment',
|
||||||
|
name='amount',
|
||||||
|
field=models.DecimalField(decimal_places=2, max_digits=13),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='orderposition',
|
||||||
|
name='price',
|
||||||
|
field=models.DecimalField(decimal_places=2, max_digits=13),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='orderposition',
|
||||||
|
name='tax_value',
|
||||||
|
field=models.DecimalField(decimal_places=2, max_digits=13),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='orderposition',
|
||||||
|
name='voucher_budget_use',
|
||||||
|
field=models.DecimalField(decimal_places=2, max_digits=13, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='orderrefund',
|
||||||
|
name='amount',
|
||||||
|
field=models.DecimalField(decimal_places=2, max_digits=13),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='subeventitem',
|
||||||
|
name='price',
|
||||||
|
field=models.DecimalField(decimal_places=2, max_digits=13, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='subeventitemvariation',
|
||||||
|
name='price',
|
||||||
|
field=models.DecimalField(decimal_places=2, max_digits=13, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='transaction',
|
||||||
|
name='price',
|
||||||
|
field=models.DecimalField(decimal_places=2, max_digits=13),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='transaction',
|
||||||
|
name='tax_value',
|
||||||
|
field=models.DecimalField(decimal_places=2, max_digits=13),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='voucher',
|
||||||
|
name='budget',
|
||||||
|
field=models.DecimalField(decimal_places=2, max_digits=13, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='voucher',
|
||||||
|
name='value',
|
||||||
|
field=models.DecimalField(decimal_places=2, max_digits=13, null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -55,7 +55,12 @@ class CheckinList(LoggedModel):
|
|||||||
all_products = models.BooleanField(default=True, verbose_name=_("All products (including newly created ones)"))
|
all_products = models.BooleanField(default=True, verbose_name=_("All products (including newly created ones)"))
|
||||||
limit_products = models.ManyToManyField('Item', verbose_name=_("Limit to products"), blank=True)
|
limit_products = models.ManyToManyField('Item', verbose_name=_("Limit to products"), blank=True)
|
||||||
subevent = models.ForeignKey('SubEvent', null=True, blank=True,
|
subevent = models.ForeignKey('SubEvent', null=True, blank=True,
|
||||||
verbose_name=pgettext_lazy('subevent', 'Date'), on_delete=models.CASCADE)
|
verbose_name=pgettext_lazy('subevent', 'Date'),
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
help_text=_('If you choose "all dates", tickets will be considered part of this list '
|
||||||
|
'and valid for check-in regardless of which date they are purchased for. '
|
||||||
|
'You can limit their validity through the advanced check-in rules, '
|
||||||
|
'though.'))
|
||||||
include_pending = models.BooleanField(verbose_name=pgettext_lazy('checkin', 'Include pending orders'),
|
include_pending = models.BooleanField(verbose_name=pgettext_lazy('checkin', 'Include pending orders'),
|
||||||
default=False,
|
default=False,
|
||||||
help_text=_('With this option, people will be able to check in even if the '
|
help_text=_('With this option, people will be able to check in even if the '
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ class Discount(LoggedModel):
|
|||||||
condition_min_value = models.DecimalField(
|
condition_min_value = models.DecimalField(
|
||||||
verbose_name=_('Minimum gross value of matching products'),
|
verbose_name=_('Minimum gross value of matching products'),
|
||||||
decimal_places=2,
|
decimal_places=2,
|
||||||
max_digits=10,
|
max_digits=13,
|
||||||
default=Decimal('0.00'),
|
default=Decimal('0.00'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ class GiftCardTransaction(models.Model):
|
|||||||
)
|
)
|
||||||
value = models.DecimalField(
|
value = models.DecimalField(
|
||||||
decimal_places=2,
|
decimal_places=2,
|
||||||
max_digits=10
|
max_digits=13
|
||||||
)
|
)
|
||||||
order = models.ForeignKey(
|
order = models.ForeignKey(
|
||||||
'Order',
|
'Order',
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ class Invoice(models.Model):
|
|||||||
footer_text = models.TextField(blank=True)
|
footer_text = models.TextField(blank=True)
|
||||||
|
|
||||||
foreign_currency_display = models.CharField(max_length=50, null=True, blank=True)
|
foreign_currency_display = models.CharField(max_length=50, null=True, blank=True)
|
||||||
foreign_currency_rate = models.DecimalField(decimal_places=4, max_digits=10, null=True, blank=True)
|
foreign_currency_rate = models.DecimalField(decimal_places=4, max_digits=13, null=True, blank=True)
|
||||||
foreign_currency_rate_date = models.DateField(null=True, blank=True)
|
foreign_currency_rate_date = models.DateField(null=True, blank=True)
|
||||||
foreign_currency_source = models.CharField(max_length=100, null=True, blank=True)
|
foreign_currency_source = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
|
||||||
@@ -347,8 +347,8 @@ class InvoiceLine(models.Model):
|
|||||||
invoice = models.ForeignKey('Invoice', related_name='lines', on_delete=models.CASCADE)
|
invoice = models.ForeignKey('Invoice', related_name='lines', on_delete=models.CASCADE)
|
||||||
position = models.PositiveIntegerField(default=0)
|
position = models.PositiveIntegerField(default=0)
|
||||||
description = models.TextField()
|
description = models.TextField()
|
||||||
gross_value = models.DecimalField(max_digits=10, decimal_places=2)
|
gross_value = models.DecimalField(max_digits=13, decimal_places=2)
|
||||||
tax_value = models.DecimalField(max_digits=10, decimal_places=2, default=Decimal('0.00'))
|
tax_value = models.DecimalField(max_digits=13, decimal_places=2, default=Decimal('0.00'))
|
||||||
tax_rate = models.DecimalField(max_digits=7, decimal_places=2, default=Decimal('0.00'))
|
tax_rate = models.DecimalField(max_digits=7, decimal_places=2, default=Decimal('0.00'))
|
||||||
tax_name = models.CharField(max_length=190)
|
tax_name = models.CharField(max_length=190)
|
||||||
subevent = models.ForeignKey('SubEvent', null=True, blank=True, on_delete=models.PROTECT)
|
subevent = models.ForeignKey('SubEvent', null=True, blank=True, on_delete=models.PROTECT)
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ class SubEventItem(models.Model):
|
|||||||
"""
|
"""
|
||||||
subevent = models.ForeignKey('SubEvent', on_delete=models.CASCADE)
|
subevent = models.ForeignKey('SubEvent', on_delete=models.CASCADE)
|
||||||
item = models.ForeignKey('Item', on_delete=models.CASCADE)
|
item = models.ForeignKey('Item', on_delete=models.CASCADE)
|
||||||
price = models.DecimalField(max_digits=7, decimal_places=2, null=True, blank=True)
|
price = models.DecimalField(max_digits=13, decimal_places=2, null=True, blank=True)
|
||||||
disabled = models.BooleanField(default=False, verbose_name=_('Disable product for this date'))
|
disabled = models.BooleanField(default=False, verbose_name=_('Disable product for this date'))
|
||||||
available_from = models.DateTimeField(
|
available_from = models.DateTimeField(
|
||||||
verbose_name=_("Available from"),
|
verbose_name=_("Available from"),
|
||||||
@@ -220,7 +220,7 @@ class SubEventItemVariation(models.Model):
|
|||||||
"""
|
"""
|
||||||
subevent = models.ForeignKey('SubEvent', on_delete=models.CASCADE)
|
subevent = models.ForeignKey('SubEvent', on_delete=models.CASCADE)
|
||||||
variation = models.ForeignKey('ItemVariation', on_delete=models.CASCADE)
|
variation = models.ForeignKey('ItemVariation', on_delete=models.CASCADE)
|
||||||
price = models.DecimalField(max_digits=7, decimal_places=2, null=True, blank=True)
|
price = models.DecimalField(max_digits=13, decimal_places=2, null=True, blank=True)
|
||||||
disabled = models.BooleanField(default=False, verbose_name=_('Disable product for this date'))
|
disabled = models.BooleanField(default=False, verbose_name=_('Disable product for this date'))
|
||||||
available_from = models.DateTimeField(
|
available_from = models.DateTimeField(
|
||||||
verbose_name=_("Available from"),
|
verbose_name=_("Available from"),
|
||||||
@@ -407,7 +407,7 @@ class Item(LoggedModel):
|
|||||||
help_text=_("If this product has multiple variations, you can set different prices for each of the "
|
help_text=_("If this product has multiple variations, you can set different prices for each of the "
|
||||||
"variations. If a variation does not have a special price or if you do not have variations, "
|
"variations. If a variation does not have a special price or if you do not have variations, "
|
||||||
"this price will be used."),
|
"this price will be used."),
|
||||||
max_digits=7, decimal_places=2, null=True
|
max_digits=13, decimal_places=2, null=True
|
||||||
)
|
)
|
||||||
free_price = models.BooleanField(
|
free_price = models.BooleanField(
|
||||||
default=False,
|
default=False,
|
||||||
@@ -538,7 +538,7 @@ class Item(LoggedModel):
|
|||||||
original_price = models.DecimalField(
|
original_price = models.DecimalField(
|
||||||
verbose_name=_('Original price'),
|
verbose_name=_('Original price'),
|
||||||
blank=True, null=True,
|
blank=True, null=True,
|
||||||
max_digits=7, decimal_places=2,
|
max_digits=13, decimal_places=2,
|
||||||
help_text=_('If set, this will be displayed next to the current price to show that the current price is a '
|
help_text=_('If set, this will be displayed next to the current price to show that the current price is a '
|
||||||
'discounted one. This is just a cosmetic setting and will not actually impact pricing.')
|
'discounted one. This is just a cosmetic setting and will not actually impact pricing.')
|
||||||
)
|
)
|
||||||
@@ -952,14 +952,14 @@ class ItemVariation(models.Model):
|
|||||||
verbose_name=_("Position")
|
verbose_name=_("Position")
|
||||||
)
|
)
|
||||||
default_price = models.DecimalField(
|
default_price = models.DecimalField(
|
||||||
decimal_places=2, max_digits=7,
|
decimal_places=2, max_digits=13,
|
||||||
null=True, blank=True,
|
null=True, blank=True,
|
||||||
verbose_name=_("Default price"),
|
verbose_name=_("Default price"),
|
||||||
)
|
)
|
||||||
original_price = models.DecimalField(
|
original_price = models.DecimalField(
|
||||||
verbose_name=_('Original price'),
|
verbose_name=_('Original price'),
|
||||||
blank=True, null=True,
|
blank=True, null=True,
|
||||||
max_digits=7, decimal_places=2,
|
max_digits=13, decimal_places=2,
|
||||||
help_text=_('If set, this will be displayed next to the current price to show that the current price is a '
|
help_text=_('If set, this will be displayed next to the current price to show that the current price is a '
|
||||||
'discounted one. This is just a cosmetic setting and will not actually impact pricing.')
|
'discounted one. This is just a cosmetic setting and will not actually impact pricing.')
|
||||||
)
|
)
|
||||||
@@ -1304,7 +1304,7 @@ class ItemBundle(models.Model):
|
|||||||
)
|
)
|
||||||
designated_price = models.DecimalField(
|
designated_price = models.DecimalField(
|
||||||
default=Decimal('0.00'), blank=True,
|
default=Decimal('0.00'), blank=True,
|
||||||
decimal_places=2, max_digits=10,
|
decimal_places=2, max_digits=13,
|
||||||
verbose_name=_('Designated price part'),
|
verbose_name=_('Designated price part'),
|
||||||
help_text=_('If set, it will be shown that this bundled item is responsible for the given value of the total '
|
help_text=_('If set, it will be shown that this bundled item is responsible for the given value of the total '
|
||||||
'gross price. This might be important in cases of mixed taxation, but can be kept blank otherwise. This '
|
'gross price. This might be important in cases of mixed taxation, but can be kept blank otherwise. This '
|
||||||
|
|||||||
@@ -220,7 +220,7 @@ class Order(LockModel, LoggedModel):
|
|||||||
verbose_name=_("Expiration date")
|
verbose_name=_("Expiration date")
|
||||||
)
|
)
|
||||||
total = models.DecimalField(
|
total = models.DecimalField(
|
||||||
decimal_places=2, max_digits=10,
|
decimal_places=2, max_digits=13,
|
||||||
verbose_name=_("Total amount")
|
verbose_name=_("Total amount")
|
||||||
)
|
)
|
||||||
comment = models.TextField(
|
comment = models.TextField(
|
||||||
@@ -403,8 +403,8 @@ class Order(LockModel, LoggedModel):
|
|||||||
state__in=(OrderRefund.REFUND_STATE_CREATED, OrderRefund.REFUND_STATE_TRANSIT),
|
state__in=(OrderRefund.REFUND_STATE_CREATED, OrderRefund.REFUND_STATE_TRANSIT),
|
||||||
order=OuterRef('pk')
|
order=OuterRef('pk')
|
||||||
)
|
)
|
||||||
payment_sum_sq = Subquery(payment_sum, output_field=models.DecimalField(decimal_places=2, max_digits=10))
|
payment_sum_sq = Subquery(payment_sum, output_field=models.DecimalField(decimal_places=2, max_digits=13))
|
||||||
refund_sum_sq = Subquery(refund_sum, output_field=models.DecimalField(decimal_places=2, max_digits=10))
|
refund_sum_sq = Subquery(refund_sum, output_field=models.DecimalField(decimal_places=2, max_digits=13))
|
||||||
if sums:
|
if sums:
|
||||||
qs = qs.annotate(
|
qs = qs.annotate(
|
||||||
payment_sum=payment_sum_sq,
|
payment_sum=payment_sum_sq,
|
||||||
@@ -626,7 +626,10 @@ class Order(LockModel, LoggedModel):
|
|||||||
has_checkin=Exists(Checkin.objects.filter(position_id=OuterRef('pk')))
|
has_checkin=Exists(Checkin.objects.filter(position_id=OuterRef('pk')))
|
||||||
).select_related('item').prefetch_related('issued_gift_cards')
|
).select_related('item').prefetch_related('issued_gift_cards')
|
||||||
)
|
)
|
||||||
cancelable = all([op.item.allow_cancel and not op.has_checkin for op in positions])
|
if self.event.settings.change_allow_user_if_checked_in:
|
||||||
|
cancelable = all([op.item.allow_cancel for op in positions])
|
||||||
|
else:
|
||||||
|
cancelable = all([op.item.allow_cancel and not op.has_checkin for op in positions])
|
||||||
if not cancelable or not positions:
|
if not cancelable or not positions:
|
||||||
return False
|
return False
|
||||||
for op in positions:
|
for op in positions:
|
||||||
@@ -985,7 +988,7 @@ class Order(LockModel, LoggedModel):
|
|||||||
context: Dict[str, Any]=None, log_entry_type: str='pretix.event.order.email.sent',
|
context: Dict[str, Any]=None, log_entry_type: str='pretix.event.order.email.sent',
|
||||||
user: User=None, headers: dict=None, sender: str=None, invoices: list=None,
|
user: User=None, headers: dict=None, sender: str=None, invoices: list=None,
|
||||||
auth=None, attach_tickets=False, position: 'OrderPosition'=None, auto_email=True,
|
auth=None, attach_tickets=False, position: 'OrderPosition'=None, auto_email=True,
|
||||||
attach_ical=False, attach_other_files: list=None):
|
attach_ical=False, attach_other_files: list=None, attach_cached_files: list=None):
|
||||||
"""
|
"""
|
||||||
Sends an email to the user that placed this order. Basically, this method does two things:
|
Sends an email to the user that placed this order. Basically, this method does two things:
|
||||||
|
|
||||||
@@ -1030,7 +1033,7 @@ class Order(LockModel, LoggedModel):
|
|||||||
self.event, self.locale, self, headers=headers, sender=sender,
|
self.event, self.locale, self, headers=headers, sender=sender,
|
||||||
invoices=invoices, attach_tickets=attach_tickets,
|
invoices=invoices, attach_tickets=attach_tickets,
|
||||||
position=position, auto_email=auto_email, attach_ical=attach_ical,
|
position=position, auto_email=auto_email, attach_ical=attach_ical,
|
||||||
attach_other_files=attach_other_files,
|
attach_other_files=attach_other_files, attach_cached_files=attach_cached_files,
|
||||||
)
|
)
|
||||||
except SendMailException:
|
except SendMailException:
|
||||||
raise
|
raise
|
||||||
@@ -1313,7 +1316,7 @@ class AbstractPosition(models.Model):
|
|||||||
on_delete=models.PROTECT
|
on_delete=models.PROTECT
|
||||||
)
|
)
|
||||||
price = models.DecimalField(
|
price = models.DecimalField(
|
||||||
decimal_places=2, max_digits=10,
|
decimal_places=2, max_digits=13,
|
||||||
verbose_name=_("Price")
|
verbose_name=_("Price")
|
||||||
)
|
)
|
||||||
attendee_name_cached = models.CharField(
|
attendee_name_cached = models.CharField(
|
||||||
@@ -1545,7 +1548,7 @@ class OrderPayment(models.Model):
|
|||||||
max_length=190, choices=PAYMENT_STATES
|
max_length=190, choices=PAYMENT_STATES
|
||||||
)
|
)
|
||||||
amount = models.DecimalField(
|
amount = models.DecimalField(
|
||||||
decimal_places=2, max_digits=10,
|
decimal_places=2, max_digits=13,
|
||||||
verbose_name=_("Amount")
|
verbose_name=_("Amount")
|
||||||
)
|
)
|
||||||
order = models.ForeignKey(
|
order = models.ForeignKey(
|
||||||
@@ -1929,7 +1932,7 @@ class OrderRefund(models.Model):
|
|||||||
max_length=190, choices=REFUND_SOURCES
|
max_length=190, choices=REFUND_SOURCES
|
||||||
)
|
)
|
||||||
amount = models.DecimalField(
|
amount = models.DecimalField(
|
||||||
decimal_places=2, max_digits=10,
|
decimal_places=2, max_digits=13,
|
||||||
verbose_name=_("Amount")
|
verbose_name=_("Amount")
|
||||||
)
|
)
|
||||||
order = models.ForeignKey(
|
order = models.ForeignKey(
|
||||||
@@ -2078,7 +2081,7 @@ class OrderFee(models.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
value = models.DecimalField(
|
value = models.DecimalField(
|
||||||
decimal_places=2, max_digits=10,
|
decimal_places=2, max_digits=13,
|
||||||
verbose_name=_("Value")
|
verbose_name=_("Value")
|
||||||
)
|
)
|
||||||
order = models.ForeignKey(
|
order = models.ForeignKey(
|
||||||
@@ -2102,7 +2105,7 @@ class OrderFee(models.Model):
|
|||||||
null=True, blank=True
|
null=True, blank=True
|
||||||
)
|
)
|
||||||
tax_value = models.DecimalField(
|
tax_value = models.DecimalField(
|
||||||
max_digits=10, decimal_places=2,
|
max_digits=13, decimal_places=2,
|
||||||
verbose_name=_('Tax value')
|
verbose_name=_('Tax value')
|
||||||
)
|
)
|
||||||
canceled = models.BooleanField(default=False)
|
canceled = models.BooleanField(default=False)
|
||||||
@@ -2236,7 +2239,7 @@ class OrderPosition(AbstractPosition):
|
|||||||
)
|
)
|
||||||
|
|
||||||
voucher_budget_use = models.DecimalField(
|
voucher_budget_use = models.DecimalField(
|
||||||
max_digits=10, decimal_places=2, null=True, blank=True,
|
max_digits=13, decimal_places=2, null=True, blank=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
tax_rate = models.DecimalField(
|
tax_rate = models.DecimalField(
|
||||||
@@ -2249,7 +2252,7 @@ class OrderPosition(AbstractPosition):
|
|||||||
null=True, blank=True
|
null=True, blank=True
|
||||||
)
|
)
|
||||||
tax_value = models.DecimalField(
|
tax_value = models.DecimalField(
|
||||||
max_digits=10, decimal_places=2,
|
max_digits=13, decimal_places=2,
|
||||||
verbose_name=_('Tax value')
|
verbose_name=_('Tax value')
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -2555,6 +2558,27 @@ class OrderPosition(AbstractPosition):
|
|||||||
attach_tickets=True
|
attach_tickets=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
@scopes_disabled()
|
||||||
|
def attendee_change_allowed(self) -> bool:
|
||||||
|
"""
|
||||||
|
Returns whether or not this order can be changed by the attendee.
|
||||||
|
"""
|
||||||
|
from .items import ItemAddOn
|
||||||
|
|
||||||
|
if not self.event.settings.change_allow_attendee or not self.order.user_change_allowed:
|
||||||
|
return False
|
||||||
|
|
||||||
|
positions = list(
|
||||||
|
self.order.positions.filter(Q(pk=self.pk) | Q(addon_to_id=self.pk)).annotate(
|
||||||
|
has_variations=Exists(ItemVariation.objects.filter(item_id=OuterRef('item_id'))),
|
||||||
|
).select_related('item').prefetch_related('issued_gift_cards')
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
(self.order.event.settings.change_allow_user_variation and any([op.has_variations for op in positions])) or
|
||||||
|
(self.order.event.settings.change_allow_user_addons and ItemAddOn.objects.filter(base_item_id__in=[op.item_id for op in positions]).exists())
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Transaction(models.Model):
|
class Transaction(models.Model):
|
||||||
"""
|
"""
|
||||||
@@ -2649,7 +2673,7 @@ class Transaction(models.Model):
|
|||||||
verbose_name=pgettext_lazy("subevent", "Date"),
|
verbose_name=pgettext_lazy("subevent", "Date"),
|
||||||
)
|
)
|
||||||
price = models.DecimalField(
|
price = models.DecimalField(
|
||||||
decimal_places=2, max_digits=10,
|
decimal_places=2, max_digits=13,
|
||||||
verbose_name=_("Price")
|
verbose_name=_("Price")
|
||||||
)
|
)
|
||||||
tax_rate = models.DecimalField(
|
tax_rate = models.DecimalField(
|
||||||
@@ -2662,7 +2686,7 @@ class Transaction(models.Model):
|
|||||||
null=True, blank=True
|
null=True, blank=True
|
||||||
)
|
)
|
||||||
tax_value = models.DecimalField(
|
tax_value = models.DecimalField(
|
||||||
max_digits=10, decimal_places=2,
|
max_digits=13, decimal_places=2,
|
||||||
verbose_name=_('Tax value')
|
verbose_name=_('Tax value')
|
||||||
)
|
)
|
||||||
fee_type = models.CharField(
|
fee_type = models.CharField(
|
||||||
@@ -2740,19 +2764,19 @@ class CartPosition(AbstractPosition):
|
|||||||
verbose_name=_('Tax rate')
|
verbose_name=_('Tax rate')
|
||||||
)
|
)
|
||||||
listed_price = models.DecimalField(
|
listed_price = models.DecimalField(
|
||||||
decimal_places=2, max_digits=10, null=True,
|
decimal_places=2, max_digits=13, null=True,
|
||||||
)
|
)
|
||||||
price_after_voucher = models.DecimalField(
|
price_after_voucher = models.DecimalField(
|
||||||
decimal_places=2, max_digits=10, null=True,
|
decimal_places=2, max_digits=13, null=True,
|
||||||
)
|
)
|
||||||
custom_price_input = models.DecimalField(
|
custom_price_input = models.DecimalField(
|
||||||
decimal_places=2, max_digits=10, null=True,
|
decimal_places=2, max_digits=13, null=True,
|
||||||
)
|
)
|
||||||
custom_price_input_is_net = models.BooleanField(
|
custom_price_input_is_net = models.BooleanField(
|
||||||
default=False,
|
default=False,
|
||||||
)
|
)
|
||||||
line_price_gross = models.DecimalField(
|
line_price_gross = models.DecimalField(
|
||||||
decimal_places=2, max_digits=10, null=True,
|
decimal_places=2, max_digits=13, null=True,
|
||||||
)
|
)
|
||||||
requested_valid_from = models.DateTimeField(
|
requested_valid_from = models.DateTimeField(
|
||||||
null=True,
|
null=True,
|
||||||
@@ -2822,7 +2846,7 @@ class CartPosition(AbstractPosition):
|
|||||||
# Migrate from pre-discounts position
|
# Migrate from pre-discounts position
|
||||||
if self.item.free_price and self.custom_price_input is None:
|
if self.item.free_price and self.custom_price_input is None:
|
||||||
custom_price = self.price
|
custom_price = self.price
|
||||||
if custom_price > 100000000:
|
if custom_price > 99_999_999_999:
|
||||||
raise ValueError('price_too_high')
|
raise ValueError('price_too_high')
|
||||||
self.custom_price_input = custom_price
|
self.custom_price_input = custom_price
|
||||||
self.custom_price_input_is_net = not False
|
self.custom_price_input_is_net = not False
|
||||||
@@ -3035,7 +3059,7 @@ class CachedCombinedTicket(models.Model):
|
|||||||
class CancellationRequest(models.Model):
|
class CancellationRequest(models.Model):
|
||||||
order = models.ForeignKey(Order, on_delete=models.CASCADE, related_name='cancellation_requests')
|
order = models.ForeignKey(Order, on_delete=models.CASCADE, related_name='cancellation_requests')
|
||||||
created = models.DateTimeField(auto_now_add=True)
|
created = models.DateTimeField(auto_now_add=True)
|
||||||
cancellation_fee = models.DecimalField(max_digits=10, decimal_places=2)
|
cancellation_fee = models.DecimalField(max_digits=13, decimal_places=2)
|
||||||
refund_as_giftcard = models.BooleanField(default=False)
|
refund_as_giftcard = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -213,7 +213,7 @@ class Voucher(LoggedModel):
|
|||||||
verbose_name=_("Maximum discount budget"),
|
verbose_name=_("Maximum discount budget"),
|
||||||
help_text=_("This is the maximum monetary amount that will be discounted using this voucher across all usages. "
|
help_text=_("This is the maximum monetary amount that will be discounted using this voucher across all usages. "
|
||||||
"If this is sum reached, the voucher can no longer be used."),
|
"If this is sum reached, the voucher can no longer be used."),
|
||||||
decimal_places=2, max_digits=10,
|
decimal_places=2, max_digits=13,
|
||||||
null=True, blank=True
|
null=True, blank=True
|
||||||
)
|
)
|
||||||
valid_until = models.DateTimeField(
|
valid_until = models.DateTimeField(
|
||||||
@@ -243,7 +243,7 @@ class Voucher(LoggedModel):
|
|||||||
)
|
)
|
||||||
value = models.DecimalField(
|
value = models.DecimalField(
|
||||||
verbose_name=_("Voucher value"),
|
verbose_name=_("Voucher value"),
|
||||||
decimal_places=2, max_digits=10, null=True, blank=True,
|
decimal_places=2, max_digits=13, null=True, blank=True,
|
||||||
)
|
)
|
||||||
item = models.ForeignKey(
|
item = models.ForeignKey(
|
||||||
Item, related_name='vouchers',
|
Item, related_name='vouchers',
|
||||||
@@ -599,7 +599,7 @@ class Voucher(LoggedModel):
|
|||||||
Order.STATUS_PENDING
|
Order.STATUS_PENDING
|
||||||
]
|
]
|
||||||
).order_by().values('voucher_id').annotate(s=Sum('voucher_budget_use')).values('s')
|
).order_by().values('voucher_id').annotate(s=Sum('voucher_budget_use')).values('s')
|
||||||
return qs.annotate(budget_used_orders=Coalesce(Subquery(opq, output_field=models.DecimalField(max_digits=10, decimal_places=2)), Decimal('0.00')))
|
return qs.annotate(budget_used_orders=Coalesce(Subquery(opq, output_field=models.DecimalField(max_digits=13, decimal_places=2)), Decimal('0.00')))
|
||||||
|
|
||||||
def budget_used(self):
|
def budget_used(self):
|
||||||
ops = OrderPosition.objects.filter(
|
ops = OrderPosition.objects.filter(
|
||||||
|
|||||||
@@ -188,6 +188,9 @@ class WaitingListEntry(LoggedModel):
|
|||||||
raise WaitingListException(_('This entry is anonymized and can no longer be used.'))
|
raise WaitingListException(_('This entry is anonymized and can no longer be used.'))
|
||||||
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
|
e = self.email
|
||||||
|
if self.name:
|
||||||
|
e += ' / ' + self.name
|
||||||
v = Voucher.objects.create(
|
v = Voucher.objects.create(
|
||||||
event=self.event,
|
event=self.event,
|
||||||
max_usages=1,
|
max_usages=1,
|
||||||
@@ -196,7 +199,7 @@ class WaitingListEntry(LoggedModel):
|
|||||||
variation=self.variation,
|
variation=self.variation,
|
||||||
tag='waiting-list',
|
tag='waiting-list',
|
||||||
comment=_('Automatically created from waiting list entry for {email}').format(
|
comment=_('Automatically created from waiting list entry for {email}').format(
|
||||||
email=self.email
|
email=e
|
||||||
),
|
),
|
||||||
block_quota=True,
|
block_quota=True,
|
||||||
subevent=self.subevent,
|
subevent=self.subevent,
|
||||||
|
|||||||
@@ -1125,6 +1125,12 @@ class ManualPayment(BasePaymentProvider):
|
|||||||
widget=I18nTextarea,
|
widget=I18nTextarea,
|
||||||
validators=[PlaceholderValidator(['{order}', '{amount}', '{currency}', '{amount_with_currency}'])],
|
validators=[PlaceholderValidator(['{order}', '{amount}', '{currency}', '{amount_with_currency}'])],
|
||||||
)),
|
)),
|
||||||
|
('invoice_immediately',
|
||||||
|
forms.BooleanField(
|
||||||
|
label=_('Create an invoice for orders using bank transfer immediately if the event is otherwise '
|
||||||
|
'configured to create invoices after payment is completed.'),
|
||||||
|
required=False,
|
||||||
|
)),
|
||||||
] + list(super().settings_form_fields.items())
|
] + list(super().settings_form_fields.items())
|
||||||
)
|
)
|
||||||
d.move_to_end('_enabled', last=False)
|
d.move_to_end('_enabled', last=False)
|
||||||
@@ -1164,6 +1170,10 @@ class ManualPayment(BasePaymentProvider):
|
|||||||
format_map(self.settings.get('pending_description', as_type=LazyI18nString), self.format_map(payment.order, payment))
|
format_map(self.settings.get('pending_description', as_type=LazyI18nString), self.format_map(payment.order, payment))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def requires_invoice_immediately(self):
|
||||||
|
return self.settings.get('invoice_immediately', False, as_type=bool)
|
||||||
|
|
||||||
|
|
||||||
class OffsettingProvider(BasePaymentProvider):
|
class OffsettingProvider(BasePaymentProvider):
|
||||||
is_enabled = True
|
is_enabled = True
|
||||||
|
|||||||
@@ -777,16 +777,16 @@ class Renderer:
|
|||||||
qr_y = float(o['bottom']) * mm
|
qr_y = float(o['bottom']) * mm
|
||||||
renderPDF.draw(d, canvas, qr_x, qr_y)
|
renderPDF.draw(d, canvas, qr_x, qr_y)
|
||||||
|
|
||||||
# Add QR content + PDF issuer as a hidden string (fully transparent & off page)
|
# Add QR content + PDF issuer as a hidden string (fully transparent & very very small)
|
||||||
# This helps automated processing of the PDF file by 3rd parties, e.g. when checking tickets for resale
|
# This helps automated processing of the PDF file by 3rd parties, e.g. when checking tickets for resale
|
||||||
data = {
|
data = {
|
||||||
"issuer": settings.SITE_URL,
|
"issuer": settings.SITE_URL,
|
||||||
o.get('content', 'secret'): content
|
o.get('content', 'secret'): content
|
||||||
}
|
}
|
||||||
canvas.saveState()
|
canvas.saveState()
|
||||||
canvas.setFont('Open Sans', 1)
|
canvas.setFont('Open Sans', .01)
|
||||||
canvas.setFillColorRGB(0, 0, 0, 0)
|
canvas.setFillColorRGB(0, 0, 0, 0)
|
||||||
canvas.drawString(-1000 * mm, -1000 * mm, json.dumps(data, sort_keys=True))
|
canvas.drawString(0 * mm, 0 * mm, json.dumps(data, sort_keys=True))
|
||||||
canvas.restoreState()
|
canvas.restoreState()
|
||||||
|
|
||||||
def _get_ev(self, op, order):
|
def _get_ev(self, op, order):
|
||||||
|
|||||||
@@ -726,7 +726,7 @@ class CartManager:
|
|||||||
custom_price = None
|
custom_price = None
|
||||||
if item.free_price and i.get('price'):
|
if item.free_price and i.get('price'):
|
||||||
custom_price = Decimal(str(i.get('price')).replace(",", "."))
|
custom_price = Decimal(str(i.get('price')).replace(",", "."))
|
||||||
if custom_price > 100000000:
|
if custom_price > 99_999_999_999:
|
||||||
raise ValueError('price_too_high')
|
raise ValueError('price_too_high')
|
||||||
|
|
||||||
op = self.AddOperation(
|
op = self.AddOperation(
|
||||||
@@ -841,7 +841,7 @@ class CartManager:
|
|||||||
custom_price = None
|
custom_price = None
|
||||||
if item.free_price and a.get('price'):
|
if item.free_price and a.get('price'):
|
||||||
custom_price = Decimal(str(a.get('price')).replace(",", "."))
|
custom_price = Decimal(str(a.get('price')).replace(",", "."))
|
||||||
if custom_price > 100000000:
|
if custom_price > 99_999_999_999:
|
||||||
raise ValueError('price_too_high')
|
raise ValueError('price_too_high')
|
||||||
|
|
||||||
# Fix positions with wrong price (TODO: happens out-of-cartmanager-transaction and therefore a little hacky)
|
# Fix positions with wrong price (TODO: happens out-of-cartmanager-transaction and therefore a little hacky)
|
||||||
@@ -1141,7 +1141,7 @@ class CartManager:
|
|||||||
custom_price_input=op.custom_price_input,
|
custom_price_input=op.custom_price_input,
|
||||||
custom_price_input_is_net=op.custom_price_input_is_net,
|
custom_price_input_is_net=op.custom_price_input_is_net,
|
||||||
line_price_gross=line_price.gross,
|
line_price_gross=line_price.gross,
|
||||||
tax_rate=line_price.tax,
|
tax_rate=line_price.rate,
|
||||||
price=line_price.gross,
|
price=line_price.gross,
|
||||||
)
|
)
|
||||||
if self.event.settings.attendee_names_asked:
|
if self.event.settings.attendee_names_asked:
|
||||||
@@ -1188,7 +1188,7 @@ class CartManager:
|
|||||||
custom_price_input=b.custom_price_input,
|
custom_price_input=b.custom_price_input,
|
||||||
custom_price_input_is_net=b.custom_price_input_is_net,
|
custom_price_input_is_net=b.custom_price_input_is_net,
|
||||||
line_price_gross=bline_price.gross,
|
line_price_gross=bline_price.gross,
|
||||||
tax_rate=bline_price.tax,
|
tax_rate=bline_price.rate,
|
||||||
price=bline_price.gross,
|
price=bline_price.gross,
|
||||||
is_bundled=True
|
is_bundled=True
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -47,7 +47,8 @@ from django.core.cache import cache
|
|||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import (
|
from django.db.models import (
|
||||||
Count, Exists, F, IntegerField, Max, Min, OuterRef, Q, Sum, Value,
|
Count, Exists, F, IntegerField, Max, Min, OuterRef, Q, QuerySet, Sum,
|
||||||
|
Value,
|
||||||
)
|
)
|
||||||
from django.db.models.functions import Coalesce, Greatest
|
from django.db.models.functions import Coalesce, Greatest
|
||||||
from django.db.transaction import get_connection
|
from django.db.transaction import get_connection
|
||||||
@@ -188,6 +189,7 @@ error_messages = {
|
|||||||
'min'
|
'min'
|
||||||
),
|
),
|
||||||
'addon_no_multi': gettext_lazy('You can select every add-ons from the category %(cat)s for the product %(base)s at most once.'),
|
'addon_no_multi': gettext_lazy('You can select every add-ons from the category %(cat)s for the product %(base)s at most once.'),
|
||||||
|
'addon_already_checked_in': gettext_lazy('You cannot remove the position %(addon)s since it has already been checked in.'),
|
||||||
}
|
}
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -1607,6 +1609,7 @@ class OrderChangeManager:
|
|||||||
override_tax_rate=new_rate)
|
override_tax_rate=new_rate)
|
||||||
self._totaldiff += new_tax.gross - pos.price
|
self._totaldiff += new_tax.gross - pos.price
|
||||||
self._operations.append(self.PriceOperation(pos, new_tax, new_tax.gross - pos.price))
|
self._operations.append(self.PriceOperation(pos, new_tax, new_tax.gross - pos.price))
|
||||||
|
self._invoice_dirty = True
|
||||||
|
|
||||||
def cancel_fee(self, fee: OrderFee):
|
def cancel_fee(self, fee: OrderFee):
|
||||||
self._totaldiff -= fee.value
|
self._totaldiff -= fee.value
|
||||||
@@ -1701,7 +1704,19 @@ class OrderChangeManager:
|
|||||||
for a in position.addons.all():
|
for a in position.addons.all():
|
||||||
self._operations.append(self.SplitOperation(a))
|
self._operations.append(self.SplitOperation(a))
|
||||||
|
|
||||||
def set_addons(self, addons):
|
def set_addons(self, addons, limit_main_positions=None):
|
||||||
|
"""
|
||||||
|
This is a convenience method to change the add-on products selected on an order. The input structure is similar
|
||||||
|
to CartManager.set_addons. It will automatically compute the correct operations to add, cancel, or change
|
||||||
|
positions on the order. Every existing add-on not in the input will be canceled. Availability of the
|
||||||
|
products is validated (with some exceptions).
|
||||||
|
|
||||||
|
:param addons: A list of dictionaries with the keys ``"addon_to"``, ``"item"``, ``"variation"`` (all ID values),
|
||||||
|
``"count"``, and ``"price"``.
|
||||||
|
:param limit_main_positions: By default, the method works on all methods of the order. If you set this to a
|
||||||
|
queryset or a list of positions, all other positions and their add-ons will be kept
|
||||||
|
untouched.
|
||||||
|
"""
|
||||||
if self._operations:
|
if self._operations:
|
||||||
raise ValueError("Setting addons should be the first/only operation")
|
raise ValueError("Setting addons should be the first/only operation")
|
||||||
|
|
||||||
@@ -1713,7 +1728,13 @@ class OrderChangeManager:
|
|||||||
quota_diff = Counter() # Quota -> Number of usages
|
quota_diff = Counter() # Quota -> Number of usages
|
||||||
available_categories = defaultdict(set) # OrderPos -> Category IDs to choose from
|
available_categories = defaultdict(set) # OrderPos -> Category IDs to choose from
|
||||||
price_included = defaultdict(dict) # OrderPos -> CategoryID -> bool(price is included)
|
price_included = defaultdict(dict) # OrderPos -> CategoryID -> bool(price is included)
|
||||||
toplevel_op = self.order.positions.filter(
|
if isinstance(limit_main_positions, QuerySet):
|
||||||
|
toplevel_qs = limit_main_positions
|
||||||
|
elif limit_main_positions is not None:
|
||||||
|
toplevel_qs = self.order.positions.filter(pk__in=[p.pk for p in limit_main_positions])
|
||||||
|
else:
|
||||||
|
toplevel_qs = self.order.positions
|
||||||
|
toplevel_op = toplevel_qs.filter(
|
||||||
addon_to__isnull=True
|
addon_to__isnull=True
|
||||||
).prefetch_related(
|
).prefetch_related(
|
||||||
'addons', 'item__addons', 'item__addons__addon_category'
|
'addons', 'item__addons', 'item__addons__addon_category'
|
||||||
@@ -1877,6 +1898,12 @@ class OrderChangeManager:
|
|||||||
for a in current_addons[cp][k][:current_num - input_num]:
|
for a in current_addons[cp][k][:current_num - input_num]:
|
||||||
if a.canceled:
|
if a.canceled:
|
||||||
continue
|
continue
|
||||||
|
if a.checkins.exists():
|
||||||
|
raise OrderError(
|
||||||
|
error_messages['addon_already_checked_in'] % {
|
||||||
|
'addon': str(a.item.name),
|
||||||
|
}
|
||||||
|
)
|
||||||
self.cancel(a)
|
self.cancel(a)
|
||||||
|
|
||||||
def _check_seats(self):
|
def _check_seats(self):
|
||||||
@@ -2516,7 +2543,7 @@ class OrderChangeManager:
|
|||||||
positions_to_fake_cart[op.position].seat = op.seat
|
positions_to_fake_cart[op.position].seat = op.seat
|
||||||
elif isinstance(op, self.MembershipOperation):
|
elif isinstance(op, self.MembershipOperation):
|
||||||
positions_to_fake_cart[op.position].used_membership = op.membership
|
positions_to_fake_cart[op.position].used_membership = op.membership
|
||||||
elif isinstance(op, self.CancelOperation):
|
elif isinstance(op, self.CancelOperation) and op.position in positions_to_fake_cart:
|
||||||
fake_cart.remove(positions_to_fake_cart[op.position])
|
fake_cart.remove(positions_to_fake_cart[op.position])
|
||||||
elif isinstance(op, self.AddOperation):
|
elif isinstance(op, self.AddOperation):
|
||||||
cp = CartPosition(
|
cp = CartPosition(
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ def get_price(item: Item, variation: ItemVariation = None,
|
|||||||
elif item.free_price and custom_price is not None and custom_price != "":
|
elif item.free_price and custom_price is not None and custom_price != "":
|
||||||
if not isinstance(custom_price, Decimal):
|
if not isinstance(custom_price, Decimal):
|
||||||
custom_price = Decimal(str(custom_price).replace(",", "."))
|
custom_price = Decimal(str(custom_price).replace(",", "."))
|
||||||
if custom_price > 100000000:
|
if custom_price > 99_999_999_999:
|
||||||
raise ValueError('price_too_high')
|
raise ValueError('price_too_high')
|
||||||
|
|
||||||
price = tax_rule.tax(price, invoice_address=invoice_address)
|
price = tax_rule.tax(price, invoice_address=invoice_address)
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ def dictsum(*dicts) -> dict:
|
|||||||
|
|
||||||
def order_overview(
|
def order_overview(
|
||||||
event: Event, subevent: SubEvent=None, date_filter='', date_from=None, date_until=None, fees=False,
|
event: Event, subevent: SubEvent=None, date_filter='', date_from=None, date_until=None, fees=False,
|
||||||
admission_only=False, base_qs=None
|
admission_only=False, base_qs=None, base_fees_qs=None,
|
||||||
) -> Tuple[List[Tuple[ItemCategory, List[Item]]], Dict[str, Tuple[Decimal, Decimal]]]:
|
) -> Tuple[List[Tuple[ItemCategory, List[Item]]], Dict[str, Tuple[Decimal, Decimal]]]:
|
||||||
items = event.items.all().select_related(
|
items = event.items.all().select_related(
|
||||||
'category', # for re-grouping
|
'category', # for re-grouping
|
||||||
@@ -233,7 +233,8 @@ def order_overview(
|
|||||||
payment_items = []
|
payment_items = []
|
||||||
|
|
||||||
if subevent is None and fees:
|
if subevent is None and fees:
|
||||||
qs = OrderFee.all.filter(
|
qs = OrderFee.all if base_fees_qs is None else base_fees_qs
|
||||||
|
qs = qs.filter(
|
||||||
order__event=event
|
order__event=event
|
||||||
).annotate(
|
).annotate(
|
||||||
status=Case(
|
status=Case(
|
||||||
|
|||||||
+52
-11
@@ -63,7 +63,8 @@ from rest_framework import serializers
|
|||||||
from pretix.api.serializers.fields import (
|
from pretix.api.serializers.fields import (
|
||||||
ListMultipleChoiceField, UploadedFileField,
|
ListMultipleChoiceField, UploadedFileField,
|
||||||
)
|
)
|
||||||
from pretix.api.serializers.i18n import I18nField
|
from pretix.api.serializers.i18n import I18nField, I18nURLField
|
||||||
|
from pretix.base.forms import I18nURLFormField
|
||||||
from pretix.base.models.tax import VAT_ID_COUNTRIES, TaxRule
|
from pretix.base.models.tax import VAT_ID_COUNTRIES, TaxRule
|
||||||
from pretix.base.reldate import (
|
from pretix.base.reldate import (
|
||||||
RelativeDateField, RelativeDateTimeField, RelativeDateWrapper,
|
RelativeDateField, RelativeDateTimeField, RelativeDateWrapper,
|
||||||
@@ -1396,6 +1397,19 @@ DEFAULTS = {
|
|||||||
'serializer_class': serializers.BooleanField,
|
'serializer_class': serializers.BooleanField,
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_("Hide all unavailable dates from calendar or list views"),
|
label=_("Hide all unavailable dates from calendar or list views"),
|
||||||
|
help_text=_("This option currently only affects the calendar of this event series, not the organizer-wide "
|
||||||
|
"calendar.")
|
||||||
|
)
|
||||||
|
},
|
||||||
|
'event_calendar_future_only': {
|
||||||
|
'default': 'False',
|
||||||
|
'type': bool,
|
||||||
|
'form_class': forms.BooleanField,
|
||||||
|
'serializer_class': serializers.BooleanField,
|
||||||
|
'form_kwargs': dict(
|
||||||
|
label=_("Hide all past dates from calendar"),
|
||||||
|
help_text=_("This option currently only affects the calendar of this event series, not the organizer-wide "
|
||||||
|
"calendar.")
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
'allow_modifications_after_checkin': {
|
'allow_modifications_after_checkin': {
|
||||||
@@ -1470,6 +1484,32 @@ DEFAULTS = {
|
|||||||
label=_("Do not allow changes after"),
|
label=_("Do not allow changes after"),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
'change_allow_user_if_checked_in': {
|
||||||
|
'default': 'False',
|
||||||
|
'type': bool,
|
||||||
|
'form_class': forms.BooleanField,
|
||||||
|
'serializer_class': serializers.BooleanField,
|
||||||
|
'form_kwargs': dict(
|
||||||
|
label=_("Allow change even though the ticket has already been checked in"),
|
||||||
|
help_text=_("By default, order changes are disabled after any ticket in the order has been checked in. "
|
||||||
|
"If you check this box, this requirement is lifted. It is still not possible to remove an "
|
||||||
|
"add-on product that has already been checked in individually. Use with care, and preferably "
|
||||||
|
"only in combination with a limitation on price changes above."),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
'change_allow_attendee': {
|
||||||
|
'default': 'False',
|
||||||
|
'type': bool,
|
||||||
|
'form_class': forms.BooleanField,
|
||||||
|
'serializer_class': serializers.BooleanField,
|
||||||
|
'form_kwargs': dict(
|
||||||
|
label=_("Allow individual attendees to change their ticket"),
|
||||||
|
help_text=_("By default, only the person who ordered the tickets can make any changes. If you check this "
|
||||||
|
"box, individual attendees can also make changes. However, individual attendees can always "
|
||||||
|
"only make changes that do not change the total price of the order. Such changes can always "
|
||||||
|
"only be made by the main customer."),
|
||||||
|
)
|
||||||
|
},
|
||||||
'cancel_allow_user': {
|
'cancel_allow_user': {
|
||||||
'default': 'True',
|
'default': 'True',
|
||||||
'type': bool,
|
'type': bool,
|
||||||
@@ -1485,7 +1525,7 @@ DEFAULTS = {
|
|||||||
'form_class': forms.DecimalField,
|
'form_class': forms.DecimalField,
|
||||||
'serializer_class': serializers.DecimalField,
|
'serializer_class': serializers.DecimalField,
|
||||||
'serializer_kwargs': dict(
|
'serializer_kwargs': dict(
|
||||||
max_digits=10, decimal_places=2
|
max_digits=13, decimal_places=2
|
||||||
),
|
),
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_("Charge a fixed cancellation fee"),
|
label=_("Charge a fixed cancellation fee"),
|
||||||
@@ -1510,7 +1550,7 @@ DEFAULTS = {
|
|||||||
'form_class': forms.DecimalField,
|
'form_class': forms.DecimalField,
|
||||||
'serializer_class': serializers.DecimalField,
|
'serializer_class': serializers.DecimalField,
|
||||||
'serializer_kwargs': dict(
|
'serializer_kwargs': dict(
|
||||||
max_digits=10, decimal_places=2
|
max_digits=13, decimal_places=2
|
||||||
),
|
),
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_("Charge a percentual cancellation fee"),
|
label=_("Charge a percentual cancellation fee"),
|
||||||
@@ -1544,7 +1584,7 @@ DEFAULTS = {
|
|||||||
'form_class': forms.DecimalField,
|
'form_class': forms.DecimalField,
|
||||||
'serializer_class': serializers.DecimalField,
|
'serializer_class': serializers.DecimalField,
|
||||||
'serializer_kwargs': dict(
|
'serializer_kwargs': dict(
|
||||||
max_digits=10, decimal_places=2
|
max_digits=13, decimal_places=2
|
||||||
),
|
),
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_("Keep a fixed cancellation fee"),
|
label=_("Keep a fixed cancellation fee"),
|
||||||
@@ -1565,7 +1605,7 @@ DEFAULTS = {
|
|||||||
'form_class': forms.DecimalField,
|
'form_class': forms.DecimalField,
|
||||||
'serializer_class': serializers.DecimalField,
|
'serializer_class': serializers.DecimalField,
|
||||||
'serializer_kwargs': dict(
|
'serializer_kwargs': dict(
|
||||||
max_digits=10, decimal_places=2
|
max_digits=13, decimal_places=2
|
||||||
),
|
),
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_("Keep a percentual cancellation fee"),
|
label=_("Keep a percentual cancellation fee"),
|
||||||
@@ -1604,10 +1644,10 @@ DEFAULTS = {
|
|||||||
'form_class': forms.DecimalField,
|
'form_class': forms.DecimalField,
|
||||||
'serializer_class': serializers.DecimalField,
|
'serializer_class': serializers.DecimalField,
|
||||||
'serializer_kwargs': dict(
|
'serializer_kwargs': dict(
|
||||||
max_digits=10, decimal_places=2
|
max_digits=13, decimal_places=2
|
||||||
),
|
),
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
max_digits=10, decimal_places=2,
|
max_digits=13, decimal_places=2,
|
||||||
label=_("Step size for reduction amount"),
|
label=_("Step size for reduction amount"),
|
||||||
help_text=_('By default, customers can choose an arbitrary amount for you to keep. If you set this to e.g. '
|
help_text=_('By default, customers can choose an arbitrary amount for you to keep. If you set this to e.g. '
|
||||||
'10, they will only be able to choose values in increments of 10.')
|
'10, they will only be able to choose values in increments of 10.')
|
||||||
@@ -1689,14 +1729,15 @@ DEFAULTS = {
|
|||||||
},
|
},
|
||||||
'privacy_url': {
|
'privacy_url': {
|
||||||
'default': None,
|
'default': None,
|
||||||
'type': str,
|
'type': LazyI18nString,
|
||||||
'form_class': forms.URLField,
|
'form_class': I18nURLFormField,
|
||||||
'form_kwargs': dict(
|
'form_kwargs': dict(
|
||||||
label=_("Privacy Policy URL"),
|
label=_("Privacy Policy URL"),
|
||||||
help_text=_("This should point e.g. to a part of your website that explains how you use data gathered in "
|
help_text=_("This should point e.g. to a part of your website that explains how you use data gathered in "
|
||||||
"your ticket shop."),
|
"your ticket shop."),
|
||||||
|
widget=I18nTextInput,
|
||||||
),
|
),
|
||||||
'serializer_class': serializers.URLField,
|
'serializer_class': I18nURLField,
|
||||||
},
|
},
|
||||||
'confirm_texts': {
|
'confirm_texts': {
|
||||||
'default': LazyI18nStringList(),
|
'default': LazyI18nStringList(),
|
||||||
@@ -3243,7 +3284,7 @@ COUNTRIES_WITH_STATE_IN_ADDRESS = {
|
|||||||
'CA': (['Province', 'Territory'], 'short'),
|
'CA': (['Province', 'Territory'], 'short'),
|
||||||
# 'CN': (['Province', 'Autonomous region', 'Munincipality'], 'long'),
|
# 'CN': (['Province', 'Autonomous region', 'Munincipality'], 'long'),
|
||||||
'MY': (['State'], 'long'),
|
'MY': (['State'], 'long'),
|
||||||
'MX': (['State', 'Federal District'], 'short'),
|
'MX': (['State', 'Federal district'], 'short'),
|
||||||
'US': (['State', 'Outlying area', 'District'], 'short'),
|
'US': (['State', 'Outlying area', 'District'], 'short'),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -151,16 +151,19 @@ class BaseDataShredder:
|
|||||||
|
|
||||||
def shred_log_fields(logentry, banlist=None, whitelist=None):
|
def shred_log_fields(logentry, banlist=None, whitelist=None):
|
||||||
d = logentry.parsed_data
|
d = logentry.parsed_data
|
||||||
|
shredded = False
|
||||||
if whitelist:
|
if whitelist:
|
||||||
for k, v in d.items():
|
for k, v in d.items():
|
||||||
if k not in whitelist:
|
if k not in whitelist:
|
||||||
d[k] = '█'
|
d[k] = '█'
|
||||||
|
shredded = True
|
||||||
elif banlist:
|
elif banlist:
|
||||||
for f in banlist:
|
for f in banlist:
|
||||||
if f in d:
|
if f in d:
|
||||||
d[f] = '█'
|
d[f] = '█'
|
||||||
|
shredded = True
|
||||||
logentry.data = json.dumps(d)
|
logentry.data = json.dumps(d)
|
||||||
logentry.shredded = True
|
logentry.shredded = logentry.shredded or shredded
|
||||||
logentry.save(update_fields=['data', 'shredded'])
|
logentry.save(update_fields=['data', 'shredded'])
|
||||||
|
|
||||||
|
|
||||||
@@ -380,9 +383,10 @@ class QuestionAnswerShredder(BaseDataShredder):
|
|||||||
d = le.parsed_data
|
d = le.parsed_data
|
||||||
if 'data' in d:
|
if 'data' in d:
|
||||||
for i, row in enumerate(d['data']):
|
for i, row in enumerate(d['data']):
|
||||||
for f in row:
|
if isinstance(d['data'], list):
|
||||||
if f not in ('attendee_name', 'attendee_email'):
|
for f in row:
|
||||||
d['data'][i][f] = '█'
|
if f not in ('attendee_name', 'attendee_email'):
|
||||||
|
d['data'][i][f] = '█'
|
||||||
le.data = json.dumps(d)
|
le.data = json.dumps(d)
|
||||||
le.shredded = True
|
le.shredded = True
|
||||||
le.save(update_fields=['data', 'shredded'])
|
le.save(update_fields=['data', 'shredded'])
|
||||||
|
|||||||
@@ -511,6 +511,7 @@ class EventSettingsForm(SettingsForm):
|
|||||||
'low_availability_percentage',
|
'low_availability_percentage',
|
||||||
'event_list_type',
|
'event_list_type',
|
||||||
'event_list_available_only',
|
'event_list_available_only',
|
||||||
|
'event_calendar_future_only',
|
||||||
'frontpage_text',
|
'frontpage_text',
|
||||||
'event_info_text',
|
'event_info_text',
|
||||||
'attendee_names_asked',
|
'attendee_names_asked',
|
||||||
@@ -602,6 +603,7 @@ class EventSettingsForm(SettingsForm):
|
|||||||
del self.fields['frontpage_subevent_ordering']
|
del self.fields['frontpage_subevent_ordering']
|
||||||
del self.fields['event_list_type']
|
del self.fields['event_list_type']
|
||||||
del self.fields['event_list_available_only']
|
del self.fields['event_list_available_only']
|
||||||
|
del self.fields['event_calendar_future_only']
|
||||||
|
|
||||||
# create "virtual" fields for better UX when editing <name>_asked and <name>_required fields
|
# create "virtual" fields for better UX when editing <name>_asked and <name>_required fields
|
||||||
self.virtual_keys = []
|
self.virtual_keys = []
|
||||||
@@ -688,6 +690,8 @@ class CancelSettingsForm(SettingsForm):
|
|||||||
'change_allow_user_price',
|
'change_allow_user_price',
|
||||||
'change_allow_user_until',
|
'change_allow_user_until',
|
||||||
'change_allow_user_addons',
|
'change_allow_user_addons',
|
||||||
|
'change_allow_user_if_checked_in',
|
||||||
|
'change_allow_attendee',
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
@@ -1213,6 +1217,7 @@ class MailSettingsForm(SettingsForm):
|
|||||||
'mail_subject_order_canceled': ['event', 'order', 'comment'],
|
'mail_subject_order_canceled': ['event', 'order', 'comment'],
|
||||||
'mail_text_order_expire_warning': ['event', 'order'],
|
'mail_text_order_expire_warning': ['event', 'order'],
|
||||||
'mail_subject_order_expire_warning': ['event', 'order'],
|
'mail_subject_order_expire_warning': ['event', 'order'],
|
||||||
|
'mail_text_order_pending_warning': ['event', 'order'],
|
||||||
'mail_subject_order_pending_warning': ['event', 'order'],
|
'mail_subject_order_pending_warning': ['event', 'order'],
|
||||||
'mail_text_order_incomplete_payment': ['event', 'order', 'pending_sum'],
|
'mail_text_order_incomplete_payment': ['event', 'order', 'pending_sum'],
|
||||||
'mail_subject_order_incomplete_payment': ['event', 'order'],
|
'mail_subject_order_incomplete_payment': ['event', 'order'],
|
||||||
@@ -1374,7 +1379,10 @@ class TaxRuleLineForm(I18nForm):
|
|||||||
invoice_text = I18nFormField(
|
invoice_text = I18nFormField(
|
||||||
label=_('Text on invoice'),
|
label=_('Text on invoice'),
|
||||||
required=False,
|
required=False,
|
||||||
widget=I18nTextInput
|
widget=I18nTextInput,
|
||||||
|
widget_kwargs=dict(attrs={
|
||||||
|
'placeholder': _('Text on invoice'),
|
||||||
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -1564,7 +1572,7 @@ class QuickSetupProductForm(I18nForm):
|
|||||||
)
|
)
|
||||||
default_price = forms.DecimalField(
|
default_price = forms.DecimalField(
|
||||||
label=_("Price (optional)"),
|
label=_("Price (optional)"),
|
||||||
max_digits=7, decimal_places=2, required=False,
|
max_digits=13, decimal_places=2, required=False,
|
||||||
localize=True,
|
localize=True,
|
||||||
widget=forms.TextInput(
|
widget=forms.TextInput(
|
||||||
attrs={
|
attrs={
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ from pretix.base.models import (
|
|||||||
SubEventMetaValue, Team, TeamAPIToken, TeamInvite, Voucher,
|
SubEventMetaValue, Team, TeamAPIToken, TeamInvite, Voucher,
|
||||||
)
|
)
|
||||||
from pretix.base.signals import register_payment_providers
|
from pretix.base.signals import register_payment_providers
|
||||||
from pretix.control.forms.widgets import Select2
|
from pretix.control.forms.widgets import Select2, Select2ItemVarQuota
|
||||||
from pretix.control.signals import order_search_filter_q
|
from pretix.control.signals import order_search_filter_q
|
||||||
from pretix.helpers.countries import CachedCountries
|
from pretix.helpers.countries import CachedCountries
|
||||||
from pretix.helpers.database import (
|
from pretix.helpers.database import (
|
||||||
@@ -383,8 +383,6 @@ class OrderFilterForm(FilterForm):
|
|||||||
|
|
||||||
if fdata.get('ordering'):
|
if fdata.get('ordering'):
|
||||||
qs = qs.order_by(*get_deterministic_ordering(Order, self.get_order_by()))
|
qs = qs.order_by(*get_deterministic_ordering(Order, self.get_order_by()))
|
||||||
else:
|
|
||||||
qs = qs.order_by('-datetime', '-pk')
|
|
||||||
|
|
||||||
if fdata.get('provider'):
|
if fdata.get('provider'):
|
||||||
qs = qs.annotate(
|
qs = qs.annotate(
|
||||||
@@ -2204,7 +2202,30 @@ class CheckinFilterForm(FilterForm):
|
|||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
self.fields['device'].queryset = self.event.organizer.devices.all().order_by('device_id')
|
self.fields['device'].queryset = self.event.organizer.devices.all().order_by('device_id')
|
||||||
|
self.fields['device'].widget = Select2(
|
||||||
|
attrs={
|
||||||
|
'data-model-select2': 'generic',
|
||||||
|
'data-select2-url': reverse('control:organizer.devices.select2', kwargs={
|
||||||
|
'organizer': self.event.organizer.slug,
|
||||||
|
}),
|
||||||
|
'data-placeholder': _('All devices'),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.fields['device'].widget.choices = self.fields['device'].choices
|
||||||
|
self.fields['device'].label = _('Device')
|
||||||
|
|
||||||
self.fields['gate'].queryset = self.event.organizer.gates.all()
|
self.fields['gate'].queryset = self.event.organizer.gates.all()
|
||||||
|
self.fields['gate'].widget = Select2(
|
||||||
|
attrs={
|
||||||
|
'data-model-select2': 'generic',
|
||||||
|
'data-select2-url': reverse('control:organizer.gates.select2', kwargs={
|
||||||
|
'organizer': self.event.organizer.slug,
|
||||||
|
}),
|
||||||
|
'data-placeholder': _('All gates'),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.fields['gate'].widget.choices = self.fields['gate'].choices
|
||||||
|
self.fields['gate'].label = _('Gate')
|
||||||
|
|
||||||
self.fields['checkin_list'].queryset = self.event.checkin_lists.all()
|
self.fields['checkin_list'].queryset = self.event.checkin_lists.all()
|
||||||
self.fields['checkin_list'].widget = Select2(
|
self.fields['checkin_list'].widget = Select2(
|
||||||
@@ -2231,6 +2252,20 @@ class CheckinFilterForm(FilterForm):
|
|||||||
choices.append((str(i.pk), str(i)))
|
choices.append((str(i.pk), str(i)))
|
||||||
self.fields['itemvar'].choices = choices
|
self.fields['itemvar'].choices = choices
|
||||||
|
|
||||||
|
self.fields['itemvar'].choices = choices
|
||||||
|
self.fields['itemvar'].widget = Select2ItemVarQuota(
|
||||||
|
attrs={
|
||||||
|
'data-model-select2': 'generic',
|
||||||
|
'data-select2-url': reverse('control:event.items.itemvar.select2', kwargs={
|
||||||
|
'event': self.event.slug,
|
||||||
|
'organizer': self.event.organizer.slug,
|
||||||
|
}),
|
||||||
|
'data-placeholder': _('All products')
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.fields['itemvar'].required = False
|
||||||
|
self.fields['itemvar'].widget.choices = self.fields['itemvar'].choices
|
||||||
|
|
||||||
def filter_qs(self, qs):
|
def filter_qs(self, qs):
|
||||||
fdata = self.cleaned_data
|
fdata = self.cleaned_data
|
||||||
|
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ class CancelForm(ForceQuotaConfirmationForm):
|
|||||||
)
|
)
|
||||||
cancellation_fee = forms.DecimalField(
|
cancellation_fee = forms.DecimalField(
|
||||||
required=False,
|
required=False,
|
||||||
max_digits=10, decimal_places=2,
|
max_digits=13, decimal_places=2,
|
||||||
localize=True,
|
localize=True,
|
||||||
label=_('Keep a cancellation fee of'),
|
label=_('Keep a cancellation fee of'),
|
||||||
help_text=_('If you keep a fee, all positions within this order will be canceled and the order will be reduced '
|
help_text=_('If you keep a fee, all positions within this order will be canceled and the order will be reduced '
|
||||||
@@ -213,7 +213,7 @@ class MarkPaidForm(ConfirmPaymentForm):
|
|||||||
)
|
)
|
||||||
amount = forms.DecimalField(
|
amount = forms.DecimalField(
|
||||||
required=True,
|
required=True,
|
||||||
max_digits=10, decimal_places=2,
|
max_digits=13, decimal_places=2,
|
||||||
localize=True,
|
localize=True,
|
||||||
label=_('Payment amount'),
|
label=_('Payment amount'),
|
||||||
)
|
)
|
||||||
@@ -318,7 +318,7 @@ class OrderPositionAddForm(forms.Form):
|
|||||||
)
|
)
|
||||||
price = forms.DecimalField(
|
price = forms.DecimalField(
|
||||||
required=False,
|
required=False,
|
||||||
max_digits=10, decimal_places=2,
|
max_digits=13, decimal_places=2,
|
||||||
localize=True,
|
localize=True,
|
||||||
label=_('Gross price'),
|
label=_('Gross price'),
|
||||||
help_text=_("Including taxes, if any. Keep empty for the product's default price")
|
help_text=_("Including taxes, if any. Keep empty for the product's default price")
|
||||||
@@ -444,7 +444,7 @@ class OrderPositionChangeForm(forms.Form):
|
|||||||
)
|
)
|
||||||
price = forms.DecimalField(
|
price = forms.DecimalField(
|
||||||
required=False,
|
required=False,
|
||||||
max_digits=10, decimal_places=2,
|
max_digits=13, decimal_places=2,
|
||||||
localize=True,
|
localize=True,
|
||||||
label=_('New price (gross)')
|
label=_('New price (gross)')
|
||||||
)
|
)
|
||||||
@@ -566,7 +566,7 @@ class OrderPositionChangeForm(forms.Form):
|
|||||||
class OrderFeeChangeForm(forms.Form):
|
class OrderFeeChangeForm(forms.Form):
|
||||||
value = forms.DecimalField(
|
value = forms.DecimalField(
|
||||||
required=False,
|
required=False,
|
||||||
max_digits=10, decimal_places=2,
|
max_digits=13, decimal_places=2,
|
||||||
localize=True,
|
localize=True,
|
||||||
label=_('New price (gross)')
|
label=_('New price (gross)')
|
||||||
)
|
)
|
||||||
@@ -731,7 +731,7 @@ class OrderRefundForm(forms.Form):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
partial_amount = forms.DecimalField(
|
partial_amount = forms.DecimalField(
|
||||||
required=False, max_digits=10, decimal_places=2,
|
required=False, max_digits=13, decimal_places=2,
|
||||||
localize=True
|
localize=True
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -820,18 +820,18 @@ class EventCancelForm(forms.Form):
|
|||||||
)
|
)
|
||||||
keep_fee_fixed = forms.DecimalField(
|
keep_fee_fixed = forms.DecimalField(
|
||||||
label=_("Keep a fixed cancellation fee"),
|
label=_("Keep a fixed cancellation fee"),
|
||||||
max_digits=10, decimal_places=2,
|
max_digits=13, decimal_places=2,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
keep_fee_per_ticket = forms.DecimalField(
|
keep_fee_per_ticket = forms.DecimalField(
|
||||||
label=_("Keep a fixed cancellation fee per ticket"),
|
label=_("Keep a fixed cancellation fee per ticket"),
|
||||||
help_text=_("Free tickets and add-on products are not counted"),
|
help_text=_("Free tickets and add-on products are not counted"),
|
||||||
max_digits=10, decimal_places=2,
|
max_digits=13, decimal_places=2,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
keep_fee_percentage = forms.DecimalField(
|
keep_fee_percentage = forms.DecimalField(
|
||||||
label=_("Keep a percentual cancellation fee"),
|
label=_("Keep a percentual cancellation fee"),
|
||||||
max_digits=10, decimal_places=2,
|
max_digits=13, decimal_places=2,
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
keep_fees = forms.MultipleChoiceField(
|
keep_fees = forms.MultipleChoiceField(
|
||||||
|
|||||||
@@ -578,7 +578,8 @@ class WebHookForm(forms.ModelForm):
|
|||||||
class GiftCardCreateForm(forms.ModelForm):
|
class GiftCardCreateForm(forms.ModelForm):
|
||||||
value = forms.DecimalField(
|
value = forms.DecimalField(
|
||||||
label=_('Gift card value'),
|
label=_('Gift card value'),
|
||||||
min_value=Decimal('0.00')
|
min_value=Decimal('0.00'),
|
||||||
|
max_value=Decimal('99999999.99'),
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ class PermissionMiddleware:
|
|||||||
url = resolve(request.path_info)
|
url = resolve(request.path_info)
|
||||||
url_name = url.url_name
|
url_name = url.url_name
|
||||||
|
|
||||||
if not request.path.startswith(get_script_prefix() + 'control'):
|
if not request.path.startswith(get_script_prefix() + 'control') and not (url.namespace.startswith("api-") and url_name == "authorize"):
|
||||||
# This middleware should only touch the /control subpath
|
# This middleware should only touch the /control subpath
|
||||||
return self.get_response(request)
|
return self.get_response(request)
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,7 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="table-responsive">
|
<span class="table-responsive">
|
||||||
<table class="table table-hover table-quotas">
|
<table class="table table-hover table-quotas">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -146,6 +146,10 @@
|
|||||||
<span class="fa fa-qrcode fa-fw"></span>
|
<span class="fa fa-qrcode fa-fw"></span>
|
||||||
<span title="{{ c.raw_barcode }}">
|
<span title="{{ c.raw_barcode }}">
|
||||||
{{ c.raw_barcode|slice:":16" }}{% if c.raw_barcode|length > 16 %}…{% endif %}
|
{{ c.raw_barcode|slice:":16" }}{% if c.raw_barcode|length > 16 %}…{% endif %}
|
||||||
|
<button type="button" class="btn btn-xs btn-link btn-clipboard" data-clipboard-text="{{ c.raw_barcode }}">
|
||||||
|
<i class="fa fa-clipboard" aria-hidden="true"></i>
|
||||||
|
<span class="sr-only">{% trans "Copy to clipboard" %}</span>
|
||||||
|
</button>
|
||||||
</span>
|
</span>
|
||||||
{% if c.raw_item %}
|
{% if c.raw_item %}
|
||||||
<br>
|
<br>
|
||||||
|
|||||||
@@ -67,6 +67,8 @@
|
|||||||
{% bootstrap_field form.change_allow_user_addons layout="control" %}
|
{% bootstrap_field form.change_allow_user_addons layout="control" %}
|
||||||
{% bootstrap_field form.change_allow_user_until layout="control" %}
|
{% bootstrap_field form.change_allow_user_until layout="control" %}
|
||||||
{% bootstrap_field form.change_allow_user_price layout="control" %}
|
{% bootstrap_field form.change_allow_user_price layout="control" %}
|
||||||
|
{% bootstrap_field form.change_allow_user_if_checked_in layout="control" %}
|
||||||
|
{% bootstrap_field form.change_allow_attendee layout="control" %}
|
||||||
<div class="alert alert-info">
|
<div class="alert alert-info">
|
||||||
<p>
|
<p>
|
||||||
{% blocktrans trimmed %}
|
{% blocktrans trimmed %}
|
||||||
|
|||||||
@@ -315,7 +315,10 @@
|
|||||||
{% if sform.event_list_available_only %}
|
{% if sform.event_list_available_only %}
|
||||||
{% bootstrap_field sform.event_list_available_only layout="control" %}
|
{% bootstrap_field sform.event_list_available_only layout="control" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% bootstrap_field sform.low_availability_percentage layout="control" %}
|
{% if sform.event_calendar_future_only %}
|
||||||
|
{% bootstrap_field sform.event_calendar_future_only layout="control" %}
|
||||||
|
{% endif %}
|
||||||
|
{% bootstrap_field sform.low_availability_percentage layout="control" addon_after="%" %}
|
||||||
|
|
||||||
{% url "control:organizer.edit" organizer=request.organizer.slug as org_url %}
|
{% url "control:organizer.edit" organizer=request.organizer.slug as org_url %}
|
||||||
{% propagated request.event org_url "meta_noindex" %}
|
{% propagated request.event org_url "meta_noindex" %}
|
||||||
|
|||||||
@@ -515,6 +515,16 @@
|
|||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>{% trans "Check-in lists" %}</legend>
|
<legend>{% trans "Check-in lists" %}</legend>
|
||||||
|
<p class="help-block">
|
||||||
|
{% blocktrans trimmed %}
|
||||||
|
You can choose to either add one or more check-in lists for every date in your series individually,
|
||||||
|
or use just one check-in list for all your dates and limit admission through check-in rules. Which
|
||||||
|
approach is better depends on multiple factors, such as the number of dates in your series. For a
|
||||||
|
series with one or less event date per day, individual lists are usually more helpful. If you
|
||||||
|
use dates to represent many time slots on the same day, or even overlapping time slots, working with
|
||||||
|
just one large check-in list will be easier.
|
||||||
|
{% endblocktrans %}
|
||||||
|
</p>
|
||||||
<div class="formset" data-formset data-formset-prefix="{{ cl_formset.prefix }}">
|
<div class="formset" data-formset data-formset-prefix="{{ cl_formset.prefix }}">
|
||||||
{{ cl_formset.management_form }}
|
{{ cl_formset.management_form }}
|
||||||
{% bootstrap_formset_errors cl_formset %}
|
{% bootstrap_formset_errors cl_formset %}
|
||||||
|
|||||||
@@ -157,6 +157,16 @@
|
|||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>{% trans "Check-in lists" %}</legend>
|
<legend>{% trans "Check-in lists" %}</legend>
|
||||||
|
<p class="help-block">
|
||||||
|
{% blocktrans trimmed %}
|
||||||
|
You can choose to either add one or more check-in lists for every date in your series individually,
|
||||||
|
or use just one check-in list for all your dates and limit admission through check-in rules. Which
|
||||||
|
approach is better depends on multiple factors, such as the number of dates in your series. For a
|
||||||
|
series with one or less event date per day, individual lists are usually more helpful. If you
|
||||||
|
use dates to represent many time slots on the same day, or even overlapping time slots, working with
|
||||||
|
just one large check-in list will be easier.
|
||||||
|
{% endblocktrans %}
|
||||||
|
</p>
|
||||||
<div class="formset" data-formset data-formset-prefix="{{ cl_formset.prefix }}">
|
<div class="formset" data-formset data-formset-prefix="{{ cl_formset.prefix }}">
|
||||||
{{ cl_formset.management_form }}
|
{{ cl_formset.management_form }}
|
||||||
{% bootstrap_formset_errors cl_formset %}
|
{% bootstrap_formset_errors cl_formset %}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
{% block title %}{% trans "Voucher" %}{% endblock %}
|
{% block title %}{% trans "Voucher" %}{% endblock %}
|
||||||
{% block inside %}
|
{% block inside %}
|
||||||
<h1>{% trans "Create multiple vouchers" %}</h1>
|
<h1>{% trans "Create multiple vouchers" %}</h1>
|
||||||
<form action="" method="post" class="form-horizontal" data-asynctask>
|
<form action="" method="post" class="form-horizontal" data-asynctask mail-preview-url="{% url "control:event.vouchers.bulk.mail_preview" event=request.event.slug organizer=request.event.organizer.slug %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% bootstrap_form_errors form %}
|
{% bootstrap_form_errors form %}
|
||||||
<fieldset>
|
<fieldset>
|
||||||
@@ -82,8 +82,54 @@
|
|||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>{% trans "Send out emails" %}</legend>
|
<legend>{% trans "Send out emails" %}</legend>
|
||||||
{% bootstrap_field form.send layout="control" %}
|
{% bootstrap_field form.send layout="control" %}
|
||||||
{% bootstrap_field form.send_subject layout="horizontal" %}
|
|
||||||
{% bootstrap_field form.send_message layout="horizontal" %}
|
<div id="send_subject_panel" class="preview-panel form-group" for="send_subject">
|
||||||
|
{% with field=form.send_subject item="send_subject" %}
|
||||||
|
<label class="col-md-3 control-label">{{ field.label }}</label>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<ul class="nav nav-tabs">
|
||||||
|
<li role="presentation" class="active">
|
||||||
|
<a data-toggle="tab" type="edit" href="#{{ item }}_edit"><i class="fa fa-pencil-square-o fa-fw"></i> {% trans "Edit" %}</a>
|
||||||
|
</li>
|
||||||
|
<li role="presentation">
|
||||||
|
<a data-toggle="tab" type="preview" href="#{{ item }}_preview"><i class="fa fa-tv fa-fw"></i> {% trans "Preview" %}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="tab-content">
|
||||||
|
<div id="{{ item }}_edit" class="tab-pane fade in active">
|
||||||
|
{% bootstrap_field field show_label=False form_group_class="" %}
|
||||||
|
</div>
|
||||||
|
<div id="{{ item }}_preview" class="tab-pane mail-preview-group">
|
||||||
|
<div lang="all" for="{{ item }}" class="mail-preview"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endwith %}
|
||||||
|
</div>
|
||||||
|
<div id="send_message_panel" class="preview-panel form-group" for="send_message">
|
||||||
|
{% with field=form.send_message item="send_message" %}
|
||||||
|
<label class="col-md-3 control-label">{{ field.label }}</label>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<ul class="nav nav-tabs">
|
||||||
|
<li role="presentation" class="active">
|
||||||
|
<a data-toggle="tab" type="edit" href="#{{ item }}_edit"><i class="fa fa-pencil-square-o fa-fw"></i> {% trans "Edit" %}</a>
|
||||||
|
</li>
|
||||||
|
<li role="presentation">
|
||||||
|
<a data-toggle="tab" type="preview" href="#{{ item }}_preview"><i class="fa fa-tv fa-fw"></i> {% trans "Preview" %}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="tab-content">
|
||||||
|
<div id="{{ item }}_edit" class="tab-pane fade in active">
|
||||||
|
{% bootstrap_field field show_label=False form_group_class="" %}
|
||||||
|
</div>
|
||||||
|
<div id="{{ item }}_preview" class="tab-pane mail-preview-group">
|
||||||
|
<div lang="all" for="{{ item }}" class="mail-preview"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endwith %}
|
||||||
|
</div>
|
||||||
|
|
||||||
{% bootstrap_field form.send_recipients layout="horizontal" %}
|
{% bootstrap_field form.send_recipients layout="horizontal" %}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
{% eventsignal request.event "pretix.control.signals.voucher_form_html" form=form %}
|
{% eventsignal request.event "pretix.control.signals.voucher_form_html" form=form %}
|
||||||
|
|||||||
@@ -177,6 +177,7 @@ urlpatterns = [
|
|||||||
re_path(r'^organizer/(?P<organizer>[^/]+)/webhook/(?P<webhook>[^/]+)/logs$', organizer.WebHookLogsView.as_view(),
|
re_path(r'^organizer/(?P<organizer>[^/]+)/webhook/(?P<webhook>[^/]+)/logs$', organizer.WebHookLogsView.as_view(),
|
||||||
name='organizer.webhook.logs'),
|
name='organizer.webhook.logs'),
|
||||||
re_path(r'^organizer/(?P<organizer>[^/]+)/devices$', organizer.DeviceListView.as_view(), name='organizer.devices'),
|
re_path(r'^organizer/(?P<organizer>[^/]+)/devices$', organizer.DeviceListView.as_view(), name='organizer.devices'),
|
||||||
|
re_path(r'^organizer/(?P<organizer>[^/]+)/devices/select2$', typeahead.devices_select2, name='organizer.devices.select2'),
|
||||||
re_path(r'^organizer/(?P<organizer>[^/]+)/device/add$', organizer.DeviceCreateView.as_view(),
|
re_path(r'^organizer/(?P<organizer>[^/]+)/device/add$', organizer.DeviceCreateView.as_view(),
|
||||||
name='organizer.device.add'),
|
name='organizer.device.add'),
|
||||||
re_path(r'^organizer/(?P<organizer>[^/]+)/device/bulk_edit$', organizer.DeviceBulkUpdateView.as_view(),
|
re_path(r'^organizer/(?P<organizer>[^/]+)/device/bulk_edit$', organizer.DeviceBulkUpdateView.as_view(),
|
||||||
@@ -190,6 +191,7 @@ urlpatterns = [
|
|||||||
re_path(r'^organizer/(?P<organizer>[^/]+)/device/(?P<device>[^/]+)/logs$', organizer.DeviceLogView.as_view(),
|
re_path(r'^organizer/(?P<organizer>[^/]+)/device/(?P<device>[^/]+)/logs$', organizer.DeviceLogView.as_view(),
|
||||||
name='organizer.device.logs'),
|
name='organizer.device.logs'),
|
||||||
re_path(r'^organizer/(?P<organizer>[^/]+)/gates$', organizer.GateListView.as_view(), name='organizer.gates'),
|
re_path(r'^organizer/(?P<organizer>[^/]+)/gates$', organizer.GateListView.as_view(), name='organizer.gates'),
|
||||||
|
re_path(r'^organizer/(?P<organizer>[^/]+)/gates/select2$', typeahead.gate_select2, name='organizer.gates.select2'),
|
||||||
re_path(r'^organizer/(?P<organizer>[^/]+)/gate/add$', organizer.GateCreateView.as_view(), name='organizer.gate.add'),
|
re_path(r'^organizer/(?P<organizer>[^/]+)/gate/add$', organizer.GateCreateView.as_view(), name='organizer.gate.add'),
|
||||||
re_path(r'^organizer/(?P<organizer>[^/]+)/gate/(?P<gate>[^/]+)/edit$', organizer.GateUpdateView.as_view(),
|
re_path(r'^organizer/(?P<organizer>[^/]+)/gate/(?P<gate>[^/]+)/edit$', organizer.GateUpdateView.as_view(),
|
||||||
name='organizer.gate.edit'),
|
name='organizer.gate.edit'),
|
||||||
@@ -271,6 +273,7 @@ urlpatterns = [
|
|||||||
re_path(r'^items/(?P<item>\d+)/delete$', item.ItemDelete.as_view(), name='event.items.delete'),
|
re_path(r'^items/(?P<item>\d+)/delete$', item.ItemDelete.as_view(), name='event.items.delete'),
|
||||||
re_path(r'^items/typeahead/meta/$', typeahead.item_meta_values, name='event.items.meta.typeahead'),
|
re_path(r'^items/typeahead/meta/$', typeahead.item_meta_values, name='event.items.meta.typeahead'),
|
||||||
re_path(r'^items/select2$', typeahead.items_select2, name='event.items.select2'),
|
re_path(r'^items/select2$', typeahead.items_select2, name='event.items.select2'),
|
||||||
|
re_path(r'^items/select2/itemvar$', typeahead.itemvar_select2, name='event.items.itemvar.select2'),
|
||||||
re_path(r'^items/select2/variation$', typeahead.variations_select2, name='event.items.variations.select2'),
|
re_path(r'^items/select2/variation$', typeahead.variations_select2, name='event.items.variations.select2'),
|
||||||
re_path(r'^categories/$', item.CategoryList.as_view(), name='event.items.categories'),
|
re_path(r'^categories/$', item.CategoryList.as_view(), name='event.items.categories'),
|
||||||
re_path(r'^categories/select2$', typeahead.category_select2, name='event.items.categories.select2'),
|
re_path(r'^categories/select2$', typeahead.category_select2, name='event.items.categories.select2'),
|
||||||
@@ -321,6 +324,7 @@ urlpatterns = [
|
|||||||
re_path(r'^vouchers/add$', vouchers.VoucherCreate.as_view(), name='event.vouchers.add'),
|
re_path(r'^vouchers/add$', vouchers.VoucherCreate.as_view(), name='event.vouchers.add'),
|
||||||
re_path(r'^vouchers/go$', vouchers.VoucherGo.as_view(), name='event.vouchers.go'),
|
re_path(r'^vouchers/go$', vouchers.VoucherGo.as_view(), name='event.vouchers.go'),
|
||||||
re_path(r'^vouchers/bulk_add$', vouchers.VoucherBulkCreate.as_view(), name='event.vouchers.bulk'),
|
re_path(r'^vouchers/bulk_add$', vouchers.VoucherBulkCreate.as_view(), name='event.vouchers.bulk'),
|
||||||
|
re_path(r'^vouchers/bulk_add/mail_preview$', vouchers.VoucherBulkMailPreview.as_view(), name='event.vouchers.bulk.mail_preview'),
|
||||||
re_path(r'^vouchers/bulk_action$', vouchers.VoucherBulkAction.as_view(), name='event.vouchers.bulkaction'),
|
re_path(r'^vouchers/bulk_action$', vouchers.VoucherBulkAction.as_view(), name='event.vouchers.bulkaction'),
|
||||||
re_path(r'^orders/(?P<code>[0-9A-Z]+)/transition$', orders.OrderTransition.as_view(),
|
re_path(r'^orders/(?P<code>[0-9A-Z]+)/transition$', orders.OrderTransition.as_view(),
|
||||||
name='event.order.transition'),
|
name='event.order.transition'),
|
||||||
|
|||||||
@@ -712,8 +712,11 @@ class MailSettingsPreview(EventPermissionRequiredMixin, View):
|
|||||||
ctx = {}
|
ctx = {}
|
||||||
for p in get_available_placeholders(self.request.event, MailSettingsForm.base_context[item]).values():
|
for p in get_available_placeholders(self.request.event, MailSettingsForm.base_context[item]).values():
|
||||||
s = str(p.render_sample(self.request.event))
|
s = str(p.render_sample(self.request.event))
|
||||||
if s.strip().startswith('*'):
|
if s.strip().startswith('* '):
|
||||||
ctx[p.identifier] = s
|
ctx[p.identifier] = '<div class="placeholder" title="{}">{}</div>'.format(
|
||||||
|
_('This value will be replaced based on dynamic parameters.'),
|
||||||
|
markdown_compile_email(s)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
ctx[p.identifier] = '<span class="placeholder" title="{}">{}</span>'.format(
|
ctx[p.identifier] = '<span class="placeholder" title="{}">{}</span>'.format(
|
||||||
_('This value will be replaced based on dynamic parameters.'),
|
_('This value will be replaced based on dynamic parameters.'),
|
||||||
|
|||||||
@@ -306,10 +306,11 @@ class EventWizard(SafeSessionWizardView):
|
|||||||
event.set_active_plugins(settings.PRETIX_PLUGINS_DEFAULT.split(","),
|
event.set_active_plugins(settings.PRETIX_PLUGINS_DEFAULT.split(","),
|
||||||
allow_restricted=settings.PRETIX_PLUGINS_DEFAULT.split(","))
|
allow_restricted=settings.PRETIX_PLUGINS_DEFAULT.split(","))
|
||||||
event.save(update_fields=['plugins'])
|
event.save(update_fields=['plugins'])
|
||||||
event.checkin_lists.create(
|
if not event.has_subevents:
|
||||||
name=_('Default'),
|
event.checkin_lists.create(
|
||||||
all_products=True
|
name=_('Default'),
|
||||||
)
|
all_products=True
|
||||||
|
)
|
||||||
event.set_defaults()
|
event.set_defaults()
|
||||||
|
|
||||||
if basics_data['tax_rate']:
|
if basics_data['tax_rate']:
|
||||||
|
|||||||
@@ -2276,7 +2276,7 @@ class ExportMixin:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
if self.scheduled:
|
if self.scheduled:
|
||||||
initial = self.scheduled.export_form_data
|
initial = dict(self.scheduled.export_form_data)
|
||||||
|
|
||||||
test_form = ExporterForm(data=self.request.GET, prefix=ex.identifier)
|
test_form = ExporterForm(data=self.request.GET, prefix=ex.identifier)
|
||||||
test_form.fields = ex.export_form_fields
|
test_form.fields = ex.export_form_fields
|
||||||
|
|||||||
@@ -1550,7 +1550,7 @@ class ExportMixin:
|
|||||||
if id != ex.identifier:
|
if id != ex.identifier:
|
||||||
continue
|
continue
|
||||||
if self.scheduled:
|
if self.scheduled:
|
||||||
initial = self.scheduled.export_form_data
|
initial = dict(self.scheduled.export_form_data)
|
||||||
|
|
||||||
test_form = ExporterForm(data=self.request.GET, prefix=ex.identifier)
|
test_form = ExporterForm(data=self.request.GET, prefix=ex.identifier)
|
||||||
test_form.fields = ex.export_form_fields
|
test_form.fields = ex.export_form_fields
|
||||||
|
|||||||
@@ -251,7 +251,9 @@ class SubEventEditorMixin(MetaDataEditorMixin):
|
|||||||
'include_pending': False,
|
'include_pending': False,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
extra = 0
|
|
||||||
|
if not self.request.event.checkin_lists.filter(subevent__isnull=True).exists():
|
||||||
|
extra = 1
|
||||||
|
|
||||||
formsetclass = inlineformset_factory(
|
formsetclass = inlineformset_factory(
|
||||||
SubEvent, CheckinList,
|
SubEvent, CheckinList,
|
||||||
|
|||||||
@@ -545,6 +545,47 @@ def checkinlist_select2(request, **kwargs):
|
|||||||
return JsonResponse(doc)
|
return JsonResponse(doc)
|
||||||
|
|
||||||
|
|
||||||
|
@event_permission_required(None)
|
||||||
|
def itemvar_select2(request, **kwargs):
|
||||||
|
query = request.GET.get('query', '')
|
||||||
|
try:
|
||||||
|
page = int(request.GET.get('page', '1'))
|
||||||
|
except ValueError:
|
||||||
|
page = 1
|
||||||
|
|
||||||
|
pagesize = 20
|
||||||
|
offset = (page - 1) * pagesize
|
||||||
|
|
||||||
|
choices = []
|
||||||
|
|
||||||
|
# We are very unlikely to need pagination
|
||||||
|
itemqs = request.event.items.prefetch_related('variations').filter(Q(name__icontains=i18ncomp(query)) | Q(internal_name__icontains=query))
|
||||||
|
total = itemqs.count()
|
||||||
|
|
||||||
|
for i in itemqs[offset:offset + pagesize]:
|
||||||
|
variations = list(i.variations.all())
|
||||||
|
if variations:
|
||||||
|
choices.append((str(i.pk), _('{product} – Any variation').format(product=i), not i.active))
|
||||||
|
for v in variations:
|
||||||
|
choices.append(('%d-%d' % (i.pk, v.pk), '%s – %s' % (i, v.value), not v.active))
|
||||||
|
else:
|
||||||
|
choices.append((str(i.pk), str(i), not i.active))
|
||||||
|
|
||||||
|
doc = {
|
||||||
|
'results': [
|
||||||
|
{
|
||||||
|
'id': k,
|
||||||
|
'text': str(v),
|
||||||
|
}
|
||||||
|
for k, v, d in choices
|
||||||
|
],
|
||||||
|
'pagination': {
|
||||||
|
"more": total >= (offset + pagesize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return JsonResponse(doc)
|
||||||
|
|
||||||
|
|
||||||
@event_permission_required(None)
|
@event_permission_required(None)
|
||||||
def itemvarquota_select2(request, **kwargs):
|
def itemvarquota_select2(request, **kwargs):
|
||||||
query = request.GET.get('query', '')
|
query = request.GET.get('query', '')
|
||||||
@@ -831,3 +872,71 @@ def item_meta_values(request, organizer, event):
|
|||||||
)
|
)
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@organizer_permission_required(("can_view_orders", "can_change_organizer_settings"))
|
||||||
|
# This decorator is a bit of a hack since this is not technically an organizer permission, but it does the job here --
|
||||||
|
# anyone who can see orders for any event can see the check-in log view where this is used as a filter
|
||||||
|
def devices_select2(request, **kwargs):
|
||||||
|
query = request.GET.get('query', '')
|
||||||
|
try:
|
||||||
|
page = int(request.GET.get('page', '1'))
|
||||||
|
except ValueError:
|
||||||
|
page = 1
|
||||||
|
|
||||||
|
qq = (
|
||||||
|
Q(name__icontains=query) | Q(hardware_brand__icontains=query) | Q(hardware_model__icontains=query) |
|
||||||
|
Q(unique_serial__istartswith=query)
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
qq |= Q(device_id=int(query))
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
qs = request.organizer.devices.filter(qq).order_by('device_id')
|
||||||
|
|
||||||
|
total = qs.count()
|
||||||
|
pagesize = 20
|
||||||
|
offset = (page - 1) * pagesize
|
||||||
|
doc = {
|
||||||
|
'results': [
|
||||||
|
{
|
||||||
|
'id': e.pk,
|
||||||
|
'text': str(e),
|
||||||
|
}
|
||||||
|
for e in qs[offset:offset + pagesize]
|
||||||
|
],
|
||||||
|
'pagination': {
|
||||||
|
"more": total >= (offset + pagesize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return JsonResponse(doc)
|
||||||
|
|
||||||
|
|
||||||
|
@organizer_permission_required(("can_view_orders", "can_change_organizer_settings"))
|
||||||
|
# This decorator is a bit of a hack since this is not technically an organizer permission, but it does the job here --
|
||||||
|
# anyone who can see orders for any event can see the check-in log view where this is used as a filter
|
||||||
|
def gate_select2(request, **kwargs):
|
||||||
|
query = request.GET.get('query', '')
|
||||||
|
try:
|
||||||
|
page = int(request.GET.get('page', '1'))
|
||||||
|
except ValueError:
|
||||||
|
page = 1
|
||||||
|
|
||||||
|
qs = request.organizer.gates.filter(Q(name__icontains=query) | Q(identifier__icontains=query)).order_by('name')
|
||||||
|
|
||||||
|
total = qs.count()
|
||||||
|
pagesize = 20
|
||||||
|
offset = (page - 1) * pagesize
|
||||||
|
doc = {
|
||||||
|
'results': [
|
||||||
|
{
|
||||||
|
'id': e.pk,
|
||||||
|
'text': str(e),
|
||||||
|
}
|
||||||
|
for e in qs[offset:offset + pagesize]
|
||||||
|
],
|
||||||
|
'pagination': {
|
||||||
|
"more": total >= (offset + pagesize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return JsonResponse(doc)
|
||||||
|
|||||||
@@ -35,6 +35,7 @@
|
|||||||
|
|
||||||
import io
|
import io
|
||||||
|
|
||||||
|
import bleach
|
||||||
from defusedcsv import csv
|
from defusedcsv import csv
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
@@ -56,10 +57,12 @@ from django.views.generic import (
|
|||||||
CreateView, ListView, TemplateView, UpdateView, View,
|
CreateView, ListView, TemplateView, UpdateView, View,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from pretix.base.email import get_available_placeholders
|
||||||
from pretix.base.models import CartPosition, LogEntry, Voucher
|
from pretix.base.models import CartPosition, LogEntry, Voucher
|
||||||
from pretix.base.models.vouchers import generate_codes
|
from pretix.base.models.vouchers import generate_codes
|
||||||
from pretix.base.services.locking import NoLockManager
|
from pretix.base.services.locking import NoLockManager
|
||||||
from pretix.base.services.vouchers import vouchers_send
|
from pretix.base.services.vouchers import vouchers_send
|
||||||
|
from pretix.base.templatetags.rich_text import markdown_compile_email
|
||||||
from pretix.base.views.tasks import AsyncFormView
|
from pretix.base.views.tasks import AsyncFormView
|
||||||
from pretix.control.forms.filter import VoucherFilterForm, VoucherTagFilterForm
|
from pretix.control.forms.filter import VoucherFilterForm, VoucherTagFilterForm
|
||||||
from pretix.control.forms.vouchers import VoucherBulkForm, VoucherForm
|
from pretix.control.forms.vouchers import VoucherBulkForm, VoucherForm
|
||||||
@@ -67,6 +70,7 @@ from pretix.control.permissions import EventPermissionRequiredMixin
|
|||||||
from pretix.control.signals import voucher_form_class
|
from pretix.control.signals import voucher_form_class
|
||||||
from pretix.control.views import PaginationMixin
|
from pretix.control.views import PaginationMixin
|
||||||
from pretix.helpers.compat import CompatDeleteView
|
from pretix.helpers.compat import CompatDeleteView
|
||||||
|
from pretix.helpers.format import format_map
|
||||||
from pretix.helpers.models import modelcopy
|
from pretix.helpers.models import modelcopy
|
||||||
|
|
||||||
|
|
||||||
@@ -509,6 +513,52 @@ class VoucherBulkCreate(EventPermissionRequiredMixin, AsyncFormView):
|
|||||||
return super().form_invalid(form)
|
return super().form_invalid(form)
|
||||||
|
|
||||||
|
|
||||||
|
class VoucherBulkMailPreview(EventPermissionRequiredMixin, View):
|
||||||
|
permission = 'can_change_vouchers'
|
||||||
|
|
||||||
|
# return the origin text if key is missing in dict
|
||||||
|
class SafeDict(dict):
|
||||||
|
def __missing__(self, key):
|
||||||
|
return '{' + key + '}'
|
||||||
|
|
||||||
|
# get all supported placeholders with dummy values
|
||||||
|
def placeholders(self, item):
|
||||||
|
ctx = {}
|
||||||
|
base_ctx = ['event', 'name']
|
||||||
|
if item == 'send_message':
|
||||||
|
base_ctx += ['voucher_list']
|
||||||
|
for p in get_available_placeholders(self.request.event, base_ctx).values():
|
||||||
|
s = str(p.render_sample(self.request.event))
|
||||||
|
if s.strip().startswith('* ') or s.startswith(' '):
|
||||||
|
ctx[p.identifier] = '<div class="placeholder" title="{}">{}</div>'.format(
|
||||||
|
_('This value will be replaced based on dynamic parameters.'),
|
||||||
|
markdown_compile_email(s)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
ctx[p.identifier] = '<span class="placeholder" title="{}">{}</span>'.format(
|
||||||
|
_('This value will be replaced based on dynamic parameters.'),
|
||||||
|
s
|
||||||
|
)
|
||||||
|
return self.SafeDict(ctx)
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
preview_item = request.POST.get('item', '')
|
||||||
|
if preview_item not in ('send_message', 'send_subject'):
|
||||||
|
return HttpResponseBadRequest(_('invalid item'))
|
||||||
|
msgs = {}
|
||||||
|
if "subject" in preview_item:
|
||||||
|
msgs["all"] = format_map(bleach.clean(request.POST.get(preview_item, "")), self.placeholders(preview_item))
|
||||||
|
else:
|
||||||
|
msgs["all"] = markdown_compile_email(
|
||||||
|
format_map(request.POST.get(preview_item), self.placeholders(preview_item))
|
||||||
|
)
|
||||||
|
|
||||||
|
return JsonResponse({
|
||||||
|
'item': preview_item,
|
||||||
|
'msgs': msgs
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
class VoucherRNG(EventPermissionRequiredMixin, View):
|
class VoucherRNG(EventPermissionRequiredMixin, View):
|
||||||
permission = 'can_change_vouchers'
|
permission = 'can_change_vouchers'
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
# <https://www.gnu.org/licenses/>.
|
# <https://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
import re
|
import re
|
||||||
|
import types
|
||||||
from inspect import isgenerator
|
from inspect import isgenerator
|
||||||
|
|
||||||
from openpyxl import Workbook
|
from openpyxl import Workbook
|
||||||
@@ -140,9 +141,9 @@ class SafeWorkbook(Workbook):
|
|||||||
# monkeypatch existing sheets
|
# monkeypatch existing sheets
|
||||||
for s in self._sheets:
|
for s in self._sheets:
|
||||||
if self.write_only:
|
if self.write_only:
|
||||||
s.append = SafeWriteOnlyWorksheet.append
|
s.append = types.MethodType(SafeWriteOnlyWorksheet.append, s)
|
||||||
else:
|
else:
|
||||||
s.append = SafeWorksheet.append
|
s.append = types.MethodType(SafeWorksheet.append, s)
|
||||||
|
|
||||||
def create_sheet(self, title=None, index=None):
|
def create_sheet(self, title=None, index=None):
|
||||||
if self.read_only:
|
if self.read_only:
|
||||||
|
|||||||
+1147
-1080
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2023-02-27 08:00+0000\n"
|
"POT-Creation-Date: 2023-03-08 15:04+0000\n"
|
||||||
"PO-Revision-Date: 2021-09-15 11:22+0000\n"
|
"PO-Revision-Date: 2021-09-15 11:22+0000\n"
|
||||||
"Last-Translator: Mohamed Tawfiq <mtawfiq@wafyapp.com>\n"
|
"Last-Translator: Mohamed Tawfiq <mtawfiq@wafyapp.com>\n"
|
||||||
"Language-Team: Arabic <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
"Language-Team: Arabic <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||||
|
|||||||
+1128
-1071
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2023-02-27 08:00+0000\n"
|
"POT-Creation-Date: 2023-03-08 15:04+0000\n"
|
||||||
"PO-Revision-Date: 2020-12-19 07:00+0000\n"
|
"PO-Revision-Date: 2020-12-19 07:00+0000\n"
|
||||||
"Last-Translator: albert <albert.serra.monner@gmail.com>\n"
|
"Last-Translator: albert <albert.serra.monner@gmail.com>\n"
|
||||||
"Language-Team: Catalan <https://translate.pretix.eu/projects/pretix/pretix-"
|
"Language-Team: Catalan <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||||
|
|||||||
+1394
-1355
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2023-02-27 08:00+0000\n"
|
"POT-Creation-Date: 2023-03-08 15:04+0000\n"
|
||||||
"PO-Revision-Date: 2023-01-13 08:00+0000\n"
|
"PO-Revision-Date: 2023-01-13 08:00+0000\n"
|
||||||
"Last-Translator: tree <jan.stransky@arnal.cz>\n"
|
"Last-Translator: tree <jan.stransky@arnal.cz>\n"
|
||||||
"Language-Team: Czech <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
"Language-Team: Czech <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||||
|
|||||||
+1126
-1071
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: \n"
|
"Project-Id-Version: \n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2023-02-27 08:00+0000\n"
|
"POT-Creation-Date: 2023-03-08 15:04+0000\n"
|
||||||
"PO-Revision-Date: 2022-12-01 17:00+0000\n"
|
"PO-Revision-Date: 2022-12-01 17:00+0000\n"
|
||||||
"Last-Translator: Mie Frydensbjerg <mif@aarhus.dk>\n"
|
"Last-Translator: Mie Frydensbjerg <mif@aarhus.dk>\n"
|
||||||
"Language-Team: Danish <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
"Language-Team: Danish <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||||
|
|||||||
+1162
-1084
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: \n"
|
"Project-Id-Version: \n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2023-02-27 08:00+0000\n"
|
"POT-Creation-Date: 2023-03-08 15:04+0000\n"
|
||||||
"PO-Revision-Date: 2023-02-09 13:56+0000\n"
|
"PO-Revision-Date: 2023-02-09 13:56+0000\n"
|
||||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||||
"Language-Team: German <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
"Language-Team: German <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||||
|
|||||||
@@ -333,6 +333,7 @@ Veranstaltername
|
|||||||
Veranstalterprofil
|
Veranstalterprofil
|
||||||
Veranstalterseite
|
Veranstalterseite
|
||||||
Veranstalterübersicht
|
Veranstalterübersicht
|
||||||
|
veranstalterweiten
|
||||||
Veranstaltungs
|
Veranstaltungs
|
||||||
veranstaltungsweiten
|
veranstaltungsweiten
|
||||||
Verfügbarkeitsberechnung
|
Verfügbarkeitsberechnung
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: \n"
|
"Project-Id-Version: \n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2023-02-27 08:00+0000\n"
|
"POT-Creation-Date: 2023-03-08 15:04+0000\n"
|
||||||
"PO-Revision-Date: 2023-02-09 13:56+0000\n"
|
"PO-Revision-Date: 2023-02-09 13:56+0000\n"
|
||||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||||
"Language-Team: German (informal) <https://translate.pretix.eu/projects/"
|
"Language-Team: German (informal) <https://translate.pretix.eu/projects/"
|
||||||
|
|||||||
@@ -333,6 +333,7 @@ Veranstaltername
|
|||||||
Veranstalterprofil
|
Veranstalterprofil
|
||||||
Veranstalterseite
|
Veranstalterseite
|
||||||
Veranstalterübersicht
|
Veranstalterübersicht
|
||||||
|
veranstalterweiten
|
||||||
Veranstaltungs
|
Veranstaltungs
|
||||||
veranstaltungsweiten
|
veranstaltungsweiten
|
||||||
Verfügbarkeitsberechnung
|
Verfügbarkeitsberechnung
|
||||||
|
|||||||
+1109
-1058
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2023-02-27 08:00+0000\n"
|
"POT-Creation-Date: 2023-03-08 15:04+0000\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
|||||||
+1158
-1100
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2023-02-27 08:00+0000\n"
|
"POT-Creation-Date: 2023-03-08 15:04+0000\n"
|
||||||
"PO-Revision-Date: 2019-10-03 19:00+0000\n"
|
"PO-Revision-Date: 2019-10-03 19:00+0000\n"
|
||||||
"Last-Translator: Chris Spy <chrispiropoulou@hotmail.com>\n"
|
"Last-Translator: Chris Spy <chrispiropoulou@hotmail.com>\n"
|
||||||
"Language-Team: Greek <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
"Language-Team: Greek <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||||
|
|||||||
+1144
-1089
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2023-02-27 08:00+0000\n"
|
"POT-Creation-Date: 2023-03-08 15:04+0000\n"
|
||||||
"PO-Revision-Date: 2021-11-25 21:00+0000\n"
|
"PO-Revision-Date: 2021-11-25 21:00+0000\n"
|
||||||
"Last-Translator: Ismael Menéndez Fernández <ismael.menendez@balidea.com>\n"
|
"Last-Translator: Ismael Menéndez Fernández <ismael.menendez@balidea.com>\n"
|
||||||
"Language-Team: Spanish <https://translate.pretix.eu/projects/pretix/pretix-"
|
"Language-Team: Spanish <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||||
|
|||||||
+1155
-1084
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2023-02-27 08:00+0000\n"
|
"POT-Creation-Date: 2023-03-08 15:04+0000\n"
|
||||||
"PO-Revision-Date: 2021-11-10 05:00+0000\n"
|
"PO-Revision-Date: 2021-11-10 05:00+0000\n"
|
||||||
"Last-Translator: Jaakko Rinta-Filppula <jaakko@r-f.fi>\n"
|
"Last-Translator: Jaakko Rinta-Filppula <jaakko@r-f.fi>\n"
|
||||||
"Language-Team: Finnish <https://translate.pretix.eu/projects/pretix/pretix-"
|
"Language-Team: Finnish <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||||
|
|||||||
+1124
-1069
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: French\n"
|
"Project-Id-Version: French\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2023-02-27 08:00+0000\n"
|
"POT-Creation-Date: 2023-03-08 15:04+0000\n"
|
||||||
"PO-Revision-Date: 2023-01-17 01:00+0000\n"
|
"PO-Revision-Date: 2023-01-17 01:00+0000\n"
|
||||||
"Last-Translator: Maurice Kaag <maurice@kaag.me>\n"
|
"Last-Translator: Maurice Kaag <maurice@kaag.me>\n"
|
||||||
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||||
|
|||||||
+1135
-1078
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2023-02-27 08:00+0000\n"
|
"POT-Creation-Date: 2023-03-08 15:04+0000\n"
|
||||||
"PO-Revision-Date: 2022-02-22 22:00+0000\n"
|
"PO-Revision-Date: 2022-02-22 22:00+0000\n"
|
||||||
"Last-Translator: Ismael Menéndez Fernández <ismael.menendez@balidea.com>\n"
|
"Last-Translator: Ismael Menéndez Fernández <ismael.menendez@balidea.com>\n"
|
||||||
"Language-Team: Galician <https://translate.pretix.eu/projects/pretix/pretix-"
|
"Language-Team: Galician <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||||
|
|||||||
+1109
-1058
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2023-02-27 08:00+0000\n"
|
"POT-Creation-Date: 2023-03-08 15:04+0000\n"
|
||||||
"PO-Revision-Date: 2021-09-24 13:54+0000\n"
|
"PO-Revision-Date: 2021-09-24 13:54+0000\n"
|
||||||
"Last-Translator: ofirtro <ofir.tro@gmail.com>\n"
|
"Last-Translator: ofirtro <ofir.tro@gmail.com>\n"
|
||||||
"Language-Team: Hebrew <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
"Language-Team: Hebrew <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||||
|
|||||||
+1109
-1058
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2023-02-27 08:00+0000\n"
|
"POT-Creation-Date: 2023-03-08 15:04+0000\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: Automatically generated\n"
|
"Last-Translator: Automatically generated\n"
|
||||||
"Language-Team: none\n"
|
"Language-Team: none\n"
|
||||||
|
|||||||
+1119
-1064
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2023-02-27 08:00+0000\n"
|
"POT-Creation-Date: 2023-03-08 15:04+0000\n"
|
||||||
"PO-Revision-Date: 2020-01-24 08:00+0000\n"
|
"PO-Revision-Date: 2020-01-24 08:00+0000\n"
|
||||||
"Last-Translator: Prokaj Miklós <mixolid0@gmail.com>\n"
|
"Last-Translator: Prokaj Miklós <mixolid0@gmail.com>\n"
|
||||||
"Language-Team: Hungarian <https://translate.pretix.eu/projects/pretix/pretix-"
|
"Language-Team: Hungarian <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||||
|
|||||||
+1148
-1080
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2023-02-27 08:00+0000\n"
|
"POT-Creation-Date: 2023-03-08 15:04+0000\n"
|
||||||
"PO-Revision-Date: 2022-05-08 19:00+0000\n"
|
"PO-Revision-Date: 2022-05-08 19:00+0000\n"
|
||||||
"Last-Translator: Emanuele Signoretta <signorettae@gmail.com>\n"
|
"Last-Translator: Emanuele Signoretta <signorettae@gmail.com>\n"
|
||||||
"Language-Team: Italian <https://translate.pretix.eu/projects/pretix/pretix-"
|
"Language-Team: Italian <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||||
|
|||||||
+1109
-1058
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2023-02-27 08:00+0000\n"
|
"POT-Creation-Date: 2023-03-08 15:04+0000\n"
|
||||||
"PO-Revision-Date: 2022-03-15 00:00+0000\n"
|
"PO-Revision-Date: 2022-03-15 00:00+0000\n"
|
||||||
"Last-Translator: Yuriko Matsunami <y.matsunami@enobyte.com>\n"
|
"Last-Translator: Yuriko Matsunami <y.matsunami@enobyte.com>\n"
|
||||||
"Language-Team: Japanese <https://translate.pretix.eu/projects/pretix/pretix-"
|
"Language-Team: Japanese <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||||
|
|||||||
+1150
-1079
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2023-02-27 08:00+0000\n"
|
"POT-Creation-Date: 2023-03-08 15:04+0000\n"
|
||||||
"PO-Revision-Date: 2022-04-06 03:00+0000\n"
|
"PO-Revision-Date: 2022-04-06 03:00+0000\n"
|
||||||
"Last-Translator: Liga V <lerning_by_dreaming@gmx.de>\n"
|
"Last-Translator: Liga V <lerning_by_dreaming@gmx.de>\n"
|
||||||
"Language-Team: Latvian <https://translate.pretix.eu/projects/pretix/pretix-"
|
"Language-Team: Latvian <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2023-02-27 08:00+0000\n"
|
"POT-Creation-Date: 2023-03-08 15:04+0000\n"
|
||||||
"PO-Revision-Date: 2022-06-20 02:00+0000\n"
|
"PO-Revision-Date: 2022-06-20 02:00+0000\n"
|
||||||
"Last-Translator: fyksen <fredrik@fyksen.me>\n"
|
"Last-Translator: fyksen <fredrik@fyksen.me>\n"
|
||||||
"Language-Team: Norwegian Bokmål <https://translate.pretix.eu/projects/pretix/"
|
"Language-Team: Norwegian Bokmål <https://translate.pretix.eu/projects/pretix/"
|
||||||
|
|||||||
+1151
-1087
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: 1\n"
|
"Project-Id-Version: 1\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2023-02-27 08:00+0000\n"
|
"POT-Creation-Date: 2023-03-08 15:04+0000\n"
|
||||||
"PO-Revision-Date: 2021-10-29 02:00+0000\n"
|
"PO-Revision-Date: 2021-10-29 02:00+0000\n"
|
||||||
"Last-Translator: Maarten van den Berg <maartenberg1@gmail.com>\n"
|
"Last-Translator: Maarten van den Berg <maartenberg1@gmail.com>\n"
|
||||||
"Language-Team: Dutch <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
"Language-Team: Dutch <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2023-02-27 08:00+0000\n"
|
"POT-Creation-Date: 2023-03-08 15:04+0000\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: Automatically generated\n"
|
"Last-Translator: Automatically generated\n"
|
||||||
"Language-Team: none\n"
|
"Language-Team: none\n"
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user