mirror of
https://github.com/pretix/pretix.git
synced 2026-05-07 15:34:02 +00:00
@@ -35,8 +35,8 @@ from i18nfield.forms import I18nInlineFormSet
|
||||
from pretix.base.forms import I18nModelForm
|
||||
from pretix.base.forms.widgets import DatePickerWidget, TimePickerWidget
|
||||
from pretix.base.models.event import SubEvent, SubEventMetaValue
|
||||
from pretix.base.models.items import SubEventItem
|
||||
from pretix.base.reldate import RelativeDateTimeField
|
||||
from pretix.base.models.items import SubEventItem, SubEventItemVariation
|
||||
from pretix.base.reldate import RelativeDateTimeField, RelativeDateWrapper
|
||||
from pretix.base.templatetags.money import money_filter
|
||||
from pretix.control.forms import SplitDateTimeField, SplitDateTimePickerWidget
|
||||
from pretix.helpers.money import change_decimal_field
|
||||
@@ -263,10 +263,16 @@ class SubEventItemForm(SubEventItemOrVariationFormMixin, forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = SubEventItem
|
||||
fields = ['price', 'disabled']
|
||||
fields = ['price', 'disabled', 'available_from', 'available_until']
|
||||
widgets = {
|
||||
'available_from': SplitDateTimePickerWidget(),
|
||||
'available_until': SplitDateTimePickerWidget(),
|
||||
'price': forms.TextInput
|
||||
}
|
||||
field_classes = {
|
||||
'available_from': SplitDateTimeField,
|
||||
'available_until': SplitDateTimeField,
|
||||
}
|
||||
|
||||
|
||||
class SubEventItemVariationForm(SubEventItemOrVariationFormMixin, forms.ModelForm):
|
||||
@@ -276,11 +282,61 @@ class SubEventItemVariationForm(SubEventItemOrVariationFormMixin, forms.ModelFor
|
||||
self.fields['price'].label = '{} – {}'.format(str(self.item), self.variation.value)
|
||||
|
||||
class Meta:
|
||||
model = SubEventItem
|
||||
fields = ['price', 'disabled']
|
||||
model = SubEventItemVariation
|
||||
fields = ['price', 'disabled', 'available_from', 'available_until']
|
||||
widgets = {
|
||||
'available_from': SplitDateTimePickerWidget(),
|
||||
'available_until': SplitDateTimePickerWidget(),
|
||||
'price': forms.TextInput
|
||||
}
|
||||
field_classes = {
|
||||
'available_from': SplitDateTimeField,
|
||||
'available_until': SplitDateTimeField,
|
||||
}
|
||||
|
||||
|
||||
class BulkSubEventItemForm(SubEventItemForm):
|
||||
rel_available_from = RelativeDateTimeField(
|
||||
label=_('Available from'),
|
||||
required=False,
|
||||
limit_choices=('date_from', 'date_to'),
|
||||
)
|
||||
rel_available_until = RelativeDateTimeField(
|
||||
label=_('Available_until'),
|
||||
required=False,
|
||||
limit_choices=('date_from', 'date_to'),
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
del self.fields['available_from']
|
||||
del self.fields['available_until']
|
||||
if self.instance and self.instance.available_from and 'rel_available_from' not in self.initial:
|
||||
self.initial['rel_available_from'] = RelativeDateWrapper(self.instance.available_from)
|
||||
if self.instance and self.instance.available_until and 'rel_available_until' not in self.initial:
|
||||
self.initial['rel_available_until'] = RelativeDateWrapper(self.instance.available_until)
|
||||
|
||||
|
||||
class BulkSubEventItemVariationForm(SubEventItemVariationForm):
|
||||
rel_available_from = RelativeDateTimeField(
|
||||
label=_('Available from'),
|
||||
required=False,
|
||||
limit_choices=('date_from', 'date_to'),
|
||||
)
|
||||
rel_available_until = RelativeDateTimeField(
|
||||
label=_('Available_until'),
|
||||
required=False,
|
||||
limit_choices=('date_from', 'date_to'),
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
del self.fields['available_from']
|
||||
del self.fields['available_until']
|
||||
if self.instance and self.instance.available_from and 'rel_available_from' not in self.initial:
|
||||
self.initial['rel_available_from'] = RelativeDateWrapper(self.instance.available_from)
|
||||
if self.instance and self.instance.available_until and 'rel_available_until' not in self.initial:
|
||||
self.initial['rel_available_until'] = RelativeDateWrapper(self.instance.available_until)
|
||||
|
||||
|
||||
class QuotaFormSet(I18nInlineFormSet):
|
||||
|
||||
@@ -480,21 +480,34 @@
|
||||
</p>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Item prices" %}</legend>
|
||||
<legend>{% trans "Product settings" %}</legend>
|
||||
<p class="text-muted">
|
||||
{% trans "These settings are optional, if you leave them empty, the default values from the product settings will be used." %}
|
||||
</p>
|
||||
{% for f in itemvar_forms %}
|
||||
<div class="form-group">
|
||||
<div class="form-group subevent-itemvar-group">
|
||||
<label class="col-md-3 control-label" for="id_{{ f.prefix }}-price">
|
||||
{% if f.variation %}{{ f.item }} – {{ f.variation }}{% else %}{{ f.item }}{% endif %}
|
||||
<br>
|
||||
<span class="optional">{% trans "Optional" %}</span>
|
||||
</label>
|
||||
<div class="col-md-6">
|
||||
<div class="col-md-4">
|
||||
<label for="{{ f.price.id_for_label }}" class="text-muted">{% trans "Price" %}</label><br>
|
||||
{% bootstrap_field f.price addon_after=request.event.currency form_group_class="" layout="inline" %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="col-md-4">
|
||||
<br>
|
||||
{% bootstrap_field f.disabled layout="inline" form_group_class="" %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group subevent-itemvar-group">
|
||||
<div class="col-md-4 col-md-offset-3">
|
||||
<label for="{{ f.rel_available_from.id_for_label }}" class="text-muted">{% trans "Available from" %}</label><br>
|
||||
{% bootstrap_field f.rel_available_from form_group_class="" layout="inline" %}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="{{ f.rel_available_until.id_for_label }}" class="text-muted">{% trans "Available until" %}</label><br>
|
||||
{% bootstrap_field f.rel_available_until form_group_class="" layout="inline" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
|
||||
@@ -151,17 +151,29 @@
|
||||
<fieldset>
|
||||
<legend>{% trans "Item prices" %}</legend>
|
||||
{% for f in itemvar_forms %}
|
||||
<div class="form-group">
|
||||
<div class="form-group subevent-itemvar-group">
|
||||
<label class="col-md-3 control-label" for="id_{{ f.prefix }}-price">
|
||||
{% if f.variation %}{{ f.item }} – {{ f.variation }}{% else %}{{ f.item }}{% endif %}
|
||||
</label>
|
||||
<div class="col-md-6">
|
||||
<div class="col-md-4">
|
||||
<label for="{{ f.price.id_for_label }}" class="text-muted">{% trans "Price" %}</label><br>
|
||||
{% bootstrap_field f.price addon_after=request.event.currency form_group_class="" layout="bulkedit_inline" %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="col-md-5">
|
||||
<label class="text-muted"> </label><br>
|
||||
{% bootstrap_field f.disabled layout="bulkedit_inline" form_group_class="" %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group subevent-itemvar-group">
|
||||
<div class="col-md-4 col-md-offset-3">
|
||||
<label for="{{ f.available_from.id_for_label }}" class="text-muted">{% trans "Available from" %}</label><br>
|
||||
{% bootstrap_field f.available_from form_group_class="" layout="bulkedit_inline" %}
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<label for="{{ f.available_until.id_for_label }}" class="text-muted">{% trans "Available until" %}</label><br>
|
||||
{% bootstrap_field f.available_until form_group_class="" layout="bulkedit_inline" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
|
||||
@@ -122,21 +122,34 @@
|
||||
</p>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Item prices" %}</legend>
|
||||
<legend>{% trans "Product settings" %}</legend>
|
||||
<p class="text-muted">
|
||||
{% trans "These settings are optional, if you leave them empty, the default values from the product settings will be used." %}
|
||||
</p>
|
||||
{% for f in itemvar_forms %}
|
||||
<div class="form-group">
|
||||
<div class="form-group subevent-itemvar-group">
|
||||
<label class="col-md-3 control-label" for="id_{{ f.prefix }}-price">
|
||||
{% if f.variation %}{{ f.item }} – {{ f.variation }}{% else %}{{ f.item }}{% endif %}
|
||||
<br>
|
||||
<span class="optional">{% trans "Optional" %}</span>
|
||||
</label>
|
||||
<div class="col-md-6">
|
||||
<div class="col-md-4">
|
||||
<label for="{{ f.price.id_for_label }}" class="text-muted">{% trans "Price" %}</label><br>
|
||||
{% bootstrap_field f.price addon_after=request.event.currency form_group_class="" layout="inline" %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="col-md-4">
|
||||
<br>
|
||||
{% bootstrap_field f.disabled layout="inline" form_group_class="" %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group subevent-itemvar-group">
|
||||
<div class="col-md-4 col-md-offset-3">
|
||||
<label for="{{ f.available_from.id_for_label }}" class="text-muted">{% trans "Available from" %}</label><br>
|
||||
{% bootstrap_field f.available_from form_group_class="" layout="inline" %}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="{{ f.available_until.id_for_label }}" class="text-muted">{% trans "Available until" %}</label><br>
|
||||
{% bootstrap_field f.available_until form_group_class="" layout="inline" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
|
||||
@@ -68,9 +68,10 @@ from pretix.control.forms.checkin import SimpleCheckinListForm
|
||||
from pretix.control.forms.filter import SubEventFilterForm
|
||||
from pretix.control.forms.item import QuotaForm
|
||||
from pretix.control.forms.subevents import (
|
||||
CheckinListFormSet, QuotaFormSet, RRuleFormSet, SubEventBulkEditForm,
|
||||
SubEventBulkForm, SubEventForm, SubEventItemForm,
|
||||
SubEventItemVariationForm, SubEventMetaValueForm, TimeFormSet,
|
||||
BulkSubEventItemForm, BulkSubEventItemVariationForm, CheckinListFormSet,
|
||||
QuotaFormSet, RRuleFormSet, SubEventBulkEditForm, SubEventBulkForm,
|
||||
SubEventForm, SubEventItemForm, SubEventItemVariationForm,
|
||||
SubEventMetaValueForm, TimeFormSet,
|
||||
)
|
||||
from pretix.control.permissions import EventPermissionRequiredMixin
|
||||
from pretix.control.signals import subevent_forms
|
||||
@@ -193,6 +194,8 @@ class SubEventDelete(EventPermissionRequiredMixin, DeleteView):
|
||||
class SubEventEditorMixin(MetaDataEditorMixin):
|
||||
meta_form = SubEventMetaValueForm
|
||||
meta_model = SubEventMetaValue
|
||||
itemformclass = SubEventItemForm
|
||||
itemvarformclass = SubEventItemVariationForm
|
||||
|
||||
@cached_property
|
||||
def plugin_forms(self):
|
||||
@@ -391,11 +394,17 @@ class SubEventEditorMixin(MetaDataEditorMixin):
|
||||
|
||||
if self.copy_from:
|
||||
se_item_instances = {
|
||||
sei.item_id: SubEventItem(item=sei.item, price=sei.price, disabled=sei.disabled)
|
||||
sei.item_id: SubEventItem(
|
||||
item=sei.item, price=sei.price, disabled=sei.disabled,
|
||||
available_from=sei.available_from, available_until=sei.available_until
|
||||
)
|
||||
for sei in SubEventItem.objects.filter(subevent=self.copy_from).select_related('item')
|
||||
}
|
||||
se_var_instances = {
|
||||
sei.variation_id: SubEventItemVariation(variation=sei.variation, price=sei.price, disabled=sei.disabled)
|
||||
sei.variation_id: SubEventItemVariation(
|
||||
variation=sei.variation, price=sei.price, disabled=sei.disabled,
|
||||
available_from=sei.available_from, available_until=sei.available_until
|
||||
)
|
||||
for sei in SubEventItemVariation.objects.filter(subevent=self.copy_from).select_related('variation')
|
||||
}
|
||||
|
||||
@@ -404,7 +413,7 @@ class SubEventEditorMixin(MetaDataEditorMixin):
|
||||
if i.has_variations:
|
||||
for v in i.variations.all():
|
||||
inst = se_var_instances.get(v.pk) or SubEventItemVariation(subevent=self.object, variation=v)
|
||||
formlist.append(SubEventItemVariationForm(
|
||||
formlist.append(self.itemvarformclass(
|
||||
prefix='itemvar-{}'.format(v.pk),
|
||||
item=i, variation=v,
|
||||
instance=inst,
|
||||
@@ -412,7 +421,7 @@ class SubEventEditorMixin(MetaDataEditorMixin):
|
||||
))
|
||||
else:
|
||||
inst = se_item_instances.get(i.pk) or SubEventItem(subevent=self.object, item=i)
|
||||
formlist.append(SubEventItemForm(
|
||||
formlist.append(self.itemformclass(
|
||||
prefix='item-{}'.format(i.pk),
|
||||
item=i,
|
||||
instance=inst,
|
||||
@@ -641,6 +650,8 @@ class SubEventBulkCreate(SubEventEditorMixin, EventPermissionRequiredMixin, Crea
|
||||
permission = 'can_change_settings'
|
||||
context_object_name = 'subevent'
|
||||
form_class = SubEventBulkForm
|
||||
itemformclass = BulkSubEventItemForm
|
||||
itemvarformclass = BulkSubEventItemVariationForm
|
||||
|
||||
def is_valid(self, form):
|
||||
return self.rrule_formset.is_valid() and self.time_formset.is_valid() and super().is_valid(form)
|
||||
@@ -846,6 +857,18 @@ class SubEventBulkCreate(SubEventEditorMixin, EventPermissionRequiredMixin, Crea
|
||||
i = copy.copy(f.instance)
|
||||
i.pk = None
|
||||
i.subevent = se
|
||||
|
||||
i.available_from = (
|
||||
f.cleaned_data['rel_available_from'].datetime(se)
|
||||
if f.cleaned_data.get('rel_available_from')
|
||||
else None
|
||||
)
|
||||
i.available_until = (
|
||||
f.cleaned_data['rel_available_until'].datetime(se)
|
||||
if f.cleaned_data.get('rel_available_until')
|
||||
else None
|
||||
)
|
||||
|
||||
if isinstance(i, SubEventItem):
|
||||
to_save_items.append(i)
|
||||
else:
|
||||
@@ -962,16 +985,19 @@ class SubEventBulkEdit(SubEventQueryMixin, EventPermissionRequiredMixin, FormVie
|
||||
def cached_num(self):
|
||||
return self.get_queryset().count()
|
||||
|
||||
itemformclass = SubEventItemForm
|
||||
itemvarformclass = SubEventItemVariationForm
|
||||
|
||||
@cached_property
|
||||
def itemvar_forms(self):
|
||||
matches = defaultdict(list)
|
||||
for sei in SubEventItem.objects.filter(
|
||||
subevent__in=self.get_queryset()
|
||||
).order_by().values('item', 'price', 'disabled').annotate(c=Count('*')):
|
||||
).order_by().values('item', 'price', 'disabled', 'available_from', 'available_until').annotate(c=Count('*')):
|
||||
matches['item', sei['item']].append(sei)
|
||||
for sei in SubEventItemVariation.objects.filter(
|
||||
subevent__in=self.get_queryset()
|
||||
).order_by().values('variation', 'price', 'disabled').annotate(c=Count('*')):
|
||||
).order_by().values('variation', 'price', 'disabled', 'available_from', 'available_until').annotate(c=Count('*')):
|
||||
matches['variation', sei['variation']].append(sei)
|
||||
total = self.cached_num
|
||||
|
||||
@@ -981,10 +1007,13 @@ class SubEventBulkEdit(SubEventQueryMixin, EventPermissionRequiredMixin, FormVie
|
||||
for v in i.variations.all():
|
||||
m = matches['variation', v.pk]
|
||||
if m and len(m) == 1 and m[0]['c'] == total:
|
||||
inst = SubEventItemVariation(variation=v, disabled=m[0]['disabled'], price=m[0]['price'])
|
||||
inst = SubEventItemVariation(
|
||||
variation=v, disabled=m[0]['disabled'], price=m[0]['price'],
|
||||
available_from=m[0]['available_from'], available_until=m[0]['available_until']
|
||||
)
|
||||
else:
|
||||
inst = SubEventItemVariation(variation=v)
|
||||
formlist.append(SubEventItemVariationForm(
|
||||
formlist.append(self.itemvarformclass(
|
||||
prefix='itemvar-{}'.format(v.pk),
|
||||
item=i, variation=v,
|
||||
instance=inst,
|
||||
@@ -993,10 +1022,13 @@ class SubEventBulkEdit(SubEventQueryMixin, EventPermissionRequiredMixin, FormVie
|
||||
else:
|
||||
m = matches['item', i.pk]
|
||||
if m and len(m) == 1 and m[0]['c'] == total:
|
||||
inst = SubEventItem(item=i, disabled=m[0]['disabled'], price=m[0]['price'])
|
||||
inst = SubEventItem(
|
||||
item=i, disabled=m[0]['disabled'], price=m[0]['price'],
|
||||
available_from=m[0]['available_from'], available_until=m[0]['available_until']
|
||||
)
|
||||
else:
|
||||
inst = SubEventItem(item=i)
|
||||
formlist.append(SubEventItemForm(
|
||||
formlist.append(self.itemformclass(
|
||||
prefix='item-{}'.format(i.pk),
|
||||
item=i,
|
||||
instance=inst,
|
||||
@@ -1405,12 +1437,16 @@ class SubEventBulkEdit(SubEventQueryMixin, EventPermissionRequiredMixin, FormVie
|
||||
u['price'] = f.cleaned_data.get('price')
|
||||
if f.prefix + 'disabled' in self.request.POST.getlist('_bulk'):
|
||||
u['disabled'] = f.cleaned_data.get('disabled')
|
||||
if f.prefix + 'available_from' in self.request.POST.getlist('_bulk'):
|
||||
u['available_from'] = f.cleaned_data.get('available_from')
|
||||
if f.prefix + 'available_until' in self.request.POST.getlist('_bulk'):
|
||||
u['available_until'] = f.cleaned_data.get('available_until')
|
||||
|
||||
if not u:
|
||||
continue
|
||||
|
||||
if isinstance(f, SubEventItemForm):
|
||||
if u.get('price') is None and not u.get('disabled'):
|
||||
if u.get('price') is None and not u.get('disabled') and not u.get('available_from') and not u.get('available_until'):
|
||||
SubEventItem.objects.filter(
|
||||
subevent__in=self.get_queryset(),
|
||||
item=f.instance.item,
|
||||
@@ -1423,7 +1459,7 @@ class SubEventBulkEdit(SubEventQueryMixin, EventPermissionRequiredMixin, FormVie
|
||||
defaults=u
|
||||
)
|
||||
elif isinstance(f, SubEventItemVariationForm):
|
||||
if u.get('price') is None and not u.get('disabled'):
|
||||
if u.get('price') is None and not u.get('disabled') and not u.get('available_from') and not u.get('available_until'):
|
||||
SubEventItemVariation.objects.filter(
|
||||
subevent__in=self.get_queryset(),
|
||||
variation=f.instance.variation,
|
||||
|
||||
Reference in New Issue
Block a user